Search…

CNN Architectures Explained: From LeNet to ResNet

In this series (30 parts)
  1. Computer Vision Roadmap: From Basics to Real Projects
  2. What is Computer Vision? From Pixels to Decisions
  3. A Short History of Computer Vision: 1545 to Now
  4. Math for CV Beginners: Vectors, Matrices, Convolutions
  5. Image Fundamentals: Color, Histograms, Noise, Filtering
  6. How Images and Video Are Stored: Colour, JPEG, Frames
  7. OpenCV Setup + First 10 Tasks in Python
  8. Vision Metrics: Accuracy, Precision, Recall, mAP, IoU
  9. CV Project Workflow: Dataset, Baseline, Error Iteration
  10. Intensity Transforms and Frequency-Domain Filtering
  11. Edge Detection and Thresholding That Actually Work
  12. Morphology, Contours, and Shape Analysis for Real Images
  13. Feature Matching (SIFT/ORB) and Image Stitching
  14. Camera Calibration and Perspective Correction
  15. Epipolar Geometry, Stereo Vision, and Depth Estimation
  16. Optical Flow and Motion Tracking in Video
  17. HOG, HOF and MBH: Descriptors Before Deep Learning
  18. Your First Image Classifier (PyTorch + Transfer Learning)
  19. Data Pipelines and Augmentation for Vision Models
  20. CNN Architectures Explained: From LeNet to ResNet
  21. YOLO Detection Pipeline: Data to Inference
  22. Semantic and Instance Segmentation: U-Net to Mask R-CNN
  23. Vision Transformers (ViT) and When to Use Them
  24. CV Explainability: Grad-CAM, Failures, Bias Checks
  25. 3D Vision Basics: SfM, Point Clouds, and Pose Estimation
  26. Multimodal Vision: CLIP, Embeddings, and Retrieval Systems
  27. Video Understanding: Flow Networks, Interpolation, Stabilisation
  28. Real-Time CV Systems: Tracking, Latency, Streaming
  29. Deploying CV Models: ONNX, TensorRT, Edge, and APIs
  30. Responsible CV: Privacy, Fairness, Security, Governance

You have used ResNet-18 twice now without knowing what is inside it. This post opens it up — not as history, but as a sequence of specific problems and the specific fixes that solved them. Every claim here comes with the arithmetic.

Prerequisites: Your first image classifier and convolution basics.

Why convolution at all

A 224×224 colour image has 150,528 numbers. Connect that to even 1,000 hidden units with a fully connected layer:

224×224×3×1000=150,528,000 weights224 \times 224 \times 3 \times 1000 = 150{,}528{,}000 \text{ weights}

150 million parameters for one layer, and it would still be a bad model: a cat shifted ten pixels right activates completely different weights, so the network has to learn “cat” separately at every position.

A convolution layer with 64 filters of size 3×3 on 3 input channels:

3×3×3×64+64=1,728+64=1,792 parameters3 \times 3 \times 3 \times 64 + 64 = 1{,}728 + 64 = 1{,}792 \text{ parameters}

weight sharing The same small filter is applied at every position in the image, so a feature learned in one place is detected everywhere. It is what makes a conv layer's parameter count independent of image size.

84,000 times fewer parameters, and the feature is detected wherever it appears. Those two properties — weight sharing and translation equivariance — are the entire argument for convolution.

Position (0, 0)
Initialization: A 4×4 filter (16 weights) will slide across the 12×12 input with stride 2. Output size = (12 − 4)/2 + 1 = 5×5.

Step the filter through the grid above. The same nine weights are reused at every position; only the patch underneath changes.

The parameter formula

params=K×K×Cin×Cout+Cout\text{params} = K \times K \times C_{in} \times C_{out} + C_{out}

