Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

CNN classification

GPU is available, using GPU Tesla T4

Building a CNN

Now that we understand convolution, and why we should care about it, we are almost ready to implement a CNN. In general, a CNN architecture contains:

  • Convolution layers which execute a discrete convolution with a learnable weighting or kernel function

  • Pooling layers which perform spatial downsampling (mimicking the role of complex cells in a visual cortex)

  • Fully-connected layers which make determinations based on the high-level features at the end of the network

Dataset: MNIST

For this example, we’ll use MNIST. But since we’re using torch now, we can use the MNIST located in torchvision

Training dataset has length 48000
Training dataset has length 12000
Training dataset has length 10000
Input has shape torch.Size([1, 28, 28]) and the output is 7
<Figure size 960x720 with 10 Axes>

CNN definition

Model transforms input from size torch.Size([1, 1, 28, 28]) into torch.Size([1, 32, 7, 7])
Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (4): ReLU() (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) )
<Figure size 1500x750 with 32 Axes>
Model transforms input from size torch.Size([1, 1, 28, 28]) into torch.Size([1, 1568])
Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (4): ReLU() (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (flatten): Flatten(start_dim=1, end_dim=-1) )
Model transforms input from size torch.Size([1, 1, 28, 28]) into torch.Size([1, 10])
Sequential( (0): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (4): ReLU() (5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (flatten): Flatten(start_dim=1, end_dim=-1) (dropout): Dropout(p=0.5, inplace=False) (fc1): Linear(in_features=1568, out_features=256, bias=True) (relu): ReLU() (fc2): Linear(in_features=256, out_features=10, bias=True) )

CNN training

Loading...
Loading...
Epoch 1 | Train loss 0.258 | Train acc 0.918 | Val loss 0.083 | Val acc 0.975
Loading...
Loading...
Epoch 2 | Train loss 0.089 | Train acc 0.972 | Val loss 0.051 | Val acc 0.984
Loading...
Loading...
Epoch 3 | Train loss 0.066 | Train acc 0.978 | Val loss 0.042 | Val acc 0.987
Loading...
Loading...
Epoch 4 | Train loss 0.055 | Train acc 0.983 | Val loss 0.034 | Val acc 0.990
Loading...
Loading...
Epoch 5 | Train loss 0.048 | Train acc 0.985 | Val loss 0.033 | Val acc 0.990
Loading...
Loading...
Epoch 6 | Train loss 0.043 | Train acc 0.986 | Val loss 0.032 | Val acc 0.989
Loading...
Loading...
Epoch 7 | Train loss 0.037 | Train acc 0.988 | Val loss 0.030 | Val acc 0.990
Loading...
Loading...
Epoch 8 | Train loss 0.033 | Train acc 0.990 | Val loss 0.031 | Val acc 0.991
Loading...
Loading...
Epoch 9 | Train loss 0.031 | Train acc 0.990 | Val loss 0.033 | Val acc 0.990
Loading...
Loading...
Epoch 10 | Train loss 0.029 | Train acc 0.990 | Val loss 0.029 | Val acc 0.992
<Figure size 750x375 with 2 Axes>

Model inference

Loading...
Test accuracy: 0.992
<Figure size 1500x600 with 10 Axes>
Feature kernels have shape (32, 1, 3, 3) and values between -0.655 and 0.627
<Figure size 1500x750 with 32 Axes>

CNN classification with a more complicated dataset

Dataset: STL10

Train/val dataset has size (5000, 3, 96, 96) and targets (5000,)
Train/val dataset has 10 unique labels
Test dataset has size (8000, 3, 96, 96) and targets (8000,)
Test dataset has 10 unique labels

Another dataset: CIFAR10

Train/val dataset has size (50000, 32, 32, 3) and targets 50000
Train/val dataset has 10 unique labels
Test dataset has size (10000, 32, 32, 3) and targets 10000
Test dataset has 10 unique labels

Visualization

<Figure size 960x720 with 10 Axes>
<Figure size 960x720 with 1 Axes>

Image transforms

We don’t have that much training data, only 5,000 images. So we apply transformations to augment our dataset. This can do several things. Like SGD, it may introduce stochasticity to improve model training and avoid local minima. But it may also improve our model’s ability to generalize by making it more robust to particular transformations

Image type: <class 'torchvision.tv_tensors._image.Image'>
Image shape: torch.Size([3, 96, 96])
Image min: 0.0
Image max: 1.0
Label: 1
<Figure size 1200x600 with 8 Axes>

Create train, validation, datasets, check augmentations

Images has shape torch.Size([32, 3, 96, 96])
Labels has shape torch.Size([32])
<Figure size 1500x750 with 32 Axes>

CNN definition

CNN has 93248 trainable parameters
Model output has size: torch.Size([32, 128, 12, 12])
Sequential( (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Dropout(p=0.2, inplace=False) (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same) (5): ReLU() (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (7): Dropout(p=0.2, inplace=False) (8): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=same) (9): ReLU() (10): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (11): Dropout(p=0.2, inplace=False) )

Different “classifier heads”

CNN has 94538 trainable parameters
Model output has size: torch.Size([32, 10])
Sequential( (0): Sequential( (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Dropout(p=0.2, inplace=False) (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same) (5): ReLU() (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (7): Dropout(p=0.2, inplace=False) (8): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=same) (9): ReLU() (10): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (11): Dropout(p=0.2, inplace=False) ) (1): Sequential( (0): MaxPool2d(kernel_size=12, stride=12, padding=0, dilation=1, ceil_mode=False) (1): Flatten(start_dim=1, end_dim=-1) (2): Linear(in_features=128, out_features=10, bias=True) ) )
CNN has 94538 trainable parameters
Model output has size: torch.Size([32, 10])
Sequential( (0): Sequential( (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Dropout(p=0.2, inplace=False) (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same) (5): ReLU() (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (7): Dropout(p=0.2, inplace=False) (8): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=same) (9): ReLU() (10): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (11): Dropout(p=0.2, inplace=False) ) (1): Sequential( (0): AvgPool2d(kernel_size=12, stride=12, padding=0) (1): Flatten(start_dim=1, end_dim=-1) (2): Linear(in_features=128, out_features=10, bias=True) ) )
CNN has 2453962 trainable parameters
Model output has size: torch.Size([32, 10])
Sequential( (0): Sequential( (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=same) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (3): Dropout(p=0.2, inplace=False) (4): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=same) (5): ReLU() (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (7): Dropout(p=0.2, inplace=False) (8): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=same) (9): ReLU() (10): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (11): Dropout(p=0.2, inplace=False) ) (1): Sequential( (0): Flatten(start_dim=1, end_dim=-1) (1): Linear(in_features=18432, out_features=128, bias=True) (2): ReLU() (3): Linear(in_features=128, out_features=10, bias=True) ) )

Aside on weight sharing

The model above has fewer than 100,000 trainable parameters. This is pretty small! As a comparison, let’s look at a multi-layer perceptron which directly goes from the image to a 128-dimensional hidden layer, and from that to an output

MLP has 3540362 trainable parameters

CNN training

Loading...
Loading...
Epoch 1 | Train loss 2.089 | Train acc 0.222 | Val loss 1.818 | Val acc 0.354
Loading...
Loading...
Epoch 2 | Train loss 1.725 | Train acc 0.348 | Val loss 1.647 | Val acc 0.368
Loading...
Loading...
Epoch 3 | Train loss 1.570 | Train acc 0.417 | Val loss 1.569 | Val acc 0.423
Loading...
Loading...
Epoch 4 | Train loss 1.527 | Train acc 0.429 | Val loss 1.462 | Val acc 0.462
Loading...
Loading...
Epoch 5 | Train loss 1.426 | Train acc 0.480 | Val loss 1.442 | Val acc 0.451
Loading...
Loading...
Epoch 6 | Train loss 1.343 | Train acc 0.507 | Val loss 1.366 | Val acc 0.508
Loading...
Loading...
Epoch 7 | Train loss 1.322 | Train acc 0.508 | Val loss 1.293 | Val acc 0.531
Loading...
Loading...
Epoch 8 | Train loss 1.231 | Train acc 0.549 | Val loss 1.259 | Val acc 0.524
Loading...
Loading...
Epoch 9 | Train loss 1.198 | Train acc 0.558 | Val loss 1.243 | Val acc 0.539
Loading...
Loading...
Epoch 10 | Train loss 1.163 | Train acc 0.571 | Val loss 1.269 | Val acc 0.536
Loading...
Loading...
Epoch 11 | Train loss 1.113 | Train acc 0.592 | Val loss 1.172 | Val acc 0.589
Loading...
Loading...
Epoch 12 | Train loss 1.061 | Train acc 0.613 | Val loss 1.183 | Val acc 0.575
Loading...
Loading...
Epoch 13 | Train loss 1.042 | Train acc 0.617 | Val loss 1.207 | Val acc 0.557
Loading...
Loading...
Epoch 14 | Train loss 0.992 | Train acc 0.635 | Val loss 1.204 | Val acc 0.575
Loading...
Loading...
Epoch 15 | Train loss 1.000 | Train acc 0.633 | Val loss 1.169 | Val acc 0.572
Loading...
Loading...
Epoch 16 | Train loss 0.997 | Train acc 0.644 | Val loss 1.162 | Val acc 0.587
Loading...
Loading...
Epoch 17 | Train loss 0.941 | Train acc 0.659 | Val loss 1.197 | Val acc 0.572
Loading...
Loading...
Epoch 18 | Train loss 0.892 | Train acc 0.673 | Val loss 1.279 | Val acc 0.567
Loading...
Loading...
Epoch 19 | Train loss 0.894 | Train acc 0.676 | Val loss 1.148 | Val acc 0.589
Loading...
Loading...
Epoch 20 | Train loss 0.848 | Train acc 0.700 | Val loss 1.148 | Val acc 0.597
Loading...
Loading...
Epoch 21 | Train loss 0.828 | Train acc 0.701 | Val loss 1.136 | Val acc 0.616
Loading...
Loading...
Epoch 22 | Train loss 0.808 | Train acc 0.703 | Val loss 1.218 | Val acc 0.582
Loading...
Loading...
Epoch 23 | Train loss 0.795 | Train acc 0.712 | Val loss 1.112 | Val acc 0.585
Loading...
Loading...
Epoch 24 | Train loss 0.761 | Train acc 0.724 | Val loss 1.188 | Val acc 0.595
Loading...
Loading...
Epoch 25 | Train loss 0.746 | Train acc 0.724 | Val loss 1.164 | Val acc 0.590
<Figure size 750x375 with 2 Axes>

Model inference

Loading...
Test accuracy: 0.609
torch.Size([64, 3, 96, 96]) torch.Size([64])
<Figure size 1200x600 with 8 Axes>