Skip to content
ML Academy

Core day 36 of 60 · Week 8 · Part II · orientation depth

CNNs

A plant engineer wants to classify 28 x 28 greyscale crops of weld seams as good or defective. A colleague proposes one convolutional layer: 8 filters of size 5 x 5, stride 2, padding 1. Before training, can you say what shape comes out, how many numbers the layer learns, and what one filter looks for?

By the end: For a convolution layer you compute the output shape (with padding, stride and dilation) and the parameter count by hand, explain what a filter detects, and say when a convolution fits non-image data and when it does not.

At a glance

One small filter, reused at every position, turns an image into maps of where each learned pattern appears; shapes and counts follow from five numbers.

  • Local filter

    A k x k grid of weights looks at one small patch at a time.

  • Shared weights

    The same filter at every position: 208 weights instead of a million.

  • Output size

    floor((n + 2p - d(k - 1) - 1) / s) + 1 per axis; padding counts twice.

  • Equivariant

    Shift the input and the feature map shifts; pooling adds invariance.

  • When it fits

    Neighbouring values related and patterns meaning the same anywhere.

Recall · 5 min

No notes, one minute each: (1) the parameter count of a dense layer with 784 inputs and 10 outputs, biases included; (2) what a validation loss that rises while the training loss keeps falling tells you (Day 34); (3) the array shape of a batch of 32 greyscale 28 x 28 images. Today's idea in one line: reuse the same small weight pattern at every position.

Open due reviews

Sign in to record this block.

Concept · 10 min

A dense layer treats a 28 x 28 image as 784 unrelated numbers. Images, audio and sensor traces are different: nearby values belong together, and the same pattern (an edge, a scratch, a knock) can appear anywhere. A Convolution (filter, kernel) slides one small grid of learned weights over the input and records, at every position, how strongly its pattern appears.

Output size is arithmetic - kernel, padding, stride and dilation decide it, and the filter decides what lights up

Predict first

A 28 x 28 input, a 3 x 3 kernel, stride 1 and no padding. What is the output size?

Text description of the figure

A grid input with a sliding kernel. Controls for input size, kernel size, padding and stride update the output size and the number of weights, and a pattern filter highlights where the input matches it.

Four ideas behind a convolution layer Overview
  • Local filter

    A small grid of weights, for example 3 x 3, multiplies one patch and sums.

  • Shared weights

    The same filter slides over every position of the input.

  • Feature map

    The output grid: how strongly the pattern appears at each position.

  • Pooling

    A max or mean over a window or the whole map; no weights.

One filter slid over every position fills the feature map Illustration
Input image (10 x 10)0000000000000000000000000000000011111100001111110000111111000011111100001111110000111111000000000000

Input: 0 = dark, 1 = bright. The highlighted box is the 3 x 3 window for output row 3, column 1.

Vertical-edge filter-101-101-101

Filter: right column minus left column. In the box: 3 products are non-zero, sum = 3.

Feature map (8 x 8)00000000110000-1-1220000-2-2330000-3-3330000-3-3330000-3-3330000-3-3220000-2-2

10 - 3 + 1 = 8 positions per axis. Only 28 of 64 cells fire: + where dark turns bright, - where bright turns dark.

Horizontal-edge filter's map00000000123333211233332100000000000000000000000000000000-1-2-3-3-3-3-2-1

Same image, the transposed filter: it ignores the side edges and fires on the top and bottom edges instead.

Synthetic 10 x 10 image; cross-correlation without padding (no kernel flip), computed by the same code as the conv calculator. Blue = positive, red = negative response.

Each cell of the map is one window's sum of nine products. The same nine weights produce all 64 cells; the sign tells dark-to-bright from bright-to-dark.