# Layer KK CinC_{in} CoutC_{out} Calculation Parameters target
1 conv1 7 3 64 7×7×3×64 + 64 9,472
2 3×3 early 3 64 64 3×3×64×64 + 64 36,928
3 3×3 mid 3 64 128 3×3×64×128 + 128 73,856
4 3×3 deep 3 256 512 3×3×256×512 + 512 1,180,160
5 1×1 reduce 1 256 64 1×1×256×64 + 64 16,448

Conv layer parameters. Note how deep layers with many channels dominate the total.

Nothing in that formula mentions image size. A 3×3 conv from 64 to 128 channels costs 73,856 parameters whether the input is 8×8 or 800×800.

The output size formula

out=WK+2PS+1\text{out} = \left\lfloor \frac{W - K + 2P}{S} \right\rfloor + 1

# Input WW KK Padding PP Stride SS Calculation Output target
1 224 3 1 1 (224−3+2)/1 + 1 224
2 224 3 0 1 (224−3+0)/1 + 1 222
3 224 3 1 2 (224−3+2)/2 + 1 112
4 224 7 3 2 (224−7+6)/2 + 1 112
5 56 1 0 2 (56−1+0)/2 + 1 28

Output sizes. Padding K//2 with stride 1 keeps the size unchanged — that is why 3×3 pairs with padding 1.

Receptive field: how much each unit sees

receptive field The region of the original input image that influences one output unit. It grows with depth, which is how small filters eventually see whole objects.

A single 3×3 conv sees a 3×3 patch. Stack another and each unit of the second layer sees a 3×3 region of the first layer, which itself spans 3×3 of the input — so it sees 5×5.

1 layer: sees 3×3 (7×7)
1
1
1
0
0
0
0
1
1
1
0
0
0
0
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
stack
2 layers: sees 5×5 (7×7)
1
1
1
1
1
0
0
1
1
1
1
1
0
0
1
1
1
1
1
0
0
1
1
1
1
1
0
0
1
1
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
stack
3 layers: sees 7×7 (7×7)
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

Each 3×3 layer adds 2 to the receptive field. With stride, growth is multiplicative:

RFn=RFn1+(K1)i<nSiRF_{n} = RF_{n-1} + (K - 1) \prod_{i<n} S_i

# After layer Kernel Stride Cumulative stride Receptive field target
1 conv1 7 2 2 7
2 maxpool 3 2 4 11
3 layer1 (2 blocks) 3 1 4 43
4 layer2 (2 blocks) 3 2 8 99
5 layer3 (2 blocks) 3 2 16 211
6 layer4 (2 blocks) 3 2 32 435

ResNet-18 receptive field growth. By layer4 each unit sees more than the whole 224×224 input.

That final row explains something practical. By layer4, a unit’s receptive field exceeds the input, which is why fine-tuning only layer4 adapts whole-object reasoning while leaving edge and texture detectors alone.

Why 3×3 beat 5×5

VGG’s contribution was noticing that two 3×3 convs reach the same 5×5 receptive field as one 5×5 conv, and cost less. With CC channels in and out:

one 5×5:  5×5×C×C=25C2\text{one } 5\times5: \; 5 \times 5 \times C \times C = 25C^2 two 3×3:  2×(3×3×C×C)=18C2\text{two } 3\times3: \; 2 \times (3 \times 3 \times C \times C) = 18C^2

18 versus 25 is 28% fewer parameters, and there is a ReLU between the two 3×3 layers where the 5×5 has none — more nonlinearity for less cost.

With C=128C = 128: one 5×5 costs 409,600 weights, two 3×3 cost 294,912.

Position (0, 0)
Watch a 3×3 filter slide across a 5×5 input. At each position, the filter overlaps a patch, multiplies element-wise, and sums to produce one value in the output feature map.

Try the filter selector above. Edge, blur and Sobel are the kinds of low-level detectors a trained network’s first layer converges to on its own.

The architectures, and what each one fixed

