A convolutional layer is a dense layer with those two things imposed on it:
locality and weight sharing. Nothing is added — capacity is removed, on purpose.
An architecture is a prior written as a constraint on the weights.
6 inputs → 4 outputs (1-D, kernel 3). Each cell is a weight; the colour says which ones are the same number.
stride 1, no padding
PyTorch computes a cross-correlation (it does not rotate the kernel). For the network it does not matter: the kernel is learned. For checking derivatives by hand it does — it comes back in the backward pass.
Almost every CNN bug is a shape bug. When PyTorch complains at the first Linear, print the shape layer by layer — do not guess the number.
Input (1, 1, 28, 28) = (images, channels, height, width): one grayscale 28×28 image. Fill it in before the next slide.
| layer | output |
|---|---|
Conv2d(1, 8, 5) |
? |
MaxPool2d(2) |
? |
Conv2d(8, 16, 3, padding=1) |
? |
MaxPool2d(2) |
? |
Conv2d(16, 32, 3, stride=2) |
? |
Flatten() |
? |
Conv2d(channels in, number of filters, kernel side)
MaxPool2d(2): 2×2 window, step 2
Output side:
Answer as channels × height × width.
| layer | output | where it comes from |
|---|---|---|
Conv2d(1, 8, 5) |
8 × 24 × 24 |
8 filters · (28 + 0 − 5)/1 + 1 = 24 |
MaxPool2d(2) |
8 × 12 × 12 |
same channels · (24 − 2)/2 + 1 = 12 |
Conv2d(8, 16, 3, padding=1) |
16 × 12 × 12 |
16 filters · (12 + 2 − 3)/1 + 1 = 12 |
MaxPool2d(2) |
16 × 6 × 6 |
same channels · (12 − 2)/2 + 1 = 6 |
Conv2d(16, 32, 3, stride=2) |
32 × 2 × 2 |
32 filters · ⌊(6 + 0 − 3)/2⌋ + 1 = 2 |
Flatten() |
128 |
32 · 2 · 2 → the next one is Linear(128, …) |
Conv2d(3, 64, 3) on a 32 × 32 image. How many parameters?
Sharing weights gives an exact property: shift the input and the map shifts with it.
That is translation equivariance — exactly right for a detector.
A classifier wants "ankle boot" wherever the boot is. Equivariance only becomes invariance when:
| mechanism | what it does |
|---|---|
| pooling / strided conv | discards position within the window — a small, local invariance |
| global average pooling at the end | collapses the whole |
| data augmentation | buys, with examples, invariance to what the conv does not cover |
The prior covers translation and nothing else. Rotation, scale, viewpoint: bought with augmentation and data — in a CNN exactly as in a Transformer.
Flatten collapses nothing: it keeps each position in its own coordinate of the vector.In the lab: an MLP, a CNN + Flatten and a CNN + GAP trained on Fashion-MNIST. Shift the test set by 4 px. Which one drops least? And rotated by 90°?
Three training epochs on Fashion-MNIST, Adam, lr = 1e-3. Test accuracy:
| network | parameters | in place | shifted 4 px | shifted 8 px | rotated 90° |
|---|---|---|---|---|---|
MLP 784 → 256 → 128 → 10 |
235,146 | 0.86 | 0.25 | 0.18 | 0.05 |
| CNN + Flatten | 20,586 | 0.90 | 0.33 | 0.19 | 0.06 |
| CNN + GAP | 24,170 | 0.85 | 0.81 | 0.65 | 0.07 |
| CNN + Flatten, augmentation ±4 px | 20,586 | 0.85 | 0.80 | 0.26 | — |
A deep unit does not see the image: it sees whatever fed it, recursively.
Self-attention reaches every position at layer 1, at an O(n²) cost. The whole CNN × Transformer trade-off is in that sentence.
Which one sees more? Which one costs less?
| gradient | what it is | implementation |
|---|---|---|
| sum of |
a reduction | |
| cross-correlation of the input with |
a convolution | |
| full cross-correlation of |
a convolution (the "transposed" one) |
With stride s > 1: insert s − 1 zeros between the entries of ∂L/∂Y and apply the same recipe.
ConvTranspose2d: the upsampling of decoders and GANs.The fix everybody uses:
nn.Upsample + Conv2d — never a bare transposed convolution.
Odena, Dumoulin & Olah (2016), Deconvolution and Checkerboard Artifacts, Distill.
No weights. It summarizes each window into one number — and the choice of summary decides who receives gradient.
6 × 6 input, max 2 × 2, stride 2, gradient 1 at every output. How many of the 36 cells receive anything?
Flatten unrolls
Linear(25088, 4096)GAP averages each channel over
Linear(C, classes).| layer | output | parameters | receptive field |
|---|---|---|---|
| input | 1 × 28 × 28 | — | 1 |
Conv2d(1, 16, 3, padding=1) + BatchNorm + ReLU |
16 × 28 × 28 | 192 | 3 |
MaxPool2d(2) |
16 × 14 × 14 | 0 | 4 |
Conv2d(16, 32, 3, padding=1) + BatchNorm + ReLU |
32 × 14 × 14 | 4,704 | 8 |
MaxPool2d(2) |
32 × 7 × 7 | 0 | 10 |
Conv2d(32, 64, 3, padding=1) + BatchNorm + ReLU |
64 × 7 × 7 | 18,624 | 18 |
AdaptiveAvgPool2d(1) + Flatten |
64 | 0 | the whole image |
Linear(64, 10) |
10 | 650 | — |
| total | 24,170 |
Resolution goes down, channels go up · the parameters live at the end · an MLP of similar accuracy has 235,146.
1×1 — a dense layer per position: mixes channels, ignores neighbours. It is how every architecture changes channel count cheaply.
Depthwise separable = one
64 → 128, 3×3 |
parameters |
|---|---|
Conv2d(64, 128, 3) |
73,856 |
depthwise Conv2d(64, 64, 3, groups=64) |
640 |
pointwise Conv2d(64, 128, 1) |
8,320 |
| separable | 8,960 (12%) |
Prediction: 1/128 + 1/9 ≈ 0.119. Every model that runs on a phone (MobileNet, EfficientNet) uses it.
2020–2021: "attention beat convolution". Did it?
Much of the reported ViT advantage was the training recipe, not the architecture. The two families converged on the same block, from opposite directions.
Convolution
a strong, cheap, fixed prior
wins when data is scarce, latency matters or the structure is genuinely local
Transformer
a weak, expensive, learnable prior
wins when you can pay for enough data to learn a better structure than the one you would have imposed
That trade-off is the axis the next four classes move along: attention, Transformers, ViT and transfer learning.
Colab notebook, ~2 h, Fashion-MNIST. Each section ends in a checkpoint with a check value.
Linear errorOutline: (1) why restrict a dense layer; (2) shape and parameters; (3) equivariance and receptive field; (4) the way back: convolution and pooling; (5) the architecture that survived and where convolutions live in 2026. Closes with the Colab lab.
Let the class guess before revealing. The second bullet matters more than the count: the dense layer does not know that an edge is an edge anywhere.
Row i = output i, column j = input j. Dense: everything connected to everything. Locality: only the diagonal band is left. Sharing: the band uses the same three numbers in every row — a Toeplitz matrix. Multiplying by it is exactly sliding the kernel.
Press play with "vertical edge". Then set stride to 2 and padding to same, and ask for the formula before looking at the line below. The dense/conv ratio is the argument of the class.
In the lab (section 2) the same exercise comes with the Linear(256, 10) error.
Both CNNs have BatchNorm: the only difference between them is the head. Notebook, section 7.
Ask for predictions: 5 layers 3x3 stride 1 → 11. Stride 2 → ? (63). Dilation 2 → 21.
Default case = the previous slide's numbers. Step line by line; on line 4 click Y[1,1] (7), on line 7 the centre of dXp (four windows, 1) and then a corner (one window). Then: "count the windows" (dX = 1 2 1 / 2 4 2 / 1 2 1), "same" padding (the corner output's gradient mostly lands on the zeros and is dropped on line 8) and stride 2 (windows do not overlap: dX is copies of K scaled by dY).
Answer to the previous question: 9 receive, 27 get nothing. Then drag the stride to 1 and show the accumulation when windows overlap.