3.4.5 · D3Convolutional Neural Networks

Worked examples — CNN architecture design

3,132 words14 min readBack to topic

Before any numbers, let us re-earn the four formulas so no symbol is a mystery.

Cloze checkpoints:

The floor in the output-size formula exists because
you cannot fit a fractional filter — a partial last step is discarded.
A conv has kernel side , so its receptive field contribution is
— it mixes channels but sees only one pixel of space.

The scenario matrix

Every CNN-design question is one (or a blend) of these cells. Each example below is tagged with its cell.

Cell What makes it tricky Covered by
A. Vanilla forward size plain stride-1, no padding Ex 1
B. Stride > 1 division + floor, size shrinks fast Ex 2
C. "Same" padding choose so size is preserved Ex 3
D. Non-divisible / floor bites not a multiple of Ex 4
E. Degenerate conv, RF unchanged, channel mixer Ex 5
F. Parameter comparison two designs, same RF, which is cheaper Ex 6
G. FLOP balance limiting law halve + double ⇒ FLOPs constant Ex 7
H. Receptive-field chain multi-layer RF with mixed strides Ex 8
I. Real-world word problem pick depth to cover an object Ex 9
J. Exam twist / zero-degenerate pooling to , global average pool Ex 10

Ex 1 — Cell A: vanilla forward pass

Forecast: guess — is the output bigger, smaller, or the same size as ? How many weights, roughly thousands or millions?

  1. Output side. Plug into the size formula. Why this step? A filter with no padding cannot reach the outer 2-pixel rim, so the map shrinks by pixels total. Output is .
  2. Weights. . Why this step? Each of 6 output channels holds a stack across all 3 input channels.

Verify: matches "shrink by " ✓. This is exactly LeNet-5's first layer (parent note), giving — our numbers agree with the classic. Weights is tiny, as expected for a small kernel. See 3.401-convolution-operation.


Ex 2 — Cell B: stride greater than one

Forecast: stride keeps only every 4th position — expect the output to be roughly a quarter of . Guess a number near .

  1. Numerator first. . Why this step? This is "how much room the filter's top-left corner has to travel."
  2. Divide by stride, floor, add one. Why this step? Stride means only every 4th travel-position is a real output pixel; counts the starting position.

Verify: exactly (no floor needed), giving — the published AlexNet number. Sanity: ✓.


Ex 3 — Cell C: "same" padding

Forecast: to undo the shrink, you must add pixels back. Guess .

  1. Set output = input and solve for (stride ): Why this step? We demand and see what padding that forces.
  2. Plug : . Why this step? One ring of zeros exactly compensates the one-pixel rim a filter cannot reach on each side.
  3. Edge case — even . The clean symmetric answer is a whole number only when is odd (then is even and divides by ). For , is not an integer, so you cannot pad the same amount on both sides and still preserve size exactly — frameworks resort to asymmetric padding (e.g. on the left, on the right). This is exactly why almost every real CNN uses odd kernels (): odd kernels have a well-defined centre pixel and admit integer symmetric "same" padding.

Verify: with : ✓. With : , confirming the even-kernel obstruction. This is why VGG uses odd kernels with everywhere and its spatial size only changes at pooling — see 3.4.02-pooling-layers.


Ex 4 — Cell D: the floor actually bites

Forecast: stride roughly halves → guess . But watch the floor.

  1. Numerator: . Why this step? This is the travel room for the filter's top-left corner; we compute it first because everything else divides this number by the stride.
  2. Divide + floor: Why this step? ; the filter's last would-be landing spot falls off the edge, so the floor discards it — the rightmost column of input is never centred on. This is a real information loss, not a rounding cosmetic.

Verify: naive (no floor) gives , but a real map must have a whole number of pixels, so we floor to . The we drop is the pixel column the design silently discards — exactly the "loses information, use sparingly" warning about stride in the parent note.


Ex 5 — Cell E: degenerate conv

Forecast: so spatial size should be untouched; the "" shrink is zero. Guess , RF unchanged.

  1. Spatial size. . Unchanged ✓. Why this step? A filter sits on a single pixel — no rim to lose.
  2. Channels & params. Output . Params . Why this step? It's a per-pixel linear mix of the input channels into new channels — a channel compressor, not a spatial filter.
  3. Receptive field. , so RF does not grow. Why this step? It sees no new spatial context; this is why Inception/ResNet put convs before the expensive ones purely to cut channel count.

Verify: ✓. This squeeze is the dimensionality-reduction trick from the parent's Inception section.


Ex 6 — Cell F: parameter comparison, same receptive field

Forecast: parent note claimed ~28% saving. Guess (b) is cheaper.

  1. Design (a) params: . Why this step? Straight from the parameter formula with and both channel counts equal to , so .
  2. Design (b) params: two layers, each : . Why this step? Each layer also has in and out, giving ; there are two such layers stacked, so we add their counts.
  3. Saving fraction: . Why this step? cancels — the saving is independent of width.