# Model Year Layers Parameters ImageNet top-1 target The problem it fixed
1 LeNet-5 1998 7 60K Proved conv + pool works, on digits
2 AlexNet 2012 8 60M 57.1% ReLU, dropout, GPUs — scale was possible
3 VGG-16 2014 16 138M 71.6% Depth via uniform 3×3 stacks
4 GoogLeNet 2014 22 6.8M 69.8% 1×1 bottlenecks cut cost massively
5 ResNet-50 2015 50 25.6M 76.1% Skip connections made real depth trainable
6 ResNet-152 2015 152 60M 78.3% Depth kept paying off
7 EfficientNet-B0 2019 82 5.3M 77.7% Balanced scaling of depth/width/resolution

Seven architectures. Notice GoogLeNet: 22 layers in 6.8M parameters against VGG's 16 layers in 138M.

The competition that drove all of this

Those architecture numbers came out of one benchmark, and seeing its full trajectory makes the pace of change concrete.

The ImageNet Large Scale Visual Recognition Challenge ran annually on 1.2 million training images across 1,000 classes. The headline metric was top-5 error: the fraction of images where the correct label was not among the model’s five best guesses.

Two things in that chart are worth sitting with. The 2011-to-2012 drop of 9.4 points is larger than the total improvement of the preceding two years, and it is the single event that redirected the field. And the crossing of the human line in 2015 is why the competition eventually stopped being interesting — once error is a third of a human’s, the remaining errors are mostly label ambiguity.

Top-1 accuracy tells the longer story, because it kept moving after top-5 saturated:

For the wider arc this benchmark sits inside — including what came before SIFT+FV and why the datasets themselves mattered as much as the models — see the history of computer vision.

The depth problem

By 2015 the obvious move was to stack more layers. It did not work.

# Plain network depth Training error target Test error Diagnosis
1 20 layers 7.5% 8.8% fine
2 32 layers 8.9% 10.2% worse
3 56 layers 10.8% 12.1% much worse

Plain deep networks on CIFAR-10. Training error rises with depth, which rules out overfitting.

Read the training error column carefully. It goes up. If this were overfitting, training error would fall while test error rose. Instead the deeper network cannot even fit the training data — an optimisation failure, not a generalisation one.

That is a mathematical absurdity on its face. A 56-layer network can represent everything a 20-layer network can: set the extra 36 layers to the identity. So the extra capacity is there and gradient descent simply cannot find it.

Why: gradients shrink multiplicatively

Backpropagation multiplies a gradient by each layer’s Jacobian on the way back. If each layer scales it by an average factor of 0.9:

# Depth Gradient factor 0.9n0.9^n target Effective learning rate at layer 1
1 10 layers 0.349 35% of nominal
2 20 layers 0.122 12%
3 30 layers 0.042 4%
4 50 layers 0.005 0.5%
5 100 layers 0.000027 essentially zero

Multiplicative gradient decay. At 50 layers the early layers receive 0.5% of the signal.

0.950=0.005150.9^{50} = 0.00515. The first layers barely move, so they stay near their random initialisation, and everything built on top of random features is handicapped.

The residual block

He and colleagues changed one thing. Instead of a block computing H(x)H(x) directly, have it compute a correction to its input:

y=F(x)+xy = F(x) + x

Ready — compare signal flow
Click Forward Pass to send a signal through both networks and see how they differ.
Plain Network ResNet (with skip connections) Skip connection

The visualization above shows the two paths. Now differentiate:

yx=F(x)x+1\frac{\partial y}{\partial x} = \frac{\partial F(x)}{\partial x} + 1

That +1+1 is the entire fix. Even when F/x\partial F/\partial x shrinks toward zero, the gradient reaching xx is at least 1. Stack fifty of these and the gradient still arrives at the first layer intact, because along the skip path it is multiplied by 1 fifty times instead of by 0.9.

There is a second reading that is just as useful. If a block is not needed, F(x)=0F(x) = 0 makes it the identity, and driving weights toward zero is exactly what weight decay does anyway. The identity is now the easy default rather than something the network must learn.

