Skip to content

PyTorch - Introduction to Convents

PyTorch - Introduction to Convolutional Neural Networks (ConvNets)

Section titled “PyTorch - Introduction to Convolutional Neural Networks (ConvNets)”

Convolutional Neural Networks (ConvNets or CNNs) are a class of deep neural networks most commonly applied to analyzing visual imagery. They are inspired by the organization of the animal visual cortex and are designed to automatically and adaptively learn spatial hierarchies of features from input images. A typical ConvNet architecture comprises several key types of layers:

  • Conv2D (Convolutional Layer): The core building block. It applies a set of learnable filters (kernels) to the input image. Each filter slides across the width and height of the input, computing dot products between the filter entries and the input at any position, producing a 2D activation map of that filter. This process helps detect features like edges, corners, and textures.
  • Activation Function (e.g., ReLU): Applied element-wise after a convolutional layer (and linear layers) to introduce non-linearity into the model, allowing it to learn more complex patterns. ReLU (Rectified Linear Unit) is a popular choice, outputting the input directly if positive, otherwise outputting zero.
  • MaxPool2D (Max Pooling Layer): Reduces the spatial dimensions (width and height) of the input volume. It works by taking the maximum value over a window (e.g., 2x2) for each feature map, effectively downsampling it. This helps to make the representation more robust to small translations and reduces computational complexity.
  • Flatten Layer: Converts the multi-dimensional output from convolutional/pooling layers into a one-dimensional vector. This is necessary before feeding the data into fully connected (Linear) layers.
  • Linear Layer (Fully Connected Layer): A standard neural network layer where each input neuron is connected to every output neuron. In ConvNets, these are typically found at the end of the network to perform classification based on the learned features.

Let’s define a simple ConvNet for image classification using torch.nn.Module:

import torch
import torch.nn as nn
import torch.nn.functional as F # Contains activation functions and loss functions
class SimpleConvNet(nn.Module):
def __init__(self, num_classes=10): # Assuming 10 classes for output (e.g., MNIST digits)
super(SimpleConvNet, self).__init__()
# Input image: (Batch_size, 1, 28, 28) for MNIST (1 color channel, 28x28 pixels)
# Convolutional Layer 1
# Input channels = 1, Output channels = 16, Kernel size = 3, Stride = 1, Padding = 1
self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)
# Output shape: (Batch_size, 16, 28, 28)
# After ReLU: same shape
# Max Pooling Layer 1
# Kernel size = 2, Stride = 2
self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
# Output shape: (Batch_size, 16, 14, 14)
# Convolutional Layer 2
self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
# Output shape: (Batch_size, 32, 14, 14)
# After ReLU: same shape
# Max Pooling Layer 2
self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
# Output shape: (Batch_size, 32, 7, 7)
# Flatten the output for the fully connected layer
# The input features to fc1 will be 32 * 7 * 7
self.flatten = nn.Flatten()
# Fully Connected Layer
self.fc1 = nn.Linear(32 * 7 * 7, num_classes)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = F.relu(x)
x = self.pool2(x)
x = self.flatten(x) # Flatten the tensor
# Or manually: x = x.view(-1, 32 * 7 * 7)
x = self.fc1(x)
# For classification with CrossEntropyLoss, raw logits are usually returned.
# Softmax is often included in the loss function itself.
return x
# Example instantiation
# model = SimpleConvNet(num_classes=10)

Training a ConvNet involves iterating over a dataset, performing forward and backward passes, and updating model weights. Below is a conceptual training/validation function. In a real scenario, you would also need to define a DataLoader, an optimizer (e.g., torch.optim.Adam), and a criterion (loss function, e.g., nn.CrossEntropyLoss).

def train_model_epoch(model, data_loader, criterion, optimizer, device, phase='training'):
running_loss = 0.0
running_corrects = 0
if phase == 'training':
model.train() # Set model to training mode
else: # 'validation' or 'testing'
model.eval() # Set model to evaluate mode
for inputs, labels in data_loader:
inputs = inputs.to(device) # Move data to the device (CPU or GPU)
labels = labels.to(device)
# Zero the parameter gradients (only in training phase)
if phase == 'training':
optimizer.zero_grad()
# Forward pass
# Track history only if in training phase
with torch.set_grad_enabled(phase == 'training'):
outputs = model(inputs)
loss = criterion(outputs, labels)
_, preds = torch.max(outputs, 1) # Get the index of the max log-probability (predicted class)
# Backward pass + optimize only if in training phase
if phase == 'training':
loss.backward() # Compute gradients
optimizer.step() # Update weights
# Statistics
running_loss += loss.item() * inputs.size(0) # loss.item() gives scalar loss, inputs.size(0) is batch size
running_corrects += torch.sum(preds == labels.data)
epoch_loss = running_loss / len(data_loader.dataset)
epoch_acc = running_corrects.double() / len(data_loader.dataset)
print(f'{phase.capitalize()} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
return epoch_loss, epoch_acc
# To use this function, you would typically loop through epochs:
# num_epochs = 25
# for epoch in range(num_epochs):
# print(f'Epoch {epoch}/{num_epochs-1}')
# print('-' * 10)
# train_loss, train_acc = train_model_epoch(model, train_loader, criterion, optimizer, device, phase='training')
# val_loss, val_acc = train_model_epoch(model, val_loader, criterion, optimizer, device, phase='validation')

This train_model_epoch function handles both training and validation phases. Key distinctions:

  • model.train() vs model.eval(): These set the model to training or evaluation mode. This is important for layers like Dropout and BatchNorm, which behave differently during training (e.g., applying dropout, updating running statistics) versus inference (e.g., disabling dropout, using fixed statistics).
  • Gradient Calculation: Gradients are computed (loss.backward()) and weights updated (optimizer.step()) only during the training phase. torch.set_grad_enabled(False) or with torch.no_grad(): is used during validation/testing to disable gradient calculations, saving memory and computation.

For a complete example, you would need to set up your data (e.g., using torchvision.datasets and DataLoader), instantiate your model, optimizer, and loss function, and then call train_model_epoch within an epoch loop.