Output size along one axis Formula
nout=⌊n+2p−d(k−1)−1s⌋+1  =  ⌊28+2−4−12⌋+1=13n_{out} = \left\lfloor \frac{n + 2p - d(k-1) - 1}{s} \right\rfloor + 1 \;=\; \left\lfloor \frac{28 + 2 - 4 - 1}{2} \right\rfloor + 1 = 13
n,kn, k
input length and kernel size (28 and 5 for the engineer's layer)
pp
zeros added on EACH side, hence 2p
ss
stride: positions skipped between windows
dd
dilation: gap between kernel taps (3 taps with d = 2 span 5 positions)
⌊⋅⌋\lfloor \cdot \rfloor
floor: a window that does not fit is dropped
Shape trace of a small CNN for 28 x 28 seam crops (padding 1, stride 1 convs) Diagram
Seam crop28 x 28 x 1greyscaleConv, 8 filters 3x3out 28 x 28 x 8; 80weightsMax pool 2 x 2out 14 x 14 x 8; 0weightsConv, 16 filters3x3x8 each; out 14 x 14x 16; 1,168 weightsMax pool 2 x 2out 7 x 7 x 16; 0weightsGlobal avg poolout 16: one mean perfilterDense 16 to 2good / defective; 34weights
  1. Seam crop28 x 28 x 1 greyscale
  2. Conv, 8 filters 3x3out 28 x 28 x 8; 80 weights
    • to Max pool 2 x 2
  3. Dense 16 to 2good / defective; 34 weights
  4. Max pool 2 x 2out 14 x 14 x 8; 0 weights
    • to Conv, 16 filters
  5. Global avg poolout 16: one mean per filter
    • back to Dense 16 to 2
  6. Conv, 16 filters3x3x8 each; out 14 x 14 x 16; 1,168 weights
  7. Max pool 2 x 2out 7 x 7 x 16; 0 weights
    • back to Global avg pool

Each box shows the tensor it outputs and the weights it learns: 1,282 in total, 0 in every pooling step. Conv weights: 8 x (9 + 1) = 80, then 16 x (3 x 3 x 8 + 1) = 1,168, because the second conv sees 8 input channels. Shapes from the output-size formula.

Learned numbers for the same 1,352 outputs from a 28 x 28 input Chart
  • Conv: 8 filters 5 x 5, stride 28 x 25 + 8208
  • Dense layer with 1,352 outputs784 x 1,352 + 1,3521,061,320

About 5,100 times fewer parameters, because one filter is reused at all 169 positions.

Move the pattern, the map moves with it; a global read-out does not Illustration
Input A (11 x 11)

Input A: a plus-shaped pattern near the top-left corner.

Feature map of A1222125212221

The template filter scores 5 where it lines up exactly (ring), less on partial overlaps.

Input B (11 x 11)

Input B: the same pattern, moved 3 rows down and 4 columns right.

Feature map of B1222125212221

The map moved by exactly (3, 4): equivariance. The peak is now at row 5, column 6 instead of 2, 2.

What survives the move
Read-outABVerdict
Feature mappeak 2,2peak 5,6moves with the input
Global max55identical: invariant
Global mean0.310.31identical: invariant
Peak cell after 2 x 2 max pool, 1-pixel shiftsame in 42 of 72 shiftsinvariant only sometimes

A 2 x 2 pool forgives a 1-pixel shift only when the peak stays inside the same window (58% of positions here): pooling gives local, partial invariance; a global read-out gives full invariance.

Synthetic 11 x 11 binary images and a hand-set template filter; maps from the same conv2d code as the calculator (no padding, stride 1). Every count in the table is computed.

Equivariance is a property of the feature map, invariance of what you read from it. Local pooling sits in between.

  • The output is a Feature map; the filter's weights are learned, so the network finds which small patterns reduce the loss.
  • Output size per axis follows from five numbers (input, kernel, padding, stride, dilation); the parameter count from kernel area, input channels and filters (Parameter sharing), never from the image size.
  • A convolution is shift-equivariant; Pooling adds invariance. Stacking layers widens the Receptive field.
  • The same arithmetic runs on one axis for vibration or ECG. With little labelled data, teams reuse pretrained filters (Transfer learning, Day 40).

Assumptions

  • Locality - neighbouring values in the array are related (pixels, adjacent time samples). A table with arbitrary column order breaks this.

  • Stationarity of patterns - the same pattern means the same thing wherever it occurs. If position matters (a label in a fixed corner, time of day), supply it explicitly.

  • Enough data for the task, or a pretrained model whose domain and preprocessing match yours.

Sign in to record this block.

Application and workplace decision · 10 min

The engineer's layer: input 28 x 28 x 1, 8 filters of 5 x 5, stride 2, padding 1. By hand, write:

  • (a) the output height and width;
  • (b) the learned weights including biases;
  • (c) the weights of a dense layer that produced the same number of outputs from the flattened input.

Then run the code, predicting first which output columns light up:

  • a from-scratch convolution (libraries compute cross-correlation; the kernel is not flipped);
  • the vertical-edge filter on a 6 x 6 image whose right half is bright;
  • the shape formula for four settings, and the parameter comparison.

Python

import numpy as np

def out_size(n, k, p=0, s=1, d=1):
    """Output length of one axis: floor((n + 2p - d(k-1) - 1) / s) + 1."""
    return (n + 2 * p - d * (k - 1) - 1) // s + 1

def conv2d(x, w, p=0, s=1, d=1):
    """Single-channel cross-correlation with padding, stride and dilation."""
    x = np.pad(x, p)
    k = w.shape[0]
    n_out = out_size(x.shape[0], k, 0, s, d)
    out = np.zeros((n_out, n_out))
    for i in range(n_out):
        for j in range(n_out):
            span = d * (k - 1) + 1
            patch = x[i * s : i * s + span : d, j * s : j * s + span : d]
            out[i, j] = (patch * w).sum()
    return out

img = np.zeros((6, 6)); img[:, 3:] = 1.0          # dark left half, bright right half
edge = np.array([[-1, 0, 1]] * 3, dtype=float)    # vertical-edge detector
resp = conv2d(img, edge)
print("response shape", resp.shape, "row 0:", resp[0])

x = np.random.default_rng(0).normal(size=(28, 28))
for p, s, d in [(0, 1, 1), (1, 2, 1), (2, 1, 1), (0, 1, 2)]:
    got = conv2d(x, np.ones((5, 5)), p, s, d).shape[0]
    assert got == out_size(28, 5, p, s, d)
    print(f"28x28, k=5, pad={p}, stride={s}, dilation={d} -> {got}x{got}")

filters, k, c_in = 8, 5, 1
side = out_size(28, k, 1, 2)
dense = 28 * 28 * (side * side * filters) + side * side * filters
print(f"conv params: {filters * (k * k * c_in) + filters}; dense with the same outputs: {dense:,}")

This code is executed in the repository's checks (pnpm lesson:py / lesson:sql). It is not run on this site; copy it into your notebook.

Sign in to record this block.

Mini-assessment · 8 min

Your first answer is your evidence. You can re-answer later in practice mode; that is labelled practice.

Question 1 · easy

A convolution layer has 8 filters of 5 x 5 on a one-channel image and 208 weights in total. The input image grows from 28 x 28 to 224 x 224 pixels. How many weights does this layer have afterwards?
Options

How confident are you? (optional, used for calibration)

Question 2 · medium

A 1-D convolution runs over a window of 32 samples with kernel size 3, dilation 2, stride 2 and no padding. How many output positions does it produce?
output positions

How confident are you? (optional, used for calibration)

Question 3 · hard

A team classifies 2,000-sample vibration windows (1 kHz) with three stride-1 Conv1d layers of kernel size 5, no pooling, followed by global average pooling. An engineer claims that the model "sees" the 200-sample rotation cycle of the shaft because the filters are shared across the whole window. Which statement is correct?
Options

How confident are you? (optional, used for calibration)

Question 4 · medium

The weld-seam classifier takes 28 x 28 x 1 crops through 8 filters of 5 x 5 (stride 2, padding 1), then a 2 x 2 max pool with stride 2, then flattens the result into a dense layer with 12 outputs (weights and biases). How many parameters does that dense layer have?
parameters

How confident are you? (optional, used for calibration)

Question 5 · hard

A quality team classifies bracket images as good if the mounting hole is in the upper half of the part and defective if it is in the lower half; the hole looks the same in both cases. The model is a stack of convolutions followed by global max pooling and a linear head, and it performs close to chance. A toy check with an 8 x 8 image and a vertical-edge filter gives a feature map whose maximum is 3.0 whether the edge is at column 2 or at column 5; only the position of the maximum differs. What is the best reading and the first change?
Options

How confident are you? (optional, used for calibration)

Sign in to complete lessons and keep your progress.

Technical depth · optional · 29 min in total

Open only what you need. Nothing here is required to complete the day; the assessment covers the core above.

The layer, defined3 minThe exact equation one output cell computes, every symbol explained, and the two assumptions written as properties of that equation.
One output cell, defined (stride 1, no padding) Formula
y[i,j]  =  b + ∑u=1k∑v=1kw[u,v]  x[i+u−1,  j+v−1]y[i,j] \;=\; b \,+\, \sum_{u=1}^{k}\sum_{v=1}^{k} w[u,v]\; x[i+u-1,\; j+v-1]
y[i,j]y[i,j]
one cell of the feature map: the response of the filter at position (i, j)
w[u,v]w[u,v]
the k x k learned weights - the SAME w for every (i, j), which is the sharing
x[i+u−1,j+v−1]x[i+u-1, j+v-1]
the input patch under the window; locality: nothing outside it enters y[i,j]
bb
one learned bias per filter

A textbook convolution flips the kernel first; deep-learning libraries skip the flip (cross-correlation) because the weights are learned either way. Stride, padding and dilation only change which patches the window visits.

The equation, read as four properties Overview
  • Learned

    The k x k weights w and one bias b per filter; nothing else.

  • Fixed before training

    k, stride, padding, dilation and the number of filters: design choices.

  • Locality

    y[i,j] reads one k x k patch; a pixel outside the window cannot change it.

  • Stationarity

    The same w at every (i, j): a pattern is asserted to mean the same anywhere.

Cost follows from the same equation: one cell takes k x k x c_in multiply-adds; a full layer takes that times output positions times filters.

The sliding dot product, by hand4 minA 5 x 5 input pushed through a 3 x 3 filter cell by cell, the weight sharing counted, and the 6 x 6 edge example as a second pass.
The sliding dot product on a 5 x 5 input, cell by cell Sequence
  1. The input

    5 x 5, all 0 except column 3, which is 1: a one-pixel bright stripe (think a scratch on a part)

  2. The filter

    3 x 3 vertical-edge detector: every row is (-1, 0, 1); 9 weights, bias 0

  3. Window at column 1

    Covers columns 1-3. Each row: -1 x 0 + 0 x 0 + 1 x 1 = 1; three rows sum to +3

  4. Window at column 2

    Covers columns 2-4: the stripe sits under the 0 tap, so each row gives 0

  5. Window at column 3

    Covers columns 3-5: bright left, dark right: -1 x 1 + 0 + 1 x 0 = -1 per row, so -3

  6. The feature map

    3 x 3, every row (+3, 0, -3): position kept, sign = direction of the edge

  7. Sharing, counted

    9 weights reused at all 9 positions (81 multiplies); a dense 25-to-9 layer needs 225 weights

Verified in numpy: each output row is (3, 0, -3), and out_size(5, 3) = 3. Redo one window on paper before moving on.

A vertical-edge filter on a 6 x 6 image (left half 0, right half 1) Sequence
  1. The filter

    Each of its 3 rows is (-1, 0, 1): right column minus left column

  2. Columns 1-3 (all dark)

    Every pixel 0: response 0

  3. Columns 2-4

    Left column 0, right column 1, three rows: 3 x (0 x -1 + 1 x 1) = 3

  4. Columns 3-5

    Left column still dark, right column bright: response 3

  5. Columns 4-6 (all bright)

    1 x (-1) + 1 x 1 per row: response 0

Row of the feature map: (0, 3, 3, 0). The filter fires only where dark meets bright, and the 6-wide input gives 4 positions.

The output-size formula, derived3 minCount where windows can start and the full formula with padding, stride and dilation falls out; then four settings checked by the code.
Where the output-size formula comes from Sequence
  1. Pad

    p zeros on EACH side: the padded axis has n + 2p positions, indexed 0 to n + 2p - 1

  2. Span

    k taps with dilation d cover d(k - 1) + 1 positions (3 taps, d = 2: positions t, t+2, t+4)

  3. Starts

    Stride s means windows start at t = 0, s, 2s, ...

  4. Last fit

    The window at t ends at t + d(k - 1), which must be <= n + 2p - 1, so t_max = n + 2p - d(k - 1) - 1

  5. Count

    Starts 0..t_max in steps of s: floor(t_max / s) + 1. The floor drops a window that does not fit

  6. Check

    n = 28, k = 5, p = 1, s = 2: floor((28 + 2 - 4 - 1) / 2) + 1 = floor(12.5) + 1 = 13

This is the full formula from the size-eq figure, derived rather than memorised; the code asserts it for four settings.

The shape formula on 28 x 28 with a 5 x 5 kernel (checked by the code) Table
PaddingStrideDilationOutput
01124 x 24
12113 x 13
21128 x 28 ('same' size)
01220 x 20 (kernel spans 9)
Receptive field, derived and effective4 minHow far one output can see, three ways to widen it, the derivation, and why the usable field is smaller than the formula.
How far one output sees - three ways to stack 3-tap layers Illustration
3 layers, k = 3, stride 1input 17layer 1: 15layer 2: 13layer 3: 11sees 7 samples

Each stride-1 layer adds k - 1 = 2 samples: 7 after three layers.

Dilation 1, 2, 4 (stride 1)input 17layer 1: 15layer 2: 11layer 3: 3sees 15 samples

Same 3 weights per layer, gaps between the taps: 15 samples. No stride, so the outputs stay dense (with padding they keep the input length).

Stride 2 in every layerinput 17layer 1: 8layer 2: 3layer 3: 1sees 15 samples

Also 15 samples, but the output shrinks to 1 position: resolution is spent to buy context.

283212851212468Receptive field vs depth (k = 3)1 stride-1 layers: 3 samples1 dilated or strided layers: 3 samples2 stride-1 layers: 5 samples2 dilated or strided layers: 7 samples3 stride-1 layers: 7 samples3 dilated or strided layers: 15 samples4 stride-1 layers: 9 samples4 dilated or strided layers: 31 samples5 stride-1 layers: 11 samples5 dilated or strided layers: 63 samples6 stride-1 layers: 13 samples6 dilated or strided layers: 127 samples7 stride-1 layers: 15 samples7 dilated or strided layers: 255 samples8 stride-1 layers: 17 samples8 dilated or strided layers: 511 samples3 layers: 7 vs 15stride 1: 17 at 8dilated or strided: 511 at 8layerssamples seen

Stride-1 stacks grow linearly; doubling dilation or stride 2 grow exponentially (both give the same numbers). Log scale.

Index arithmetic on a 17-sample input, no padding; the highlighted cone is found by following every tap down the stack; it matches 1 + sum of (k - 1) x dilation x earlier strides. This is the theoretical field; trained networks weight its centre most.

Depth adds context linearly; dilation and stride add it exponentially. Stride (and pooling) pays with resolution; dilation keeps resolution but skips the samples between its taps.

Receptive field of stacked stride-1 layers Formula
r=1+∑idi(ki−1)r = 1 + \sum_{i} d_i (k_i - 1)
kik_i
kernel size of layer i
did_i
dilation of layer i

With strides or pooling the growth is faster; each later layer's step is multiplied by the earlier strides.

Why the field grows by d(k - 1) per layer Sequence
  1. One layer

    An output reads k neighbouring inputs: r = k

  2. Add a stride-1 layer

    Its k taps read outputs one input apart, each already r wide; together they add d(k - 1) new inputs

  3. Add stride or pooling below

    Neighbouring taps now sit j inputs apart (j = product of earlier strides): the layer adds d(k - 1) x j

  4. Check

    Three 3 x 3 layers: 1 + 2 + 2 + 2 = 7. Dilation 1, 2, 4: 1 + 2 + 4 + 8 = 15

Induction on depth; the receptive-field demo above finds the same numbers by following every tap down the stack.

How far one output can see Table
StackReceptive field
One 3 x 3 layer3
Two 3 x 3 layers5
Three 3 x 3 layers7
Two 5 x 5 layers9
Three 3 x 3 layers, dilation 1, 2, 415

A pattern wider than the receptive field cannot be seen by one output, however the weights are shared.

The usable field is narrower: edge taps carry little (% of one output) Chart
  • input -4 (edge)1.2 %
  • -34.9 %
  • -212.3 %
  • -119.8 %
  • 0 (centre)23.5 %
  • +119.8 %
  • +212.3 %
  • +34.9 %
  • +4 (edge)1.2 %

Four stacked 3-tap averaging layers (theoretical field 9), computed: each input's share of one output. The central 5 inputs carry 88%, the two edges 2.5%. Luo et al. (2017) found the effective field of real networks roughly Gaussian in the same way, so the pattern you need (a whole cycle, a whole bracket) must sit well inside the field, not at its edge.

Channels, 1 x 1 and depthwise-separable layersadvanced4 minWhere weights and multiply-adds really sit, and the cheap layer types that change the balance.
Parameters of a convolution layer with channels Formula
N=f⋅(khkw cin)+f,3×3,  64→128:  128⋅576+128=73,856N = f \cdot (k_h k_w \, c_{in}) + f, \qquad 3 \times 3, \; 64 \to 128: \; 128 \cdot 576 + 128 = 73{,}856
ff
number of filters = output channels
khkwcink_h k_w c_{in}
each filter spans all input channels
+f+ f
one bias per filter

Image size never appears. PyTorch's Conv2d stores the weight as (out channels, in channels, kh, kw).

Three conv layers, 64 to 128 channels: what each looks at and pays Comparison
CriterionStandard 3 x 31 x 1 convDepthwise 3 x 3, then 1 x 1
Looks atA 3 x 3 patch across all 64 channels at onceOne position only: no spatial contextEach channel's own 3 x 3 patch (groups = 64)
Mixes channelsYes, inside every filterYes: it only remixes channels per positionOnly in the 1 x 1 step after it
Weights (with biases)73,8568,320; the cheap way to shrink or widen channels640 + 8,320 = 8,960: about 8x fewer, same for multiply-adds

The standard layer does spatial filtering and channel mixing in one step; depthwise-separable splits them, which is where the saving comes from.

Where weights and compute go (64 to 128 channels, 56 x 56 map) Table
LayerWeightsMultiply-adds per image
Standard 3 x 3 conv73,856231.2 million
Depthwise 3 x 3 + pointwise 1 x 18,96027.5 million
1 x 1 conv, 64 to 16 (bottleneck)1,0403.2 million
Two 3 x 3 convs (64 to 64) vs one 5 x 573,728 vs 102,400same 5 x 5 receptive field
Flatten 128 x 56 x 56, dense to 256102,760,704same as the weights
Global average pool, dense to 25633,024same as the weights

3 x 3 kernels, stride 1, padding 1; computed in Python, weights include biases. Convolutions hold the compute; a flatten-to-dense head holds the parameters. Two stacked 3 x 3 layers see as far as one 5 x 5 with fewer weights and an extra nonlinearity between them.

Pooling, equivariance and when not to convolve5 minWhat each pooling choice throws away, the equivariance-invariance distinction, the one-axis case, and the neighbours a CNN competes with.
From equivariant to invariant: what each stage keeps of position Comparison
CriterionConvolutionMax pool 2 x 2Average poolGlobal pool
ComputesShared weights at every positionLargest value in each windowMean of each windowOne max or mean per channel over the whole map
If the pattern movesThe map moves with it: equivariant, position keptUnchanged only while the peak stays in its windowSame partial forgiveness; peaks are dilutedIdentical value: invariant, all position lost
Typical useFind the pattern wherever it isHalve height and width between conv blocksSmoother downsamplingBefore the head; works for any input size

Equivariance belongs to the feature map, invariance to what you read from it. If position decides the label, global pooling removes exactly the information you need.

The same idea on one axis (vibration, ECG, electricity load) Diagram
Signal windown samples, 1channelConv1d, kernel kn - k + 1 responsesGlobal max poolis the pattern anywhere?Linear headclass score
  1. Signal windown samples, 1 channel
  2. Conv1d, kernel kn - k + 1 responses
  3. Global max poolis the pattern anywhere?
  4. Linear headclass score

The hands-on lab builds exactly this with a hand-set filter and shows what the pooled feature buys over raw samples.

When a convolution is the right tool Map

Unordered features

Column order is arbitrary; a filter has nothing local to find. Use trees or linear models.

Convolution fits

Images, audio, sensor traces, spectrograms.

Tabular data

Neither holds; tree ensembles are the default (Day 12).

Conv plus position

Local patterns whose meaning depends on where they are: add coordinates, avoid global pooling.

A CNN and its neighbours on images or signals Comparison
CriterionCNNMLP on raw pixelsHand-built features + GBMAttention over patches
Built-in assumptionLocality and the same pattern anywhereNone: every pixel has its own weightWhatever you engineerAny pair may interact; weak locality prior
Labelled data neededModerate; pretrained backbones helpLargeSmallUsually large, or pretrained at scale
Where it breaksNo locality, or position carries the labelA one-pixel shift changes every inputPatterns nobody thought to engineerCost grows with patches squared (Day 39)

The architecture is a bet about the data. Small labelled sets favour the strongest prior that is true for your data.

The knobs, and when a CNN fails on new dataadvanced4 minPyTorch Conv2d defaults and what each trades, then a decision flow that checks the pipeline and the split before blaming the architecture.
Conv2d knobs: PyTorch defaults and what each trades Table
KnobDefaultTrade-off
padding0; 'same' allowed only with stride 1Keeps border positions and map size vs a shrinking map
stride1Less compute and more context per layer vs lost resolution
dilation1Wider view with no extra weights vs skipped samples between taps
groups1 (groups = in channels: depthwise)Far fewer weights vs less mixing across channels
biasTrueRedundant right before batch norm, which subtracts the mean
inituniform(-sqrt(k), sqrt(k)), k = groups / (c_in kh kw)Scale shrinks with fan-in; many recipes re-initialise (He init for ReLU)

Defaults read from the PyTorch 2.14 Conv2d documentation page. Weight shape is (out channels, in channels / groups, kh, kw).

A CNN that validates well and fails on next week's images Diagram
Val good, newweek bad97% random split,81% next weekSamepreprocessing?resize, crop, normSplit by seamor week?near-duplicatesFew labels?Train top blocksaugment only realvariationFix the pipelineassert one batch end toendRe-split andre-measurethe old score was a leakFrozen backbone +headplus a classicalbaselineyesnoyesnoyesno
  1. Val good, new week bad97% random split, 81% next week
  2. Same preprocessing?resize, crop, norm
    • yes: to Split by seam or week?
  3. Fix the pipelineassert one batch end to end
  4. Split by seam or week?near-duplicates
    • yes: to Few labels?
  5. Re-split and re-measurethe old score was a leak
  6. Few labels?
    • no: to Train top blocks
  7. Frozen backbone + headplus a classical baseline
  8. Train top blocksaugment only real variation

Check the cheap explanations (pipeline, split) before the architecture; a deeper network trained on the same leak only looks better.

What a staff-level answer addsadvanced2 minThe nuances that separate a textbook answer from an experienced one, and the common misreadings.
The textbook answer and what a staff-level answer adds Comparison
CriterionCommon readingStaff-level reading
Weight sharingA compression trickA prior: a pattern means the same thing everywhere
Where the budget sitsThe convolutions hold everythingConvs hold the compute; a flatten-to-dense head holds the parameters
Shift behaviour'CNNs are translation invariant'Equivariant; pooling adds invariance, stride breaks it for odd shifts
Seeing further'A bigger kernel sees more'Depth, dilation and stride usually widen the field more cheaply
Filter picturesShow what the decision relies onSuggest what a layer responds to; not evidence of reliance (Day 17)

Stride breaks invariance for any shift that is not a multiple of the stride: the sampled grid lands on different positions.

Memory anchor

One small filter, reused everywhere: the shape comes from five numbers, the weights from training.

Useful for

  • Checking a colleague's architecture sketch in minutes - shape and parameter arithmetic exposes impossible or wasteful layers before any training.

  • Choosing between a convolution, a dense layer and engineered features for data that has a spatial or temporal order.

  • Deciding whether a pretrained image backbone is a reasonable feature extractor for a small labelled set (defect photos on a line, retail shelf images, scanned insurance claim forms).

Not suitable for

  • Tabular rows with arbitrary column order - there is no locality for a filter to exploit; a gradient-boosted model or MLP is the first candidate.

  • Judging what a trained network relies on - filter pictures are suggestive, not evidence of the decision logic.

  • Choosing an architecture without a baseline; at orientation depth this lesson gives you the arithmetic, not a training recipe.

Traps

Off-by-one shapes that nothing catches

Padding is added on both sides and the formula takes a floor. A mistake of one position per layer compounds, and it surfaces either as a shape error at the flatten-to-dense step or, worse, silently when adaptive pooling hides it.

How to avoid it

Write the shape per layer on paper, then assert it with one dummy forward pass before training.

Using a convolution where there is no locality

A Conv1d across 200 tabular columns treats neighbouring columns as related. Reordering the columns changes the model's behaviour although no information changed, which is a sign the inductive bias does not match the data.

How to avoid it

Ask what "neighbour" means in your data; shuffle the column order once and see whether the score moves.

Senior reasoning

A senior treats a convolution as a bet about the data: that nearby values are related and that patterns repeat. They check that bet cheaply (shape arithmetic, a shuffled-column or shifted-input test), count where the parameters really sit (usually the flatten-to-dense layer), and prefer a frozen pretrained extractor plus a simple head when labels are scarce. They also ask for the classical baseline, which for many sensor tasks is hand-built features plus a gradient-boosted model.

Interview question

"Why do convolutional networks need far fewer parameters than dense ones on images, and when would you not use one?"

Recall cards scheduled after completion

  • Write the output length of a convolution axis and the parameter count of a conv layer.
  • Is a convolution invariant or equivariant to shifting the input, and what adds invariance?

Reviewed on a 1 · 3 · 7 · 14 · 30-day schedule; a miss returns the card to 1 day.

References

Optional sections

Deep dive · 10 min

Receptive field with stride. For a stack of layers, the receptive field grows by (ki−1)⋅ji(k_i - 1) \cdot j_i, where jij_i is the "jump" (the product of earlier strides) between neighbouring outputs of layer ii. Strides and pooling therefore widen the view faster than stride-1 layers, at the price of resolution.

Channels and 1 x 1 convolutions. A real layer mixes channels: each filter has shape k×k×cink \times k \times c_{in} and produces one map. A 1×11 \times 1 convolution has k=1k = 1 and so no spatial context; it recombines channels at each position, which is a cheap way to change channel counts.

Where the parameters go. A 3 x 3 convolution from 64 to 128 channels has 128⋅(9⋅64)+128=73,856128 \cdot (9 \cdot 64) + 128 = 73,856 weights. Flattening a 128×56×56128 \times 56 \times 56 map into 256 dense units needs about 102.8 million. In small image networks the flatten-to-dense layer, not the convolutions, holds most parameters; global average pooling before the head removes the dependence on the input resolution.

Non-image data. A 1-D convolution suits sensor windows, ECG, audio and short click sequences where the pattern can occur at any time. For a daily demand series, a convolution over the last 28 days is one of several window features you could build and has to beat lag features in a GBM (Day 24) and the strong baselines of Day 27 to justify itself.

Hands-on lab: A hand-built 1-D filter and global max pooling on shifted bursts (synthetic) · 15 min

Synthetic vibration windows: half contain a short damped burst at a random position, half are noise. A logistic regression on the raw samples cannot use position-dependent evidence; one filter matched to the burst, followed by global max pooling, turns the problem into a single feature. In a CNN the filter would be learned; here it is set by hand so you can see the mechanism. Change the burst amplitude from 2.0 to 1.0 and describe how accuracy moves, then explain why this is still not a claim about real bearings.

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

rng = np.random.default_rng(36)
n, length, width = 600, 128, 9
# short damped "knock" shape (synthetic)
burst = np.hanning(width) * np.sin(np.linspace(0, 3 * np.pi, width))

def make(label, start=None):
    x = rng.normal(0, 0.5, length)
    if label:
        start = rng.integers(0, length - width) if start is None else start
        x[start:start + width] += 2.0 * burst
    return x

y = rng.integers(0, 2, n)
X = np.stack([make(l) for l in y])
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)

