A small filter, slid over an image, detects a local pattern everywhere at once.
Weight sharing + pooling = the right prior for grid data. Built from scratch in NumPy; the mechanism, not a training loop.
An MLP takes an image as a bag of pixels — no notion that pixel 9 sits below pixel 1. Convolution bakes in what we know: patterns are local, and a pattern worth finding in one corner is worth finding everywhere.
One filter = one small pattern detector, reused at every position. 9 weights, not 4,096, and the count never grows with the image.
load_digits — 1,797 handwritten digits, 8×8, intensities 0–16. MNIST's tiny
cousin. Small enough to animate every one of a filter's 36 stops.
The sliding weighted sum, one output pixel:
S(i,j)=m=0∑kh−1n=0∑kw−1I(i+m, j+n)K(m,n)Rectify, then pool the strongest response per block:
ReLU(x)=max(0, x) P(i,j)=0≤a,b<smaxS(i⋅s+a, j⋅s+b)The kernel K has no i,j index — same weights everywhere. That's weight sharing, the whole prior.
One edge filter slides over a handwritten 3. The receptive field is outlined on the input; the feature map fills one pixel per frame; the caption names the computed value.
Values go positive on one flank of a stroke, negative on the other — the two sides of an edge. ReLU keeps one, zeros the other.
Each filter is a specialist — vertical, horizontal, the two diagonals, each with its negation. The ReLU feature maps for one 3:
Same classifier, same split (1,257 train / 540 test). Only the representation changes: 72 conv features vs. 64 raw pixels.
Conv features 0.9796 vs. raw pixels 0.9722 — a narrow, real win. ReLU discards 52% of responses: sparse, oriented features worth more each.