---
title: "CNN"
description: "Convolution, pooling, feature maps"
source: https://vizlearn.app/ai/neural-networks/cnn
access: premium (interactive player requires Lifetime; this doc is free)
---

# 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.
