CNNs are how machines see. A convolution slides a small filter over an image to detect patterns — learn convolution, padding, strides, and pooling from the ground up.
Why: flattening a 224×224 image into a vector for a dense layer throws away all spatial structure and needs millions of weights — a convolution instead slides a small shared filter across the image, using few weights and preserving where things are. When: use CNNs for any grid-structured data: images, spectrograms, some time series. Where: weight sharing is what makes CNNs efficient and translation-aware.
DENSE on a 224x224x3 image: flatten -> 150,528 inputs -> huge weight
matrix, and pixel position is lost.
CONVOLUTION: a small filter (e.g. 3x3) slides across the image,
detecting the same pattern anywhere. Few weights, position preserved.
Stack convolutions: early layers find edges, deeper layers find
shapes, then objects.Why: a Conv2d layer learns filters that detect features; padding controls whether the output keeps the input’s size, and stride controls how far the filter jumps (and thus how much it downsamples). When: use padding to preserve spatial size through a layer, stride > 1 to shrink it. Where: the channel dimension grows as you go deeper — more filters, more feature maps.
import torch, torch.nn as nn
x = torch.randn(1, 3, 32, 32) # batch=1, 3 channels (RGB), 32x32
conv = nn.Conv2d(
in_channels=3, out_channels=16, # learn 16 filters
kernel_size=3, stride=1, padding=1, # padding=1 keeps 32x32
)
out = conv(x)
print(out.shape) # torch.Size([1, 16, 32, 32])
# stride=2 halves the spatial size:
conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=2, padding=1)
print(conv2(out).shape) # torch.Size([1, 32, 16, 16])Why: pooling shrinks feature maps by taking the max (or average) over small windows, reducing computation and making the network robust to small shifts in position. When: insert pooling between conv blocks to progressively reduce spatial size. Where: max pooling keeps the strongest activation in each window, which tends to preserve the most salient feature.
import torch.nn as nn
pool = nn.MaxPool2d(kernel_size=2) # take the max over each 2x2 window
# turns a 32x32 feature map into 16x16, keeping the strongest signals
block = nn.Sequential(
nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(),
nn.MaxPool2d(2), # 32x32 -> 16x16
)
print(block(torch.randn(1, 3, 32, 32)).shape) # [1, 16, 16, 16]