raw = LogisticRegression(max_iter=2000).fit(Xtr, ytr)
print("raw-signal logistic regression accuracy:", round(raw.score(Xte, yte), 3))

def conv_maxpool(X, w):
    windows = np.lib.stride_tricks.sliding_window_view(X, len(w), axis=1)  # (n, length-k+1, k)
    resp = windows @ w                                                     # (n, length-k+1)
    return resp.max(axis=1, keepdims=True), resp.shape

feat_tr, shape = conv_maxpool(Xtr, burst)
feat_te, _ = conv_maxpool(Xte, burst)
print("conv response length per signal:", shape[1], "= 128 - 9 + 1")
clf = LogisticRegression().fit(feat_tr, ytr)
print("conv + global max pool + logistic regression accuracy:", round(clf.score(feat_te, yte), 3))

a = make(1, start=30)
b = np.roll(a, 20)                                                         # same burst, shifted
ra = conv_maxpool(a[None], burst)[0][0, 0]
rb = conv_maxpool(b[None], burst)[0][0, 0]
print("max response, original vs shifted by 20:", round(float(ra), 2), round(float(rb), 2))
Interview practice · 10 min

A teammate at a bank wants a CNN for 150 tabular features about a personal loan (income, utilisation, tenure, ...). How do you respond?

Content version 6 · last verified 2026-10-13 · depends on numpy, scikit-learn