Verify: with : (a) , (b) , and ✓. Bonus: (b) also inserts an extra ReLU, adding nonlinearity for free — the VGG insight.


Ex 7 — Cell G: the FLOP-balance limiting law

Forecast: parent note says "halve , double ⇒ FLOPs constant." Guess: equal.

  1. Stage-1 FLOPs. . Why this step? We plug directly into the FLOP formula: one multiply-add per output pixel (), per output channel (), per weight in the patch ().
  2. Stage-2 FLOPs. . Why this step? Spatial term dropped by (), but the two channel counts each doubled, multiplying by — the factors cancel exactly.

Verify: ratio ✓. This is why "double on pool" is the default channel schedule.


Ex 8 — Cell H: receptive-field chain with mixed strides

Forecast: each stride-2 layer roughly doubles how fast RF grows. Guess final RF somewhere near .

Use , where is the product of strides of earlier layers.

  1. Layer 1 (first layer, prior stride product ): . Running stride . Why this step? We start at because a single output neuron of layer 0 (an input pixel) sees exactly one input pixel — that is the base case. The first layer has no earlier strides, so its scale factor is .
  2. Layer 2 (pool, prior stride product ): . Running stride . Why this step? Because layer 1 had stride 2, one step of this pool now jumps 2 input pixels, so its growth is scaled by the accumulated ; then we update the running product to .
  3. Layer 3 (conv, prior stride product ): . Why this step? Two stride-2 layers came before, so the running product is ; each of this layer's pixel jumps now covers input pixels, adding to reach .

Verify: monotonically increasing ✓, and every added term is a positive multiple of ✓. Deep enough stems reach the target of the parent's "required depth" example. See 4.2.03-vanishing-gradient for why we can afford such depth via skips.

The figure below turns this chain into a picture. How to read it: the horizontal axis lists the four states left-to-right (input, then after each of the three layers); the vertical axis is the receptive field measured in input pixels. Each coloured bar's height is the RF after that layer, so you watch the bar climb . The red arrows between consecutive bars are the added term — notice the arrow lengths are , then , then : the middle jump is smaller than the last even though both use , because the running stride product grew from to , doubling the reach of every filter step. That growing-stride effect is the whole point of the figure.

Figure — CNN architecture design

Ex 9 — Cell I: real-world word problem

Forecast: each layer adds to RF, and we need . Guess ~25 layers.

  1. RF law for stacked , stride 1: . Why this step? With stride 1 everywhere, the running stride product stays at every layer, so the general growth simplifies: each of the layers just adds a flat on top of the base case , giving .
  2. Solve : , so . Why this step? counts whole layers, so a fractional requirement of must be rounded up: layers give only (one pixel short of the nodule), while layers give , the first depth that fully contains a -pixel feature.

Verify: ✓; ✗, so is indeed the minimum. Interpretation: pure stacks are RF-inefficient — this is exactly why real designs insert pooling/stride (Ex 8) or dilations to grow RF faster, and why transfer learning from a deep pretrained backbone is often the practical answer.


Ex 10 — Cell J: exam twist / degenerate pooling to

Forecast: global average pool collapses each map to one number → . Guess the FC-vs-GAP head is far cheaper.

  1. GAP step. Average each of the channels over its whole grid: output . Why this step? Averaging over all spatial positions throws away where a feature fired, keeping only whether it fired — full translation invariance (parent Principle 3).
  2. conv (1000 filters). Params . Output → softmax. Why this step? A per-"pixel" (here single pixel) linear map from features to class scores.
  3. Compare to flatten→FC. Flatten gives inputs; FC(1000) needs params. Why this step? Flattening bakes in the grid, so it needs more weights and locks the input size.
  4. (b) The twist. On the backbone outputs, say, . GAP still averages the whole grid down to — its output shape is independent of grid size. A flatten head would instead produce numbers and crash the fixed FC layer, which expects exactly inputs. Why this step? Zero spatial position encoding = works at any input size; GAP's output dimension depends only on channel count, never on .

Verify: GAP head params vs FC head ; ratio , i.e. GAP uses about of the weights ✓ — a saving matching the collapse. Degenerate check: on a input GAP already yields , the smallest non-trivial map, so no further pooling is possible — the boundary case is handled. See 6.3.01-model-compression.


Recall Self-test (reveal after guessing)

Conv on side gives output side ::: . AlexNet layer 1 () output side ::: . Padding to keep size for ::: (integer only because is odd). on side gives ::: (floor drops half a step). Two vs one param saving ::: . Layers of (stride 1) to reach ::: . GAP+(1000) head params on ::: .

Connections