PyTorch Basics¶

I'll assume that everyone is familiar with Python. Training neural nets in bare Python is somewhat painful, but fortunately there are several well-established libraries which can help. I like PyTorch, which is built upon an earlier library called Torch. (There are many others, including TensorFlow and Jax, both of which I would encourage you to explore at your leisure.)

In [1]:
import numpy as np
import torch
import torchvision

Torch believes that everything is a tensor.

In [2]:
a = np.random.rand(2,3)
b = torch.from_numpy(a)

print(a)
print(b)
[[0.5656557  0.27607714 0.45466866]
 [0.50539309 0.87329605 0.83471437]]
tensor([[0.5657, 0.2761, 0.4547],
        [0.5054, 0.8733, 0.8347]], dtype=torch.float64)

The main intuition is that tensors allow for intuitive and efficient matrix multiplication across different indexing dimensions. Soon we will see that training neural nets basically involves forward and backward passes, both of which are essentially matrix multiplies.

The other thing about Torch variables is that they (natively) can be differentiated. (Again, we will see why this is important when we learn about backpropagation in deep learning.)

In [3]:
A = torch.rand(2,2)
b = torch.rand(2,1)
x = torch.rand(2,1,requires_grad=True)

y = torch.matmul(A,x) + b

print(y)
tensor([[1.1021],
        [1.4504]], grad_fn=<AddBackward0>)
In [4]:
z = y.sum()
print(z)
tensor(2.5525, grad_fn=<SumBackward0>)

Here, z is a function of the input x. Let us now compute the derivative of z with respect to x using backpropagation.

In [5]:
z.backward()
print(x.grad)
print(x)
tensor([[1.2117],
        [1.4395]])
tensor([[0.1127],
        [0.7029]], requires_grad=True)

Training simple models¶

OK, enough background. Let us now train a simple model. We will train a logistic classifier (equivalent to a single-layer neural network) on a popular image dataset called Fashion-MNIST. (TorchVision has several other image datasets which we can directly load as variables, feel free to train your own models on them).

In [6]:
trainingdata = torchvision.datasets.FashionMNIST('./FashionMNIST/',train=True,download=True,transform=torchvision.transforms.ToTensor())
testdata = torchvision.datasets.FashionMNIST('./FashionMNIST/',train=False,download=True,transform=torchvision.transforms.ToTensor())
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to ./FashionMNIST/FashionMNIST/raw/train-images-idx3-ubyte.gz
100%|██████████████████████████| 26421880/26421880 [00:04<00:00, 5676746.07it/s]
Extracting ./FashionMNIST/FashionMNIST/raw/train-images-idx3-ubyte.gz to ./FashionMNIST/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz to ./FashionMNIST/FashionMNIST/raw/train-labels-idx1-ubyte.gz
100%|████████████████████████████████| 29515/29515 [00:00<00:00, 1842706.76it/s]
Extracting ./FashionMNIST/FashionMNIST/raw/train-labels-idx1-ubyte.gz to ./FashionMNIST/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz to ./FashionMNIST/FashionMNIST/raw/t10k-images-idx3-ubyte.gz
100%|████████████████████████████| 4422102/4422102 [00:00<00:00, 5298891.24it/s]
Extracting ./FashionMNIST/FashionMNIST/raw/t10k-images-idx3-ubyte.gz to ./FashionMNIST/FashionMNIST/raw

Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to ./FashionMNIST/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz
100%|█████████████████████████████████| 5148/5148 [00:00<00:00, 24206588.56it/s]
Extracting ./FashionMNIST/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to ./FashionMNIST/FashionMNIST/raw


Let's quickly check that everything has been downloaded.

In [7]:
print(len(trainingdata))
print(len(testdata))
60000
10000

Some of you might know that this is the same size as the (very commonly used) MNIST dataset. Let's plot some images.

In [8]:
image, label = trainingdata[0]
print(image.shape, label)
torch.Size([1, 28, 28]) 9

We cannot directly plot the 'image' object since it is a Torch tensor, so let's convert it back into a numpy array before displaying it. We will use matplotlib to show images.

In [9]:
import matplotlib.pyplot as plt
%matplotlib inline
plt.imshow(image.squeeze().numpy())
plt.show()
No description has been provided for this image

OK, that kinda sorta looks like a shoe? FashionMNIST is basically a bunch of black-white thumbnail images of different pieces of clothing. Let's try and plot a few of them. To step through the dataset, it will be helpful to construct a data loader object; we will use this while training our model below as well.

In [10]:
trainDataLoader = torch.utils.data.DataLoader(trainingdata,batch_size=64,shuffle=True)
testDataLoader = torch.utils.data.DataLoader(testdata,batch_size=64,shuffle=False)

Let's now use the DataLoader object to spit out a few images from the dataset.

In [19]:
images, labels = next(iter(trainDataLoader))

plt.figure(figsize=(10,4))
for index in np.arange(5):
    plt.subplot(1, 5, index + 1)
    # Assuming the images are in the format [C, H, W] and are grayscale
    # Adjust the image for visualization (transpose axes from [C, H, W] to [H, W, C])
    # For grayscale images, we also need to remove the channel dimension using squeeze()
    image = images[index].squeeze().numpy() # Assuming the images are tensors
    plt.imshow(image, cmap='gray')
    plt.axis('off')  # Optionally remove axes
