In this lecture we are going to look at the notebook which implements a feed for neural network for
image classification on the amnesty data set.
This lecture is going to walk you through a prepared code lab notebook.
Although a very good exercise which I always recommend is once you know how this is done to try and
recreate it yourself with as few references as possible as usual you can look at the title of the notebook
to determine what notebook we are currently looking at.
So as usual we're going to start by importing pi torch name pi and map lib.
We'll also be using the torch vision library which includes the MLS data set and utility functions for
handling images.
We'll start by downloading the train data which happens when we call the torch vision that data sets
that M.A. function for the first time.
Let's go through each of these arguments one by one so you understand that.
First we have the route argument where we specify the file path we want to download the data to.
In this case we're going to download the data to the local directory.
Next we set train equal to true which indicates that this function will return the train data set.
Next we said transform to transforms that to tensor which is an operation from the torch vision library
that does some useful pre processing for us.
This way everything we want to do to the data happens inside this function.
We'll see later how we can make even more sophisticated use of the transform argument.
Finally we have the download argument which tells PI to urge to download the data from the printout
we can see that in fact 4000 downloaded.
If you read it carefully you can deduce that these are the train inputs.
The train labels the test inputs and the test labels.
You can also see that they've been downloaded to the folder amnesty slash raw.
Next we're going to print out the data attribute of the train dataset which as we know represents the
input data.
Really what we want to do is make sure what we're seeing makes sense.
So what we can see is that this appears to be a three dimensional array of a bunch of zeros.
It also has these type equal to talks that you went ape.
Does this make sense.
In fact it does.
There are of course some non-zero values in the dataset but we can't see them because a large percentage
of each image in the AMA's dataset is black.
Only the digits themselves are nonblack and those take up a tiny part of the image so it's not surprising
that if we print out all the values near the edges of the image that they would be all zero.
We can verify this by checking the maximum value in the tensor which is as expected 255.
Next we can check the shape of the tensor which is sixty thousand by twenty eight by twenty eight.
As I mentioned earlier finally we can print the target's actual view which returns a one dimensional
tensor of integers which appear to be between 0 and 9.
As expected
next we're going to call the M.A. function again but this time we want the test data set.
So we need to pass in a train equal to False.
Notice how this time nothing is downloaded.
That's because the four files we needed to download were already downloaded in the previous step.
When we check the shape of the input data we see that it's ten thousand by twenty eight by twenty eight
as promised.
So there are sixty thousand training samples and 10000 test samples
now that we have our data.
It's time to build our model.
As you can see it's exactly as I've specified in the earlier lectures.
We have a linear layer followed by a real you followed by another linear layer all wrapped in a sequential
and there is no need for a soft Max as we know it's been combined with the cross entropy loss for numerical
stability
the next step is new since.
From this point forward we'll be looking at larger and larger data sets.
There is a need to make use of the GP you we know that GP use are useful for speeding up deep learning
because while they were originally built for gaming a lot of the matrix algebra that happens in physics
engines is the same as the matrix algebra that happens in deep learning so we can make use of gaming
technology to speed up deep learning computations.
So basically if you have a GP you available it will be called CUDA colon 0.
So what we're doing here is checking if that string is in the list of available devices.
If it is we'll set that to be the device and if it's not we'll set the device to be the string CCU next.
And this is important we call model that to device.
This transfers all the parameters of our model to the GP you you can picture your RAM and your GP you
as being two different physical spaces which they are and in order to do any computation like multiplying
or adding all the numbers we want to compute on have to be on the same device.
So whenever we want to calculate the output of our model both of the models parameters and the input
data have to be on either the GP you.
Or on the main RAM but we can't have one set of numbers on one device in another set of numbers.
On the other device
next we're going to set our loss and optimizer which you've already seen.
Note that this is our new laws the cross entropy loss meant for multiple categories
next there's another new thing we're going to create generators which will allow us to loop through
each batch of data as we iterate through epoch.
These are called Data loaders in PI to search for the input arguments we specify the data arrays which
are the train data set and test data set variables we specify the batch size and we specify whether
or not to shuffle the data you'll notice that we shuffle the training data but not the test data.
This is because for the training data if we loop through each sample in the same order each time this
will introduce unwanted correlations which will decrease performance.
Think of our example of measuring the average height of everyone in the world or more practically drug
testing.
We always want our sample to be random for the test data.
There is no need to shuffle it because all we want to do is evaluate the loss and accuracy.
Next we have a little snippet of code just to test how the data loader works.
As mentioned it's a generator so we can do a full loop over it.
For this data loader I'm going to set the batch size to 1 and I'm going to print out not just the shape
of the data we get on each iteration but also the data itself.
We can see something interesting.
If we look at the data which is that the data now ranges from 0 to 1 instead of from 0 to 255.
This is kind of unintuitive at first because you might think why was it that when we printed the data
earlier it was 0 to 255 but only when we use the data loader as it becomes 0 to 1.
This seems strange because it seems like the data loader object should be generic.
It can generate data from any kind of data set that you pass in.
In fact the normalization comes from the two tensor function we passed in earlier to the M.A. function
although we didn't see it at the time because it does not get called when you just call the data attribute.
So in the next line we can see what happens if we call the transform function manually on the data.
The max is 1 as expected and so this transform function is called internally by the data loader but
it's not called when you just do train data set that data
next we're going to train our model.
You'll notice that we only need 10 epochs to train this model which is a lot less than before.
Why might that be.
If you think about it this goes back to the theory behind a batch gradient descent as you recall a back
is a representative sample of the entire dataset.
In other words doing batch screening at a center on a single batch is like doing gradient descent on
the entire dataset but much faster since we're only looking at a small subset of the data and keep in
mind that the total number of iterations is still very high because we have sixty thousand data points
to loop over.
In fact some data sets are so large that we only do a single epoch over the entire dataset.
So that's why the total number of epochs is small here.
It looks small but we're actually still doing a pretty large number of gradient descent steps in total.
Now I hope that you recognize most of the elements of this loop at this point.
As mentioned we'll be looking at more or less the same thing for each example throughout the course.
So what's different.
First we have two nested loops one over the epochs and one over the train loader.
Before we go into the inner loop we first need to create an empty list to store the train loss.
We want the loss per epoch but what we actually get is the loss per batch.
So let's just store the losses for each batch and then deal with them at the end.
Alternatively you could just plot the loss per back instead of the last pre epoch.
Next we enter the inner loop through the train loader.
The first thing we do in this loop is to transfer the data to the GP you as you recall our model parameters
are already on the GP you.
And if we want to do any computation between the model and the data both have to be on the GP you next
we're going to reshape the data to be n by B where D is 784 and is the batch size.
Note that for flexibility we can specify the first argument as minus one which tells PI talks to just
assign whatever value is appropriate for the data we're given.
This works the same in num pi.
If you want to try it out
next we have all the usual steps to perform one step of gradient descent.
At the end when we get a loss we append it to our list of losses for this particular epoch.
Once we're outside the epoch we simply take a mean of this list to be the train loss for that epoch
as noted.
This is a little misleading.
Technically it's not the loss for the epoch because we have been training the entire time we've been
looping through this epoch so the losses we've collected represent a number of different states of our
model.
Nonetheless it would be inefficient to have to loop through the data twice just to calculate the loss
so we don't really want to do that if we don't need to
Next we calculate the Test loss which goes through a similar loop.
Remember that we can't calculate the test loss all at once because it may not fit into memory.
If our dataset is too large this loop has all the same steps as the train loop except for the backward
and optimizer steps.
Finally at the end of the outer loop we store the losses we collected and print them out.
As usual we would like to see the loss per iteration which is what we have next.
The next step is to calculate the accuracy again having a data loader instead of just a plain array
makes things more difficult.
So we have to loop through the data rather than just do a single computation.
We start by initializing the counts for the number correct and the number total to zero.
Then we loop through the data loader inside the loop.
We transfer the data to the GP you reshape the inputs and then get the outputs these outputs are logics
and not probabilities but that means we can still take the max to get the prediction.
We call it torture that Max over Axis 1 which means that we take the max over the columns and this returns
both the maximum value in each row and the corresponding indices.
We only want the indices since that corresponds to the classes so the first thing returns from this
function would be the maximum value.
And the second thing returned from this function would be the index.
Next we check how many of the predictions are equal to the targets and assign that to incorrect since
this is all in PI torch land.
We have to call that item to bring it back to Python land in order to get the number of samples.
That's just the shape of the targets at the 0 with index.
Finally when we're outside the loop the accuracy is just the number correct divided by the number total.
As you've seen
next we do the same thing for the test accuracy and when we're done we print the train accuracy and
the test accuracy
as you can see we do pretty well.
Next we have some code to plot a confusion matrix.
This code is outside the scope of this course.
So you don't have to worry about understanding it just knowing what a confusion matrix is is what I
want you to focus on.
So the idea is we want to draw a table with the true labels on one axis and the predicted labels on
the other axis
so on this next block of code we have a loop similar to the above where we make predictions but now
we just store them in no higher res instead of calculating the accuracy so you see here we use concatenate
rather than summing
and at the end we call our confusion matrix function
so basically the confusion matrix tells us for each label how many predictions correspond to that label.
Hopefully we expected to find most of the entries along the diagonal where the label would be equal
to the prediction.
Of course since we don't have 100 percent accuracy there are a few entries not on the diagonal.
Importantly however it's good to try and see if we can make sense of these results.
They're very interpretable because we're working with images.
And when you have images it's very easy to simply look at them.
So where do we have the highest inaccuracy.
It seems to be where the true label is for and the predicted label is 9
this actually makes sense.
If you write down a 9 the connection points in the main lines all appear in approximately the same spot.
You can imagine that it would be easy for a human observer to make the same mistake.
We can look for more instances of a large number of predictions being incorrect such as a three being
confused with an 8 and a 2 being confused with an 8.
You should be able to recognize why these mistakes were being made.
The next thing we do is actually plot some of these misclassified samples to confirm our suspicions.
Perhaps the misclassified forces really do look like science in order to find this out.
We're going to select a randomly misclassified sample and plot the image to do this.
First we have to gather the indices of all the misclassified samples.
We can do this using the name pi where function the where function simply returns the index value where
the input array is true so we can pass in the boolean array p test not equal to y test.
Note that the where function returns the results in a tuple but we only care about the first element.
So that's why we use the square brackets 0.
Next we're going to use NPR at random not choice to select one of these random misclassified indices
and assign this variable to ie.
So let's check out the results.
So here's a 5 predicted as a 9.
That kind of makes sense because this looks like a 9
use a for predicted as a 9.
As you can see it kind of looks like a 9
here's a 2 predicted as an 8
here's a 3 predicted as a 9
here's a 5 predicted as a 3
Here's a 7 predicted as a 2.
So this one actually looks like a 2
so I think overall it makes sense what we're seeing digits that confuse the computer are the same as
digits that would confuse us.