Part III · Ch. 8 — Seeing Machines

Part III · Chapter 8 of 13

Seeing Machines

CNNs: convolution, filters, and how networks see


Review prerequisite concepts
  • the dot product — multiply matching numbers and add — as a one-number agreement score
  • a neuron is a weighted sum of its inputs, plus a bias, squashed by an activation
  • activation functions, especially ReLU, that squash a score
  • the training loop: forward, grade, backprop, step — and overfitting

Why a picture breaks a plain network

A photo is a grid of numbers: one brightness per grayscale pixel, three channel values per color pixel. Could we feed that grid into the plain stack from the neuron and ask “cat or dog?” We could, but the wiring would ignore useful structure and require many weights.

On the left, a 5 by 5 grid of pixels: the three left columns are mint-tinted and hold the number 1 (bright), the two right columns are dark and hold 0, with a thin mint vertical marker labeled 'edge' between the third and fourth columns and a note 'nearby pixels belong together'. On the right, the same 25 numbers unrolled into a single long horizontal strip, row by row. Two pixels that were vertically adjacent in the grid (both ringed mint) now sit five cells apart in the strip, connected by a curved amber leader. A red label reads 'neighbors torn apart'.
A row-by-row list retains pixel values but no longer expresses both horizontal and vertical neighbors as a grid.

Flatten a $100\times100$ grayscale image into a list and it has $10{,}000$ inputs. Connect each to $10{,}000$ hidden neurons and the first layer needs $10{,}000\times10{,}000=100$ million weights. That is the matrix-times-vector count, before any later layers.

Dense first layerCost
$100\times100$ grayscale image$10{,}000$ input values
$10{,}000$ hidden neurons$100$ million input-to-neuron weights

The parameter count belongs to this particular fully connected layer, not to images in general. More pixels mean more inputs, and more hidden neurons mean more destinations for those inputs. Every pair gets a separate weight. A CNN changes those connections before training begins, so we do not have to learn from examples that neighboring pixels often form the same edge.

Flattening also stops expressing the grid’s neighborhood directly. Vertically adjacent pixels become far apart in the list, while the end of one row sits beside the start of the next. Their values are retained, so the image can be reconstructed if we know the layout; what a plain dense layer lacks is wiring that treats nearby pixels as related.

Plain dense wiringConvolution wiring
Every output reads every pixelEach output reads a local patch
Separate weights at each outputThe same detector weights at every position

Instead, slide a small detector over local patches and reuse its weights everywhere. That operation is convolution; a network built around these filters is a convolutional neural network, or CNN. We will calculate one detector and try it.

WiringLocal patches?Shared weights?
Dense layerNoNo
Separate local detectorsYesNo
ConvolutionYesYes

Both choices earn their place. Local patches preserve the neighborhood we want the model to use, and reuse keeps the number of learned weights small. Restricting attention to a patch alone would not make the weights shared; we could still give every patch its own separate detector. Convolution does both.

Sliding a filter across the picture

Our $5\times5$ image has identical rows $[1,1,1,0,0]$: bright on the left, dark on the right. The vertical edge lies between columns three and four.

Position in a filter rowWeightPurpose
Left$1$Add brightness
Middle$0$Ignore brightness
Right$-1$Subtract brightness

The weights $1,0,-1$ were chosen to compare left with right. The zero ignores the middle column, and the negative weight subtracts the right column instead of treating all brightness as evidence. A uniform bright patch therefore cancels. Reversing the contrast would reverse the signed response; this detector is directional, not a general test for every kind of edge.

Three grids left to right. Left: the 5 by 5 image, mint-tinted 1s in the first three columns and dark 0s in the last two, with a bold mint 3 by 3 outline over a patch straddling the edge, labeled 'filter here'. Middle: the 3 by 3 filter K with violet entries 1, 0, minus 1 repeated down three rows, and beneath it the arithmetic '(1·1)+(1·0)+(0·−1) ×3 rows' and '= 1+1+1 = 3'. Right: the 3 by 3 feature map with values 0, 3, 3 in every row, cells filled mint in proportion to their value; the cell matching the highlighted window is ringed amber. A muted arrow from the filter to that cell reads 'one dot product → one cell'. A bottom strip reads 'slide → dot product → squash, at every position'.
The same nine multiplications give $3$ over an edge-straddling patch and $0$ over a flat patch.

