Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 87

Building and Training a Simple CNN for Image Classification

Recommended resources

Hello! Welcome to your next lesson on deep learning with PyTorch.

In our last session, we explored the theory behind Convolutional Neural Networks (CNNs). You learned about the key building blocks—convolutional layers, pooling layers, and fully connected layers—and understood why this architecture is so powerful for image-related tasks.

Today, we're moving from theory to practice. Our goal is to take that theoretical blueprint and build a functioning CNN model from scratch. You will write the code to define a CNN, load the famous MNIST dataset of handwritten digits, and then use the training loop structure you've seen before to train your model and evaluate its performance. This hands-on experience is a crucial step in mastering deep learning.

By the end of this lesson, you will have successfully built and trained a simple but effective CNN for image classification, achieving a high accuracy on a real-world dataset.

1. The Five Main Steps

Building and training a model in PyTorch generally follows a consistent, five-step process. We'll walk through each one:

  1. Load and Prepare Data: We need to get our images and labels, convert them into tensors, and group them into batches.
  2. Define the Model: We'll create a Python class for our CNN architecture, defining the layers in the __init__ method.
  3. Define the Forward Pass: Inside our model class, we'll specify how data flows through the layers in the forward method.
  4. Set up the Training Components: We'll choose a loss function and an optimizer to guide the training process.
  5. Build the Training and Evaluation Loop: We'll write the code to repeatedly feed data to the model, calculate loss, backpropagate, update weights, and check performance.

Let's get started.


2. Step 1: Loading the MNIST Dataset

First, we need our data. PyTorch's torchvision library makes it incredibly easy to download standard datasets like MNIST. We'll also use DataLoader to efficiently manage the data by creating shuffled batches for training.

PyTorch Project: Handwritten Digit Recognition

To begin, let's watch this section from the video 'PyTorch Project: Handwritten Digit Recognition' by NeuralNine. It clearly demonstrates how to use torchvision to download the MNIST dataset and prepare it for training with DataLoader.

Watch from 01:45 to 06:32. Focus on these key PyTorch components and what they do: torchvision.datasets.MNIST: Downloads the dataset. transforms.ToTensor(): Converts the image data into a PyTorch tensor. torch.utils.data.DataLoader: Wraps the dataset to provide batches of data and shuffle them.

Here is a summary of the code structure for loading the data:

import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# Define a transform to convert images to tensors
transform = transforms.ToTensor()

# Download and load the training data
train_data = datasets.MNIST(root='data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_data, batch_size=100, shuffle=True)

# Download and load the test data
test_data = datasets.MNIST(root='data', train=False, download=True, transform=transform)
test_loader = DataLoader(test_data, batch_size=100, shuffle=False)

The train_loader and test_loader are now iterators that will provide us with batches of images and their corresponding labels.


3. Steps 2 & 3: Defining the CNN Model and Forward Pass

This is where we build the heart of our project. We will create a CNN class that inherits from PyTorch's nn.Module.

  • In the __init__ method, we define the layers we'll use: nn.Conv2d for convolutions, nn.MaxPool2d for pooling, and nn.Linear for the final classification layers.
  • In the forward method, we define the computational graph—how an input x flows through these layers and activation functions (F.relu) to produce an output.

A critical step is calculating the input size for the first fully connected (nn.Linear) layer. This depends on the output dimensions of the final pooling layer. For example, if the last pooling layer outputs a feature map of size 16 x 4 x 4, we flatten it into a vector of size 16 * 4 * 4 = 256.

PyTorch Project: Handwritten Digit Recognition

Now, let's watch how to translate the CNN architecture diagram into PyTorch code. This part of the NeuralNine video walks through creating the CNN class, layer by layer.

Watch from 06:32 to 13:28. Pay close attention to: The parameters of nn.Conv2d(in_channels, out_channels, kernel_size). The pattern of Convolution -> ReLU -> Pooling. How the feature maps are flattened using .view() before being passed to the fully connected layers. The use of F.log_softmax or F.softmax in the final layer to get class probabilities.

The resulting model class will look something like this. Notice how the forward method is a recipe for passing data through the layers defined in __init__.

import torch.nn as nn
import torch.nn.functional as F

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # Input for MNIST is 1 channel (grayscale), output is 10 feature maps
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=10, kernel_size=5)
        # Second conv layer takes 10 channels (from conv1), outputs 20
        self.conv2 = nn.Conv2d(in_channels=10, out_channels=20, kernel_size=5)
        # Max pooling layer
        self.pool = nn.MaxPool2d(kernel_size=2)
        # Dropout layer for regularization
        self.conv2_drop = nn.Dropout2d()
        # Fully connected layers
        # The input features (320) is calculated from the output of the conv/pool layers
        # (20 channels * 4x4 image size)
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10) # 10 output classes for digits 0-9

    def forward(self, x):
        # First conv layer -> relu -> pool
        x = self.pool(F.relu(self.conv1(x)))
        # Second conv layer -> dropout -> relu -> pool
        x = self.pool(F.relu(self.conv2_drop(self.conv2(x))))
        # Flatten the feature maps to a 1D vector
        x = x.view(-1, 320)
        # Pass through fully connected layers
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        # Use log_softmax for the final output, which works well with NLLLoss
        return F.log_softmax(x, dim=1)
