Chapter 8 // Architectures
Modern CNNs
A tour of the architectures that won ImageNet, one after another — and the two ideas hidden among them, batch normalization and residual connections, that everything since has been built on.
Adapted from the original chapter of Dive into Deep Learning · CC BY-SA 4.0 · the Python shown is the book's; the figures run a faithful TypeScript port in your browser
Once you can wire a convolution to a pooling layer, the question becomes how to stack them. This chapter is a decade of answers, in the order they arrived — each a brief champion of the ImageNet competition, each keeping the good ideas of the last and adding one. Most of it is architecture: diagrams of blocks. But two of these ideas are real, checkable machinery, and they reshaped deep learning far beyond vision.
Act 1 · The 2012 leap
01/05
LeNet
1995 · sigmoid · avg-pool
5 layers
61.7K params
Meet LeNet, 1995: two conv-and-pool stages, a small classifier, 61.7K parameters. It read cheques for years — then stalled. For a decade, hand-crafted features beat it on real photographs.
AlexNet proved the recipe scales, but it placed every layer by hand and hid most of its weight in two enormous fully connected layers. The next wave made the design modular — and went hunting for that fat.
Act 2 · Design by the block
01/05
VGG stops placing layers one at a time and repeats a block: a few padded 3×3 convs, then a pool. Two stacked 3×3s see the same window as one 5×5 — with fewer weights and an extra nonlinearity.
Blocks made networks deeper, and depth exposed a new problem: the signal flowing through a deep stack drifts and destabilizes, and the gradient flowing back through it dies. The next two ideas are the fixes — and the reason this chapter matters most.
Act 3 · Normalize the middle
01/06
Deep inside a network, a minibatch of activations drifts to wild, mismatched scales — one feature centered at +3, another at −4, spreads from 0.4 to 5. That drift slows every layer above.
Batch normalization steadies the forward signal. But there is a second failure at depth — the backward one — and it needs a different, even simpler idea.
Act 4 · The gradient's highway
01/06
Deeper should never be worse: if the extra layers could just copy their input, a deep net would contain the shallow one. But plain deep nets don't find that identity — past a point, adding layers hurts even the training error.
Residual connections and batch normalization together made hundred-layer networks routine. Everything after is variation: what you do with the skip, and who does the designing.
Act 5 · Beyond adding
01/04
DenseNettakes ResNet's skip one step further: instead of adding the input back, it concatenatesit. Every layer's output is appended, so channels grow by a fixed growth rate at each step.
The lab
Three claims from the acts, yours to break: that batch norm makes a layer's output deaf to the scale of its input, that a residual skip is the difference between a gradient that survives depth and one that vanishes, and that every one of these architectures is just a traced stack of shapes and parameters.
Batch norm, by hand
Drag the incoming activations off-scale. In training, the batch output holds μ=β, σ=γ no matter what.
in μ 3.13 · σ 2.37
out μ 0.00 · σ 1.00
Training: output μ ≈ β, σ ≈ γ — completely deaf to the incoming shift and scale. That invariance is what stabilizes the layers above.
Plain vs residual, at depth
Drag the stack deeper. Watch the plain gradient collapse while the residual one refuses to die.
plain 1e-17
residual 3.54
ratio 3e+17×
The plain gradient has vanished — the early layers get essentially no signal and can't learn. The residual stack still carries ~1.
The architecture explorer
Pick a network. Every shape and parameter count is traced live by the verified port.
ResNet-18
2015Residual connections: add the input back, so identity is free. Trains 100+ layers.
11M
parameters
315M
MACs / image
18
weight layers
input 1×96×96 → layers
| Conv 7×7 ×64 /2 | 64×48×48 | 3,200 |
| BatchNorm | 64×48×48 | 128 |
| ReLU | 64×48×48 | · |
| MaxPool 3×3 | 64×24×24 | · |
| Residual ×64 | 64×24×24 | 74,112 |
| Residual ×64 | 64×24×24 | 74,112 |
| Residual ×128 /2 | 128×12×12 | 230,528 |
| Residual ×128 | 128×12×12 | 295,680 |
| Residual ×256 /2 | 256×6×6 | 919,808 |
| Residual ×256 | 256×6×6 | 1,181,184 |
| Residual ×512 /2 | 512×3×3 | 3,674,624 |
| Residual ×512 | 512×3×3 | 4,721,664 |
| GlobalAvgPool | 512 | · |
| Flatten | 512 | · |
| Linear ×10 | 10 | 5,130 |
The real code
Every act carries a </> chip with its section's exact PyTorch. The residual
block — two idea-bearing lines of it — is the whole chapter in miniature:
class Residual(nn.Module):
def __init__(self, num_channels, use_1x1conv=False, strides=1):
super().__init__()
self.conv1 = nn.LazyConv2d(num_channels, kernel_size=3, padding=1, stride=strides)
self.conv2 = nn.LazyConv2d(num_channels, kernel_size=3, padding=1)
self.conv3 = nn.LazyConv2d(num_channels, kernel_size=1, stride=strides) if use_1x1conv else None
self.bn1 = nn.LazyBatchNorm2d()
self.bn2 = nn.LazyBatchNorm2d()
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
Y += X # the residual connection — everything hinges here
return F.relu(Y)Batch normalization from scratch (the other pillar)
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
if not torch.is_grad_enabled():
# prediction: normalize with the running (moving) statistics
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else:
# training: statistics of THIS minibatch (dim 0 for FC, (0,2,3) for conv)
mean = X.mean(dim=0)
var = ((X - mean) ** 2).mean(dim=0)
X_hat = (X - mean) / torch.sqrt(var + eps)
moving_mean = (1.0 - momentum) * moving_mean + momentum * mean
moving_var = (1.0 - momentum) * moving_var + momentum * var
Y = gamma * X_hat + beta # scale and shift
return Y, moving_mean.data, moving_var.dataThree things to remember
- Architectures are stacks of blocks, and the trend is efficiency. After AlexNet, progress meant composing repeatable blocks (VGG), replacing fat fully connected layers with 1×1 convs and global pooling (NiN), and running many kernel sizes in parallel (GoogLeNet) — often with fewer parameters, not more.
- Batch normalization stabilizes the forward pass. Normalize each layer's activations to zero mean and unit variance over the minibatch, then let a learned scale and shift back in. It smooths optimization, permits bigger learning rates, and regularizes — and it behaves differently at test time, using running statistics.
- Residual connections stabilize the backward pass. Adding the input back around a block means the gradient always has an identity path home, so it never vanishes — which is the only reason networks over 100 layers deep can be trained. Both ideas later became standard far outside vision.
Adapted from Chapter 8 of Dive into Deep Learning by Zhang, Lipton, Li, and Smola (CC BY-SA 4.0). The figures run this site's verified TypeScript port; all computation happens live in your browser.