A filter, also called a kernel, is a grid of weights. Our $3\times3$ filter $\mathbf{K}$ finds that edge:

$$\mathbf{K} = \begin{bmatrix} 1 & 0 & -1 \\ 1 & 0 & -1 \\ 1 & 0 & -1 \end{bmatrix}$$

In words: add the patch’s left column and subtract its right column; matching sides cancel. These nine weights form a local weighted sum like the one inside a neuron.

ColumnRole
LeftAdd brightness
MiddleIgnore
RightSubtract brightness

Worked example: one edge response

Place it over the edge-straddling patch with three rows $[1,1,0]$. Matching-cell multiplication and addition is the dot product arranged in a square. Call the middle output cell $S_{1,1}$, counting rows and columns from zero:

$$\begin{aligned} S_{1,1} &= \big[(1)(1)+(1)(0)+(0)(-1)\big] \\ &\quad + \big[(1)(1)+(1)(0)+(0)(-1)\big] \\ &\quad + \big[(1)(1)+(1)(0)+(0)(-1)\big] \\ &= 1 + 1 + 1 = 3 \end{aligned}$$

In words: multiply matching pixels and weights, then sum; each of three identical rows contributes 1, giving 3.

Row totalsPatch total
$1+1+1$$3$
  1. Place: cover three rows and three columns.
  2. Multiply and sum: compute one raw response.
  3. Move: shift one pixel and use the identical weights.

Worked example: the complete feature map

Slide one position at a time wherever the filter fits. Each sum fills a feature map:

$$\begin{bmatrix} 0 & 3 & 3 \\ 0 & 3 & 3 \\ 0 & 3 & 3 \end{bmatrix}$$

In words: edge-straddling patches give $3$; flat-bright patches give $0$, since each all-ones row computes $(1)(1)+(1)(0)+(1)(-1)=0$. Three such rows still sum to zero.

Window columnsResponse
1–3: flat$0$
2–4 or 3–5: edge$3$

The map shrinks because the filter must fit inside the image; adding border zeros can preserve its size. A CNN then applies an activation, usually ReLU: keep positive values, replace negatives with zero. Our displayed maps are raw sums; here ReLU leaves them unchanged.

A filter is a feature detector

Raw responseAfter ReLUWhat remains
$-3$$0$Negative response removed
$0$$0$No response
$3$$3$Positive response preserved

A filter detects a particular pattern. On the same image, compare a horizontal-edge filter $\mathbf{K}_{\text{horizontal}}$:

The two filters see identical pixels. Only their weight arrangements differ, which lets us isolate what “feature” means here: a particular measurement of the same patch. A zero horizontal response does not say the image is empty or unimportant. It says this detector found no top-versus-bottom contrast. Another detector can give a strong response at the same location.

On the far left, the shared 5 by 5 image (mint 1s in the first three columns, dark 0s in the last two). Top row: a violet 3 by 3 vertical-edge filter, an arrow, and its feature map showing a bright mint band of 3s, tagged 'edge found'. Bottom row: a violet 3 by 3 horizontal-edge filter, an arrow, and its feature map of all-dark zeros, tagged 'nothing here'. A note reads 'a filter detects ONE pattern — this image has vertical edges, not horizontal ones'.
A vertical-edge filter responds to this image while a horizontal-edge filter gives zeros at every position.
$$\mathbf{K}_{\text{horizontal}} = \begin{bmatrix} 1 & 1 & 1 \\ 0 & 0 & 0 \\ -1 & -1 & -1 \end{bmatrix}$$

In words: subtract the bottom row from the top; our image’s rows match, so every output is $0$. The horizontal feature map is all zeros.

The weights are the pattern the filter detects. We hand-chose $[[1,0,-1],\dots]$ to make arithmetic readable. A learned CNN starts with random filter weights and adjusts them through gradient descent. No edge labels are required: filters change when doing so helps the network’s training objective.

FilterMap on this image
Vertical contrastRows of $[0,3,3]$
Horizontal contrastAll zeros

Keep the stack’s two axes of variation separate. Moving across a map changes where we look; moving between maps changes which pattern we measure. A later layer can combine responses across these maps as well as across nearby positions. Its input is already a description of local patterns, so it can build a richer description from them.

Move through…What changesWhat is shared
One feature mapImage positionFilter weights
Different feature mapsThe filterInput image
Later layersCombined patternsWeights within each new map

A layer can use $64$ filters on the same image, producing $64$ maps. Different maps respond to different patterns and retain where those responses occur. That is what “learning features” means: the representation changes from raw pixel values into measurements that help the task.

Sharing the weights

Layer depthEvidence availablePossible use
EarlyLocal brightness contrastsEdges
MiddleCombinations of edgesShapes and textures
LaterCombinations across larger regionsEvidence for objects

Now count the savings. Turn our $5\times5$ image, $25$ inputs, into a $3\times3$ map, $9$ outputs. A dense layer connects every input to every output; one convolution slides a single nine-weight filter. We count weights here, omitting biases:

The three snapshots use different patches from the same image. They are three evaluations of one filter. If training changes the top-left weight in the single weight box, that change applies at every placement, and each output still has its own value because each placement multiplies the shared weights by different input pixels.

One 3 by 3 filter beside three overlapping patches from the image: columns 1–3, 2–4, and 3–5 yield sums 0, 3, and 3.
Three overlapping windows use the same nine weights: sums 0, 3, and 3.
$$\text{dense: } 25 \times 9 = 225 \text{ weights} \qquad \text{convolution: } 9 \text{ weights (shared)}$$

In words: dense wiring needs $225$ weights; the convolution needs nine, reused at all nine positions. A larger image adds positions to evaluate, while the same single-channel filter still has nine weights.

Weight sharing is that reuse. A vertical edge has the same local pattern at the top-left or bottom-right, so one learned detector should work in both places. Sharing also means every training location helps adjust the same weights, rather than teaching separate detectors for separate locations.

ChangeWhat follows
Move the windowRead different pixels with the same weights
Train one filter weightEvery position uses that updated weight

Fewer weights does not mean free computation. A larger image supplies more windows, so the detector must be evaluated more times even though its parameter count stays fixed. Likewise, using more filters adds both weights and output maps. The saving concerns shared parameters; the image size still affects the work of making a prediction.

Translation equivariance means shifting the input shifts the response map correspondingly, away from boundary effects. It does not mean every shifted image gives an identical final classification. Here we share weights across space; the next chapter shares them across time.

EquivarianceInvariance
Move the input; the response movesMove the input; the output stays unchanged

Pooling, and what depth adds

Pooling shrinks a feature map by summarizing small blocks. Max pooling keeps the strongest response from each block; our example uses non-overlapping $2\times2$ blocks:

On the left, a 4 by 4 feature map with values 0 3 1 0 / 2 3 0 1 / 0 0 4 2 / 1 0 1 3, cells filled mint by value, with four colored 2 by 2 block outlines (mint, amber, violet, blue). A muted arrow labeled '2×2 max pool — keep the loudest' with two annotations 'max(0,3,2,3)=3' and 'max(4,2,1,3)=4'. On the right, a 2 by 2 pooled output with values 3 1 / 1 4, each cell outlined in the color of its source block.
Each $2\times2$ block contributes its maximum, reducing the $4\times4$ map to $[[3,1],[1,4]]$.

Worked example: four pooled maxima

$$\text{max-pool} \begin{bmatrix} 0 & 3 & 1 & 0 \\ 2 & 3 & 0 & 1 \\ 0 & 0 & 4 & 2 \\ 1 & 0 & 1 & 3 \end{bmatrix} = \begin{bmatrix} 3 & 1 \\ 1 & 4 \end{bmatrix}$$

In words: keep the maximum of each block: $3$ at top-left, $1$ at top-right, $1$ at bottom-left, and $4$ at bottom-right. Each side halves, leaving a quarter as many values.

BlockValuesMaximum
Top-left$0,3,2,3$$3$
Top-right$1,0,0,1$$1$
Bottom-left$0,0,1,0$$1$
Bottom-right$4,2,1,3$$4$

Pooling reduces later computation and can add tolerance to small shifts within a block. It discards exactly where the strongest response occurred. For recognizing an object that tradeoff can help; for locating a boundary precisely, the discarded position may matter.

  1. Convolve: measure local patterns.
  2. Activate: apply ReLU to each response.
  3. Pool when useful: summarize nearby responses.
  4. Repeat: later filters combine earlier features.

A typical CNN combines convolution, activation, and occasional pooling. Early features can describe edges; later layers combine them into corners, textures, parts, and objects. This is the depth argument: later units reuse earlier computations instead of deriving everything directly from raw pixels.

A deeper unit does not reach directly into every raw pixel. It reads earlier units, which read earlier units in turn, and following those dependencies backward reveals the original region that can influence it. That is why even small filters can collect evidence spread across an object. The edge-to-part stages are the intuition; no learned filter is guaranteed a neat human label.

A left-to-right pipeline of four stages connected by muted arrows. Stage 1 'pixels': a 6 by 6 dot grid roughly suggesting a face. Stage 2 'edges (layer 1)': mint glyphs of short oriented edges, labeled 'simple edges'. Stage 3 'parts (deeper)': amber glyphs of corners, a curve, and a textured patch, labeled 'corners, curves, textures'. Stage 4 'objects (deepest)': violet glyphs of an eye and a whole-face outline, labeled 'parts → whole objects'. Beneath, a double-headed arrow that widens left to right, labeled 'receptive field grows with depth — shallow neurons see details, deep neurons see structure'.
Deeper features combine earlier responses and can depend on larger regions of the original image.

A first-layer unit sees a $3\times3$ patch. A later unit sees a patch of earlier responses, each already depending on several pixels. Its receptive field, the region of the original image that can affect it, grows with depth.

KeepDiscard
Largest response in each blockOther response values and the winner’s exact position

See it move

Watch the $3\times3$ filter cross our $5\times5$ image: each dot product fills one feature-map cell, $0$ on the flat and $3$ on the edge. Additional filters supply additional maps, and later layers combine responses.

Local dot products fill a feature map, and multiple filters build richer representations. (1:05)

The playground lets you move a window over the image, inspect its nine multiply-adds, and edit the filter weights. Changing a weight redraws every output position, so it also demonstrates sharing.

When you edit a filter, compare the highlighted patch calculation with the whole map. The first tells you why one value changed, the second shows the effect of using those weights everywhere. If a new pattern fails to appear, check whether the detector’s shape matches it before assuming the arithmetic is wrong.

Why $1/9$ for blur? There are nine pixels under the filter, so nine equal weights of $1/9$ sum to 1 and produce their average. A constant patch stays constant instead of becoming nine times brighter. The edge filter’s weights sum to zero instead, so it removes a constant background and responds to differences across the patch.

Where you'll meet this

WhereWhat it looks likeChapter link
Images and scansLocal features help recognize digits, objects, or scan patternsBuilding features
Sound spectrogramsNearby time-frequency cells form a gridSequences
Vision transformersAttention mixes information between image patchesAttention
Contact networksNeighbors lie on arbitrary graphs instead of a pixel gridGraph networks

What you now know

  • Convolution preserves local grid structure and shares a small filter across positions.
  • Each feature-map value is a dot product; our edge example produces $0$ or $3$.
  • Training learns filters; multiple filters produce multiple maps.
  • Translation equivariance moves the response with the input.
  • Pooling trades location detail for smaller maps and some shift tolerance.
  • Depth combines features and expands receptive fields.

Where we’re headed. Images share structure across space. A sentence instead unfolds in order: “the dog chased the cat” and “the cat chased the dog” use the same words differently. Remembering machines shares weights across time and asks how a network keeps what came before.