Convolution: the operation and its math
Local dot products, shared everywhere
A 224×224 color image is 150,528 numbers; a dense layer mapping it to just 1,000 units would need 150 million weights. Convolution exploits two facts about images — nearby pixels are related, and a feature is a feature wherever it appears — to extract rich features with a few thousand parameters. It is the operation inside every CNN (5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt), the patch embedding of Vision Transformers (5.7 Vision Transformers), the 1-D WaveNet model you build in 6.8 Project: character-level language model, and the U-Net denoiser inside diffusion models (8.9 Generative models II: Diffusion and flow matching). Mastering its arithmetic (output sizes, receptive fields, parameter and FLOP counts) is what lets you read any architecture table.
You'll be able to
- Compute a 2-D cross-correlation by hand and in NumPy, including multi-channel inputs and multiple filters
- Apply the output-size formula and derive receptive-field growth for stacked layers
- Explain weight sharing, locality and translation equivariance, and quantify the parameter savings
- Derive the gradients of a convolution layer with respect to its kernel and its input
- Count parameters and FLOPs for a convolution layer
Key ideas
- Deep-learning 'convolution' is cross-correlation: a dot product between a k×k×C_in patch and a learned kernel at every position; flipping is irrelevant because kernels are learned.
- Output size = ⌊(n + 2p − k)/s⌋ + 1; 'same' padding for stride 1 is p = (k−1)/2.
- Parameters per layer = C_out(C_in·k² + 1), independent of image size; FLOPs ≈ 2·H_out·W_out·C_out·C_in·k².
Show 4 more key ideas
- Locality + weight sharing give translation equivariance and a ~1000× parameter saving over dense layers — an inductive bias ViTs must learn from data.
- Receptive field grows as r_l = r_{l−1} + (k_l − 1)·∏ strides; two 3×3 layers see 5×5 with fewer weights than one 5×5.
- Convolution is a sparse, weight-tied linear map (im2col makes it a matmul); its kernel gradient is a cross-correlation and its input gradient is a transposed convolution.
- Stride-2 convolutions and pooling trade resolution for receptive field and compute.
Suggested path through this lesson
Images are tensors
A grayscale image is a matrix of intensities; a color image is a stack of three matrices (red, green, blue). PyTorch stores a batch of images as a 4-D tensor of shape : batch, channels, height, width. Pixel values 0–255 are scaled to floats, typically normalized per channel with the ImageNet mean and standard deviation. Everything a vision model does is a sequence of operations on this tensor, and the first operation is almost always a convolution.
The operation
A kernel (or filter) is a small matrix, usually or , of learnable weights. Sliding it over the image and taking a dot product at each position produces a feature map. For a single-channel image and a kernel:
Strictly this is cross-correlation; true convolution flips the kernel, . Since the kernel is learned, a flipped kernel is just another kernel, so deep-learning libraries implement cross-correlation and call it convolution. Each output value is a dot product (1.1 Vectors, dot products and norms) between a patch of the image and the kernel: the kernel is a template, and the feature map records how strongly each location matches it.
With input channels and filters, the weight tensor has shape and each output channel sums over all input channels:
where is the stride (step between positions) and the padding (zeros added around the border). The parameter count is . A first layer with , , has parameters — independent of the image size.
Hand-designed kernels
Before CNNs, people designed kernels by hand. A box blur averages a neighborhood; a Sobel kernel estimates the horizontal gradient:
Worked example. Apply to the patch : . Intensity rises by 1 per column, and the kernel's weights sum to on the right minus left, so the response is positive and proportional to the slope. A CNN learns kernels like these in its first layer — and thousands of more useful ones.
Stride, padding and the output-size formula
For input size , kernel , padding and stride :
- LeNet-style: .
- "Same" padding keeps the size for stride 1: , so .
- ResNet stem: . ✓
Stride 2 halves resolution and quarters compute; it is the modern replacement for pooling in many architectures (5.2 CNN building blocks).
Why convolution works: locality, sharing, equivariance
- Locality. Each output looks at a neighborhood: the right prior for images, where edges and textures are local.
- Weight sharing. The same kernel is used at every position. A dense layer from a image to 64 units needs million weights; the conv above needs 1,792 — a saving, and far less overfitting (3.6 Regularization and generalization).
- Translation equivariance. Shift the input and the feature map shifts by the same amount: . A cat detector works in any corner of the image. (Pooling and a final classifier turn equivariance into approximate invariance.)
Intuition. A convolution layer is a dense layer with two constraints: most weights are forced to zero (locality) and the remaining weights are tied together (sharing). Constraints are an inductive bias — knowledge about the data baked into the architecture. Vision Transformers (5.7 Vision Transformers) remove this bias and must learn it from much more data.
Receptive field
The receptive field of a unit is the region of the input that can influence it. Stacking two layers gives each output a view with weights per channel instead of 25, plus an extra nonlinearity — VGG's key insight (5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt). In general, for layer with kernel and stride :
Three stride-1 layers: . Make the second layer stride 2: . Downsampling makes receptive fields grow fast, which is why deep nets with a few stride-2 layers can see the whole image.
Convolution as a sparse linear layer, and its gradients
Unroll each patch into a row (the im2col trick) and the convolution becomes one matrix multiplication of a patch matrix with a weight matrix. That is how GPUs run it, and it shows convolution is linear: a structured (Toeplitz-like) matrix, nothing more. Backpropagation (4.4 Backpropagation, fully derived) therefore follows the chain rule, with weight sharing adding one twist — the kernel gradient sums contributions from every position:
So the gradient with respect to the kernel is itself a cross-correlation (of the input with the output gradient), and the gradient with respect to the input is a full convolution of with the flipped kernel — exactly a transposed convolution (5.2 CNN building blocks). Autograd does this for you, but knowing it explains why conv backward costs about as much as forward.
FLOPs. Each output value needs multiply-adds, so a layer costs FLOPs. The ResNet stem ( output, 64 filters, ): MFLOPs — about 6% of a whole ResNet-50 forward pass (4.1 GFLOPs) in a single layer.
import torch, torch.nn as nn
conv = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
x = torch.randn(1, 3, 224, 224)
print(conv(x).shape) # torch.Size([1, 64, 112, 112])
print(sum(p.numel() for p in conv.parameters())) # 64*(3*49+1) = 9472
Where this shows up later
- Blocks, pooling, 1×1 and transposed convolutions — 5.2 CNN building blocks; the architectures that stack them — 5.3 Architectures: LeNet → AlexNet → VGG → Inception → ResNet → EfficientNet → ConvNeXt.
- A ViT's patch embedding is a convolution with kernel = stride = patch size — 5.7 Vision Transformers.
- 1-D causal convolutions build WaveNet in makemore Part 5 — 6.8 Project: character-level language model; the U-Net inside Stable Diffusion is convolutional — 8.9 Generative models II: Diffusion and flow matching.
- FLOP and memory accounting continues for transformers — 7.4 Inside the block: residuals, LayerNorm, FFN and embeddings, 4.12 Compute, hardware and scale basics.
Common pitfalls (4)
- Forgetting that the kernel also spans all input channels: a 3×3 kernel on an RGB image has 27 weights, not 9.
- Mixing up (N, C, H, W) with (N, H, W, C) — PyTorch is channels-first; a silent transpose error wrecks training without crashing.
- Assuming CNNs are translation invariant: individual layers are equivariant; invariance only emerges after pooling/global averaging, and is approximate.
- Padding with zeros is a modeling choice: border artifacts and 'position leakage' (the network can tell where the border is) are real effects.
Recap: all 7 key points
- Deep-learning 'convolution' is cross-correlation: a dot product between a k×k×C_in patch and a learned kernel at every position; flipping is irrelevant because kernels are learned.
- Output size = ⌊(n + 2p − k)/s⌋ + 1; 'same' padding for stride 1 is p = (k−1)/2.
- Parameters per layer = C_out(C_in·k² + 1), independent of image size; FLOPs ≈ 2·H_out·W_out·C_out·C_in·k².
- Locality + weight sharing give translation equivariance and a ~1000× parameter saving over dense layers — an inductive bias ViTs must learn from data.
- Receptive field grows as r_l = r_{l−1} + (k_l − 1)·∏ strides; two 3×3 layers see 5×5 with fewer weights than one 5×5.
- Convolution is a sparse, weight-tied linear map (im2col makes it a matmul); its kernel gradient is a cross-correlation and its input gradient is a transposed convolution.
- Stride-2 convolutions and pooling trade resolution for receptive field and compute.
Watch
The cleanest visual of sliding windows, blurring and edge kernels; also why convolution connects to polynomial multiplication and the FFT.
Read
Try it live
Interactive demo
runs in your browserExercises
- By hand: a full 3×3 convolution
Write a image of your choice and the Sobel- kernel. Compute the valid output, then the 'same' output with zero padding. Then use the output-size formula to predict the shape of a kernel with stride 2 and padding 3 on a input before checking in PyTorch. Expected: .
- NumPy: conv2d three ways
Implement
conv2d(x, w, stride, padding)for input shape and weights (a) with explicit loops, (b) with im2col + one matrix multiply. Compare both totorch.nn.functional.conv2don random inputs (max abs difference below ). Time (a) vs (b) on a input. - Receptive fields and FLOPs of a real net
For a stack of layers [ s2, maxpool s2, then four s1 convs] compute the receptive field after each layer with the recursion and the FLOPs of each conv at input with 64 channels. Expected receptive field after the stack: 43 pixels.
- Gradient check for a tiny convolution
With a input, a kernel and loss , compute via the cross-correlation formula in the notes, and verify with finite differences () — relative error should be below in float64.
Quiz
Flashcards
These cards are now in your spaced-repetition queue. Review all due cards →