PyTorch Conv2d Layer

nn.Conv2d is the core building block of image processing networks in PyTorch. It applies a set of learnable filters to an input image or feature map and produces a set of output feature maps. Understanding its parameters removes most of the confusion when debugging CNN shape errors.

Conv2d Signature

nn.Conv2d(
    in_channels,    # number of input channels
    out_channels,   # number of filters (= number of output channels)
    kernel_size,    # filter size: integer or (height, width) tuple
    stride=1,       # step size when sliding the filter
    padding=0,      # pixels added around the border before convolution
    bias=True       # include a learnable bias term
)

Channels Explained

Grayscale image:   in_channels = 1  (one intensity value per pixel)
RGB color image:   in_channels = 3  (red, green, blue per pixel)
After a Conv2d with out_channels=32: the next layer sees 32 channels

Input shape:   [batch, in_channels, height, width]
Output shape:  [batch, out_channels, new_height, new_width]

How the Filter Slides

Input feature map (5×5, 1 channel):
┌───────────────┐
│  1  2  3  4  5│
│  6  7  8  9  0│
│  1  2  3  4  5│
│  6  7  8  9  0│
│  1  2  3  4  5│
└───────────────┘

3×3 filter slides with stride=1, no padding:
  Position (0,0): covers rows 0-2, cols 0-2  → one output value
  Position (0,1): covers rows 0-2, cols 1-3  → one output value
  ...
  Output size: (5-3)/1 + 1 = 3 → output is 3×3

Output Size Formula

output_size = floor((input_size + 2×padding - kernel_size) / stride) + 1

Example:
  input_size = 28, kernel_size = 3, padding = 1, stride = 1
  output = (28 + 2×1 - 3) / 1 + 1 = 28   ← same size as input ("same" padding)

  input_size = 28, kernel_size = 3, padding = 0, stride = 1
  output = (28 - 3) / 1 + 1 = 26   ← shrinks by 2

Common Conv2d Configurations

import torch.nn as nn

# Keep spatial size the same (padding = kernel_size // 2 for odd kernels)
nn.Conv2d(3, 64, kernel_size=3, padding=1)    # 3→64 ch, size unchanged

# Halve spatial size
nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1)

# Large receptive field
nn.Conv2d(3, 32, kernel_size=7, stride=2, padding=3)   # used in ResNet first layer

# 1×1 convolution — mix channels, no spatial processing
nn.Conv2d(256, 64, kernel_size=1)

Multiple Filters — Multiple Output Channels

When out_channels=32, PyTorch learns 32 separate filters. Each filter slides across the entire input and produces one output feature map. The output has 32 channels — one per filter.

in_channels=3 (RGB), out_channels=32, kernel_size=3

Each of the 32 filters has shape:  [3, 3, 3]
  ↑ depth (matches in_channels=3)
  ↑ height 3
  ↑ width 3

Total parameters: 32 × (3×3×3) + 32 = 896 weights

Counting Conv2d Parameters

layer = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, bias=True)

# Weights: out_channels × in_channels × kH × kW
# Bias:    out_channels
total = 32 * 3 * 3 * 3 + 32   # = 896
print(sum(p.numel() for p in layer.parameters()))   # 896

Padding Modes

padding=0 (default):
  No border added. Output shrinks.

padding=1 (for 3×3 kernel):
  One row/column of zeros added on all sides.
  Output same size as input.

padding='same' (PyTorch 1.9+, stride=1 only):
  Automatically computes padding to keep spatial size unchanged.

padding_mode='reflect':
  Pads with reflected values instead of zeros.
  Reduces border artifacts in image generation tasks.

Stride — Controlling Step Size

Stride controls how many pixels the filter moves each step. Stride 1 means move one pixel at a time. Stride 2 means skip every other position — halving the output size. Using stride=2 instead of MaxPool2d is common in modern architectures.

import torch
import torch.nn as nn

x = torch.rand(1, 3, 32, 32)   # 1 image, 3 channels, 32×32

conv_s1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1)
conv_s2 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1)

print(conv_s1(x).shape)   # torch.Size([1, 16, 32, 32])  ← same size
print(conv_s2(x).shape)   # torch.Size([1, 16, 16, 16])  ← halved

Dilated Convolutions

Dilated convolutions insert gaps between filter elements. A 3×3 filter with dilation=2 covers the same area as a 5×5 filter but uses only 9 weights. This expands the receptive field without increasing parameters.

nn.Conv2d(32, 32, kernel_size=3, padding=2, dilation=2)

Full Conv Block Example

conv_block = nn.Sequential(
    nn.Conv2d(3, 32, kernel_size=3, padding=1),    # 3→32 ch, same size
    nn.BatchNorm2d(32),
    nn.ReLU(),
    nn.Conv2d(32, 32, kernel_size=3, padding=1),   # 32→32 ch, same size
    nn.BatchNorm2d(32),
    nn.ReLU(),
    nn.MaxPool2d(2, 2)                             # halve spatial dims
)

Summary

nn.Conv2d takes in_channels, out_channels, and kernel_size as its three key parameters. The filter slides across the input with a given stride. Padding controls whether the output is smaller, equal, or (with asymmetric padding) larger than the input. The output size formula is (input + 2×padding - kernel) / stride + 1. Use padding=kernel_size//2 to keep the spatial size the same with stride=1. Multiple out_channels mean multiple filters — each learning to detect a different pattern. Every CNN layer builds on the feature maps produced by the layer before it.

Leave a Comment

Your email address will not be published. Required fields are marked *