Test your understanding!

Suppose you modify the CNN. After the second pooling layer, the output feature map has a shape of [batch_size, 32, 7, 7]. What value should you use for in_features in the first nn.Linear layer (self.fc1)?

Show answer

You would need to flatten the 32 x 7 x 7 tensor. So, the in_features would be 32 * 7 * 7 = 1568. The linear layer would be defined as self.fc1 = nn.Linear(1568, ...).


4. Step 4: Setting Up the Training Components

With our model and data ready, we need to define our loss function and optimizer.

  • Loss Function: Since this is a multi-class classification problem, nn.CrossEntropyLoss is a standard choice. If your model's last layer is log_softmax (as in the video example), you should use nn.NLLLoss (Negative Log Likelihood Loss).
  • Optimizer: The Adam optimizer is a robust and popular choice that often works well with little hyperparameter tuning.

This setup is very similar to what you did for the feedforward network.

PyTorch Project: Handwritten Digit Recognition

This brief segment shows how to instantiate the model, move it to a GPU if available, define the optimizer, and select the loss function.

Watch from 13:28 to 15:30. Note the lines where the model, optimizer (optim.Adam), and loss function (nn.CrossEntropyLoss) are created.


5. Step 5: Building the Training and Evaluation Loop

Now we orchestrate the entire process. We will create a train function and a test function.

The training loop will:

  1. Set the model to training mode: model.train().
  2. Iterate through the train_loader.
  3. For each batch, perform the forward pass, calculate the loss, perform backpropagation (loss.backward()), and update the weights (optimizer.step()).

The evaluation loop will:

  1. Set the model to evaluation mode: model.eval().
  2. Disable gradient calculations with with torch.no_grad():.
  3. Iterate through the test_loader.
  4. Calculate the total loss and the number of correct predictions to determine the model's accuracy.

PyTorch Project: Handwritten Digit Recognition

Let's see the full training and testing loops implemented in PyTorch. These functions encapsulate the logic we just discussed.

Watch from 15:30 to 27:25. In the training section, focus on the sequence: optimizer.zero_grad(), model(data), loss_function(...), loss.backward(), optimizer.step(). In the testing section, observe how model.eval() and torch.no_grad() are used, and how accuracy is computed by comparing model predictions (output.argmax(...)) with the true targets.

Finally, we put it all together by calling these functions inside a loop that runs for a specified number of epochs.

PyTorch Project: Handwritten Digit Recognition

This last part shows how to call the train and test functions for a set number of epochs and watch the model's accuracy improve.

Watch from 28:17 to 29:54 to see the final training run.

You'll see the test accuracy climb with each epoch, likely reaching over 98%, which is a great result and significantly better than what a simple FNN could achieve with the same amount of effort.

For reference, the articles from DataCamp and Nextjournal provide excellent text-based walkthroughs of this entire process. They are great resources to review alongside the video.


Conclusion

Congratulations! You have just walked through the complete process of building and training a Convolutional Neural Network in PyTorch. This is a milestone in your deep learning journey.

Key Takeaways:

  • Model Definition: You now know how to define a custom CNN architecture by creating a class that inherits from nn.Module, specifying layers like nn.Conv2d and nn.MaxPool2d in __init__.
  • Forward Pass: You learned to define the data flow through your network in the forward method, including applying activations and flattening feature maps.
  • Training and Evaluation: You have seen how to plug your new CNN model into a standard PyTorch training loop to train its parameters and then evaluate its performance on unseen test data.
  • Putting it all Together: You understand the end-to-end workflow: loading data, defining a model, setting up the optimizer/loss, and executing the training and evaluation process.

Preview of the Next Lesson:
So far, we've focused on data that is spatial, like images. However, many important problems in AI involve data that is sequential, where order matters, such as text, speech, or time-series data. CNNs are not naturally suited for this.

In our next lesson, we will explore Recurrent Neural Networks (RNNs) and their powerful variant, Long Short-Term Memory (LSTM) networks. You will learn how these architectures are designed to handle sequential data by maintaining an internal "memory" of past information.