plt.show()
No description has been provided for this image

OK! Time to set up our model.

In [20]:
class LinearReg(torch.nn.Module):
  def __init__(self):
    super(LinearReg, self).__init__()
    self.linear = torch.nn.Linear(28*28,10)

  def forward(self, x):
    x = x.view(-1,28*28)
    transformed_x = self.linear(x)
    return transformed_x

#net = LinearReg().cuda()
net = LinearReg()

Loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)

Cool! Everything is set up. Let's now train our network.

In [23]:
train_loss_history = []
test_loss_history = []

for epoch in range(20):
  train_loss = 0.0
  test_loss = 0.0
  for i, data in enumerate(trainDataLoader):
    images, labels = data
    images = images #.cuda()
    labels = labels #.cuda()
    optimizer.zero_grad()
    predicted_output = net(images)
    fit = Loss(predicted_output,labels)
    fit.backward()
    optimizer.step()
    train_loss += fit.item()
  for i, data in enumerate(testDataLoader):
    with torch.no_grad():
      images, labels = data
      images = images#.cuda()
      labels = labels#.cuda()
      predicted_output = net(images)
      fit = Loss(predicted_output,labels)
      test_loss += fit.item()
  train_loss = train_loss/len(trainDataLoader)
  test_loss = test_loss/len(testDataLoader)
  train_loss_history.append(train_loss)
  test_loss_history.append(test_loss)
  print('Epoch %s, Train loss %s, Test loss %s'%(epoch, train_loss, test_loss))
Epoch 0, Train loss 0.6664804585897592, Test loss 0.6461255649092851
Epoch 1, Train loss 0.6027035086965764, Test loss 0.6020751965653365
Epoch 2, Train loss 0.5679286457836501, Test loss 0.5770273548402604
Epoch 3, Train loss 0.5455557886979727, Test loss 0.5585913810001057
Epoch 4, Train loss 0.5292342450064637, Test loss 0.545891882128017
Epoch 5, Train loss 0.5165409704706053, Test loss 0.5348615168006556
Epoch 6, Train loss 0.506088534477296, Test loss 0.5280016594252009
Epoch 7, Train loss 0.49766681459285556, Test loss 0.5239671259928661
Epoch 8, Train loss 0.49072493615943485, Test loss 0.5145675993649064
Epoch 9, Train loss 0.4845343728277729, Test loss 0.5095292973290583
Epoch 10, Train loss 0.4793138336588833, Test loss 0.5043158817822766
Epoch 11, Train loss 0.4742559713087102, Test loss 0.5014074294810082
Epoch 12, Train loss 0.4701572920976163, Test loss 0.4970778027537522
Epoch 13, Train loss 0.46619399743420736, Test loss 0.4978971555354489
Epoch 14, Train loss 0.46299884320576307, Test loss 0.49122682213783264
Epoch 15, Train loss 0.45990598938866717, Test loss 0.4904827428091863
Epoch 16, Train loss 0.45701435573700905, Test loss 0.4862680175122182
Epoch 17, Train loss 0.45433947061107105, Test loss 0.4846375390982172
Epoch 18, Train loss 0.45184447986484844, Test loss 0.484140664718713
Epoch 19, Train loss 0.4495418051730341, Test loss 0.4815634836436837

Let's see how we did! We have tracked the losses so let's plot it.

In [24]:
plt.plot(range(20),train_loss_history,'-',linewidth=3,label='Train error')
plt.plot(range(20),test_loss_history,'-',linewidth=3,label='Test error')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.grid(True)
plt.legend()
Out[24]:
<matplotlib.legend.Legend at 0x7f16dd79f130>
No description has been provided for this image

Not too bad (although more epochs would have helped, I suppose). Let's evaluate our final model on a few more images to wrap things up.

In [25]:
predicted_output = net(images)
print(torch.max(predicted_output, 1))
fit = Loss(predicted_output, labels)
print(labels)
print(fit)
torch.return_types.max(
values=tensor([ 6.2214,  3.0903,  9.0717,  7.8366,  6.7475,  6.2147, 10.1018,  4.5116,
         7.1518, 11.8602, 10.5575, 10.6477,  6.8496,  4.6596,  9.4688,  4.5188],
       grad_fn=<MaxBackward0>),
indices=tensor([3, 1, 7, 5, 8, 2, 5, 6, 8, 9, 1, 9, 1, 8, 1, 5]))
tensor([3, 2, 7, 5, 8, 4, 5, 6, 8, 9, 1, 9, 1, 8, 1, 5])
tensor(0.3164, grad_fn=<NllLossBackward0>)

Cool, looks like 2 images in this batch are being misclassified; can you (as a human) guess what those are?

In [26]:
plt.figure(figsize=(10,10))
for index in np.arange(0,16):
  plt.subplot(4,4,index+1)
  plt.imshow(images[index].cpu().squeeze().numpy(),cmap=plt.cm.gray)
No description has been provided for this image
In [ ]: