CNN

A CNN (convolutional neural network) is a neural network that finds patterns in images by sliding a small grid of weights — a filter — across every location and scoring how strongly each patch matches. Because the same few weights are reused at every position, a CNN detects a feature anywhere in the image with a tiny fraction of the parameters a dense layer would need.

The right mental model is a stencil. Cut a vertical slot in a card, hold it against every window of a photograph, and ask the same question each time: does the patch behind the stencil line up with the slot? You don't need a new stencil for each position — one card can test the same pattern at each location. The filter is that stencil, and its nine numbers are the shape of the slot.

How a CNN Works

A convolution lays the stencil over a 3×3 patch of the image, multiplies each pixel by the weight sitting on top of it, and adds the nine products into one number. That number is one cell of the feature map — a score for "how much does this patch look like my pattern?" Slide one step right and repeat; at the end of the row, drop down and continue. The filter never hangs off the edge here ("valid" convolution), so a 6×6 image under a 3×3 filter yields a 4×4 feature map. The nine weights are the same at all sixteen positions — that reuse is the entire trick.

Raw convolution scores can be negative: a filter tuned to bright-to-dark edges answers very negative at a dark-to-bright one. ReLU — max(0, x) — clamps every negative score to zero, keeping only the locations that match the stencil's polarity and giving the network its nonlinearity.

Max-pooling then shrinks the map: a 2×2 window steps across it with stride 2, keeping only the largest value in each window, so the 4×4 map becomes 2×2. The exact pixel of the strongest match stops mattering; only "a strong match somewhere in this quadrant" survives. That small dose of position-tolerance, stacked over many layers, is what lets a CNN recognize a cat whether it sits left or right of center.

What you see here is one deliberately tiny layer: a single hand-picked filter, one convolve → ReLU → pool pass. A real CNN learns dozens of filters per layer and stacks many layers, with early ones finding edges and later ones composing them into shapes. The per-layer operation is exactly this one.

CNN Step by Step

The default run sweeps the vertical-edge filter — three rows of [1, 0, −1] — across a 6×6 image holding a bright vertical bar: pixels of value 9 in columns 2–3, rows 1–4, zeros elsewhere. Each row of the filter adds the patch's left pixel and subtracts its right one, so the whole filter measures left brightness minus right brightness.

  1. Position (0, 0). Do this one by hand. The top-left 3×3 patch is [[0, 0, 0], [0, 0, 9], [0, 0, 9]]. Multiply elementwise: the left column hits +1 and contributes 0; the middle column hits 0; the right column hits −1 and contributes −9 twice. Sum: −18. The patch holds a dark-to-bright edge — the mirror image of what the stencil wants — so the score is strongly negative.
  2. Sweep the remaining 15 positions. The completed 4×4 feature map reads, row by row: [−18, −18, 18, 18], [−27, −27, 27, 27], [−27, −27, 27, 27], [−18, −18, 18, 18]. Negative scores trace the bar's left edge, positive ones its right edge, and the magnitudes peak at 27 where all three filter rows sit on full bar.
  3. ReLU. max(0, x) zeroes the left half: the map becomes [0, 0, 18, 18], [0, 0, 27, 27], [0, 0, 27, 27], [0, 0, 18, 18]. Only the edge whose contrast matches the stencil survives.
  4. Max-pool, 2×2, stride 2. The two left windows hold only zeros → 0; the two right windows each contain a 27 → 27. Output: [[0, 27], [0, 27]] — a quarter-size summary saying "strong vertical edge, right-hand side."

On the canvas, the amber read-window glides across the image while the workbench beneath it shows the live arithmetic: the current patch, the nine products, and their sum. Feature-map cells fill as they're computed — dark for positive scores, blue for negative — then the blue cells flip to empty on the ReLU beat, and the pooled grid takes a green border when the layer's output is complete.

Why Convolutions Beat Dense Layers on Images

Count the weights. This filter has nine, and nine is all it ever has — on a 6×6 toy or a 4K photograph, because the same stencil is reused at every position. A dense layer gives every pixel its own weight per unit: even modest 28×28 images cost 784 weights per neuron, and none of those neurons can transfer what they learned at one location to another. Convolution buys you both economy and translation: learn "vertical edge" once, find it everywhere. The compute bill is O(out_h · out_w · k²) — one k×k dot product per output cell — linear in image area, not quadratic in pixel count.

The Random filter recipe is the control experiment, and it's the most instructive of the presets. Run it: the same vertical-bar image under nine weights with no oriented structure. The sweep, the ReLU, the pooling all execute identically — and the feature map comes out a diffuse smear of 9s with the pooled summary [[9, 9], [18, 9]], which localizes nothing. Compare that against the vertical filter's crisp [[0, 27], [0, 27]]. Same image, same machinery, opposite usefulness: the filter's values are the detector. In a real CNN those values are precisely what gradient descent learns; the hand-picked edge filters in the other recipes are stand-ins for what training would find.

But you don't have to take any preset's word for it — the filter in the Inputs panel is a 3×3 grid of clickable cells, each cycling through −1, 0, 1, and the entire pipeline recomputes from whatever you set. Three experiments worth running. Set all nine cells to 1: you've built a brightness detector, and its pooled output is [[54, 54], [54, 54]] — large responses that retain brightness information but do not localize the edge in this pooled result. Take the horizontal-edge filter and zero out its middle column: the localization survives ([[0, 0], [18, 18]] instead of [[0, 0], [27, 27]]) but every response weakens by a third — each weight is carrying real load. Then try to design a filter that lights up only the bar's top edge on the horizontal image; when you check your pooled 2×2 against your prediction, you're doing exactly what this lesson is for.

Edge Cases

  • Valid vs padded convolution. This lesson never lets the filter hang off the image, so the output shrinks 6×6 → 4×4. For this 3×3 kernel and stride 1, one zero-padding cell on each border keeps output size equal to input size — the common choice in deep stacks, where repeated shrinking would erase the image.
  • Flat regions. Where every pixel in a patch is equal, left minus right is exactly zero. The corners of the default feature map read ±18 rather than 0 only because the bar's edge clips them.
  • Opposite-contrast edges. A dark-to-bright edge scores large and negative; ReLU discards that polarity; the negative responses themselves are still meaningful signed edge information.
  • Pooling that doesn't divide evenly. A 2×2 / stride-2 window tiles a 4×4 map perfectly. Over an odd-sized map the last window falls off the edge; real implementations pad or drop the remainder.

Common Mistakes

  • Imagining a different filter per position. It is one stencil, reused everywhere. Giving each local position its own weights creates a locally connected layer; a dense layer additionally connects each unit to all inputs.
  • Skipping the nonlinearity. A stack containing only linear convolutions remains a linear map, although its boundary and parameter structure can differ; ReLU and max-pooling add nonlinearity in this lesson.
  • Confusing window size with stride. Size sets how many cells each pooling window reads; stride sets how far it jumps. Equal size and stride gives the non-overlapping tiling shown here.
  • Reading the feature map as a smaller picture. A bright cell means "the filter matched strongly here," not "this pixel was bright." It is a map of stencil responses, not a thumbnail.
  • Assuming max is the only pooling. Average pooling (the window's mean) smooths instead of selecting. Max is the usual default for classification, but it is a choice, not a law.