class BasicBlock(nn.Module):
    def __init__(self, cin, cout, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(cin, cout, 3, stride, 1, bias=False)
        self.bn1   = nn.BatchNorm2d(cout)
        self.conv2 = nn.Conv2d(cout, cout, 3, 1, 1, bias=False)
        self.bn2   = nn.BatchNorm2d(cout)
        # projection only when shape changes
        self.skip = nn.Sequential()
        if stride != 1 or cin != cout:
            self.skip = nn.Sequential(
                nn.Conv2d(cin, cout, 1, stride, bias=False),
                nn.BatchNorm2d(cout))

    def forward(self, x):
        out = torch.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        return torch.relu(out + self.skip(x))

Note bias=False on every conv. BatchNorm immediately subtracts the mean, which cancels any bias term, so it would be dead weight.

Bottleneck blocks

For deeper networks, ResNet uses a three-layer block that is far cheaper. The trick is 1×1 convolutions that squeeze the channel count before the expensive 3×3.

A plain block at 256 channels:

2×(3×3×256×256)=2×589,824=1,179,6482 \times (3 \times 3 \times 256 \times 256) = 2 \times 589{,}824 = 1{,}179{,}648

The bottleneck version — reduce to 64, do the 3×3 there, expand back:

1×1×256×6416,384+3×3×64×6436,864+1×1×64×25616,384=69,632\underbrace{1 \times 1 \times 256 \times 64}_{16{,}384} + \underbrace{3 \times 3 \times 64 \times 64}_{36{,}864} + \underbrace{1 \times 1 \times 64 \times 256}_{16{,}384} = 69{,}632

1,179,64869,632=16.9× fewer parameters\frac{1{,}179{,}648}{69{,}632} = 16.9\times \text{ fewer parameters}

# Block type Layers Parameters at 256 ch target Used in
1 Basic 3×3, 3×3 1,179,648 ResNet-18, ResNet-34
2 Bottleneck 1×1, 3×3, 1×1 69,632 ResNet-50, 101, 152

Basic versus bottleneck. The 17x saving is why ResNet-50 has fewer parameters than VGG-16 despite being three times deeper.

1×1 convolution A convolution with a single-pixel kernel. It cannot see spatial context, so it acts purely as a learned linear mixing of channels — most often to reduce or expand channel count cheaply.

Choosing a backbone

QuestionResNet-18ResNet-50EfficientNet-B0MobileNetV3
Parameters11.7M25.6M5.3M5.4M
ImageNet top-169.8%76.1%77.7%75.2%
FLOPs per image1.8G4.1G0.39G0.22G
CPU inference~35 ms~95 ms~45 ms~18 ms
Fine-tunes on 1k imagesvery wellwellwellwell
Best forFast iteration, baselinesAccuracy on a serverAccuracy per FLOPPhones and edge
Backbone selection. Start with ResNet-18 because it is fastest to experiment with.

Compare EfficientNet-B0 and ResNet-50: nearly the same accuracy from a tenth of the FLOPs. But EfficientNet’s depthwise separable convolutions are memory-bandwidth heavy, so its real CPU latency is only about twice as fast, not ten times. FLOPs are a poor predictor of latency. Always measure on your target hardware.

Practice task

Using the classifier from post 17.

  1. Print sum(p.numel() for p in model.parameters()) for ResNet-18, ResNet-50, and MobileNetV3-Small.
  2. For ResNet-18’s layer1[0].conv1, verify the parameter count against K2CinCoutK^2 C_{in} C_{out} by hand.
  3. Pass a 1×3×224×224 tensor through and print the shape after each stage. Check each against the output-size formula.
  4. Time 100 forward passes on CPU for each backbone. Plot latency against parameter count.
  5. Fine-tune all three on your dataset for the same epochs. Plot accuracy against latency.
  6. Take one BasicBlock and delete the skip connection. Retrain and compare.
  7. Build a 40-layer plain conv network with no skips and train it. Record the training accuracy.

Steps 6 and 7 are the ones that make it concrete. The 40-layer plain network will train worse than a 20-layer one on the same data, and watching that happen on your own machine turns the vanishing-gradient argument from a claim into something you have seen.

Summary

Convolution replaced 150 million fully connected weights with 1,792, because the same filter is reused at every position. Parameters are K2CinCout+CoutK^2 C_{in} C_{out} + C_{out} and do not depend on image size at all.

Receptive field grows with depth — 3, then 5, then 7 for stacked 3×3 layers, reaching 435 pixels by ResNet-18’s layer4, which is larger than the input. Two 3×3 convs match a 5×5’s field for 18C218C^2 instead of 25C225C^2, a 28% saving plus an extra ReLU.

Depth then hit a wall. A 56-layer plain network had higher training error than a 20-layer one, which is an optimisation failure rather than overfitting: at 0.950=0.0050.9^{50} = 0.005, early layers received half a percent of the gradient. The residual block’s y=F(x)+xy = F(x) + x makes y/x=F/x+1\partial y / \partial x = \partial F/\partial x + 1, and that +1+1 keeps the gradient alive through any depth.

Bottleneck blocks squeeze 256 channels to 64 for the expensive 3×3, cutting a block from 1,179,648 to 69,632 parameters — 17× — which is how ResNet-50 is both deeper and smaller than VGG-16.

What comes next

Classification names one thing per image. Most real problems need to know where things are, and how many. The YOLO object detection pipeline builds on everything here — a backbone extracts features, then a detection head predicts boxes and classes at many positions at once, scored with the IoU and mAP metrics from earlier in the series.

Test your understanding
A 56-layer plain CNN has 10.8% training error while a 20-layer one has 7.5% on the same data. What does this tell you?
Test your understanding
You need a 256-channel residual block and are choosing between a basic block (two 3×3) and a bottleneck (1×1 → 3×3 → 1×1 with 64 in the middle). What is the parameter difference?

Frequently asked questions

Should I ever design a CNN architecture myself?
Rarely. Established backbones have been tuned by thousands of GPU-hours of search and come with pretrained weights that are worth more than any architectural cleverness on a small dataset. Design your own only when your input is genuinely unlike photographs — very high resolution, non-visual channels, unusual aspect ratios — or when a hard latency budget rules out every off-the-shelf option. Even then, start by modifying a known architecture.
What does BatchNorm actually do?
It normalises each channel's activations to zero mean and unit variance over the batch, then applies a learned scale and shift. Practically it lets you use much higher learning rates, reduces sensitivity to weight initialisation, and adds mild regularisation from batch noise. It also means any preceding conv bias is redundant. On very small batches its statistics become unreliable, which is when GroupNorm is the better choice.
Why do networks reduce spatial size while increasing channels?
Early layers detect simple things like edges that only need a few channels but must be located precisely, so you keep resolution high and channels low. Deeper layers detect complex parts and objects, where there are many possible patterns but exact position matters less. Halving each spatial dimension while doubling channels keeps the compute per layer roughly constant while the representation moves from where to what.
Is ResNet still worth using?
Yes. ResNet-18 and ResNet-50 remain excellent defaults: well understood, fast, reliably fine-tunable on small datasets, and supported by every deployment toolchain. Vision transformers beat them given enough data, and EfficientNet gives better accuracy per FLOP, but neither is a better starting point for a project with a few thousand images. Start with ResNet, measure, and change only for a specific measured reason.
How do I pick between ResNet-18 and ResNet-50?
Start with ResNet-18 because it trains faster and lets you iterate on data and augmentation, which matter more. Move to ResNet-50 when your training accuracy is also low, meaning you are underfitting rather than overfitting, and when your latency budget has room for roughly three times the CPU cost. If errors are concentrated in one class pair, more data for that pair beats the larger backbone by a wide margin.
Start typing to search across all content
navigate Enter open Esc close