datarekha

Convolutional neural networks (CNNs)

How CNNs see — convolution, kernels, feature maps, stride, padding, and pooling — built from scratch in NumPy, with an interactive filter you scan across a real image.

10 min read Intermediate Deep Learning Lesson 19 of 28

What you'll learn

  • Why a fully-connected layer is the wrong tool for an image
  • How a convolution slides one small kernel to build a feature map
  • What stride, padding, channels, and pooling actually do
  • How stacking convolutions grows the receptive field into a hierarchy

Before you start

Before transformers ate the world, convolutional neural networks were how machines learned to see — and they still are the default for most vision tasks that aren’t worth a giant model. The whole idea is one small trick, repeated. Let’s earn it from first principles.

Input · 9×9 pixels
0-10-14-10-10
Kernel · 3×3
Feature map · 7×7

Edge detect: fires only where brightness changes — the rim of the disk. Output cell (3,3) = Σ (its 3×3 window × the kernel) = 0.00.

One kernel sweeps the whole image — that weight-sharing is why a CNN needs so few parameters. Click any feature-map cell to see its receptive field.

Why not just use a dense layer?

Take a modest 224×224 colour image. Flatten it and feed it to a fully connected layer with 1,000 units. That first weight matrix alone is 224 × 224 × 3 × 1000 ≈ 150 million parameters — for one layer, on a small image. Worse, it learns nothing reusable: a cat in the top-left corner and the same cat in the bottom-right are, to a dense layer, two completely unrelated input patterns. It would have to re-learn “cat” in every position.

Two facts about images break the dense approach, and point straight at the fix:

  • Locality. A pixel’s meaning comes from its neighbours, not from a pixel 200 rows away. Edges, corners, textures are local.
  • Translation invariance. An edge is an edge wherever it appears. The feature detector should be the same everywhere.

Convolution, concretely

A kernel is a small matrix of weights. You place it over a patch of the image, multiply element-wise, and sum — one number. Slide it one step, repeat. The grid of results is a feature map: high where the patch matched the kernel, low where it didn’t.

Hand-designed filters make it concrete: an edge-detector lights up only the rim of a shape, a blur smears it, identity copies it — same input, wildly different feature maps depending on the kernel. The window slides over every patch, filling the map one dot product at a time:

A convolution: slide a kernel, multiply-and-suminput (5×5)kernel (3×3)Σ(patch · kernel)feature map (3×3)one patch × kernel → one cell; slide over all patches to fill the map (high where the patch matches)

The magic of CNNs is that you don’t hand-pick these numbers. The nine kernel weights are parameters — gradient descent discovers which filters are worth having. Early layers reliably learn edge and colour detectors (the network rediscovers Sobel on its own); deeper layers learn textures, then parts, then objects.

Build it from scratch

Here is a valid 2-D convolution in pure NumPy — no deep-learning library, just the multiply-and-sum. Run it, then change the kernel to the blur np.ones((3,3))/9 and watch the output smooth out.

import numpy as np

# A tiny 6x6 "image" with a bright square in the middle
img = np.zeros((6, 6))
img[2:4, 2:4] = 1.0

# A vertical-edge detector (Sobel-x)
kernel = np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]])

def conv2d(x, k):
    kh, kw = k.shape
    H, W = x.shape
    out = np.zeros((H - kh + 1, W - kw + 1))
    for i in range(out.shape[0]):
        for j in range(out.shape[1]):
            patch = x[i:i+kh, j:j+kw]      # the receptive field
            out[i, j] = np.sum(patch * k)  # one dot product
    return out

fmap = conv2d(img, kernel)
print("input shape :", img.shape)
print("output shape:", fmap.shape, "  (valid conv shrinks by kernel-1)")
print(np.round(fmap, 1))
# The +/- pair shows the left and right edges of the square.
input shape : (6, 6)
output shape: (4, 4)   (valid conv shrinks by kernel-1)
[[ 1.  1. -1. -1.]
 [ 3.  3. -3. -3.]
 [ 3.  3. -3. -3.]
 [ 1.  1. -1. -1.]]

Notice the output is smaller than the input (6×6 → 4×4). A 3×3 kernel can’t centre on the border pixels, so a “valid” convolution trims a kernel − 1 rim. Two knobs control this:

  • Padding — add a border of zeros so the output keeps the input size (padding=1 for a 3×3 kernel → “same” convolution). Without it, a deep stack would shrink the image away to nothing.
  • Stride — how far the kernel jumps each step. Stride 2 skips every other position, halving the output size — a cheap way to downsample.

Channels: stacking filters

One kernel finds one kind of feature. Real layers learn many in parallel — say 64 kernels — producing 64 feature maps stacked into a 64-channel output. The next layer’s kernels are then 3×3×64: they look across all incoming channels at once, combining “edge here + colour there” into richer features. A colour input image is just the first case of this: 3 channels (R, G, B) in, out_channels feature maps out.

Pooling: throw away position, keep the signal

After a convolution + activation (usually ReLU), CNNs often pool: take each small region of the feature map and keep only its maximum (max pooling) or average. A 2×2 max-pool halves height and width and says “an edge fired somewhere in this region — I don’t care exactly where.” That deliberate loss of precise position is what buys translation invariance, and it shrinks the feature maps so deeper layers stay cheap.

The hierarchy: why depth works

Stack the block — conv → ReLU → pool — a few times and something powerful emerges. Each pooling step means a single deep-layer pixel now summarises a large patch of the original image: its receptive field grows. So the network builds a hierarchy:

pixels → edges → textures → motifs → object parts → objects
  (layer 1)  (layer 2)   (layer 3)    (deeper)      (head)

Early layers see tiny patches and learn generic edges; late layers see most of the image and learn “this is a wheel, that is a face.” A final dense layer (or global-average-pool) turns the top feature maps into class scores. That’s a CNN — LeNet, AlexNet, VGG, and ResNet are all this block, stacked deeper with better training tricks.

In one breath

  • A dense layer on an image is hopeless — ~150M parameters for one small image, and it re-learns each feature in every position.
  • A convolution learns one tiny kernel and slides it everywhere (weight sharing), exploiting locality and translation invariance — a few thousand params that work at any resolution.
  • Stride and padding control output size; many kernels stack into channels; pooling drops exact position to buy translation invariance and downsample.
  • Stacking conv → ReLU → pool grows each unit’s receptive field, building a hierarchy: edges → textures → parts → objects.
  • ResNet-style CNNs are still the pragmatic default for limited-data, low-latency vision; ViTs win at large data but need much more of it.

Check yourself

Quick check

0/3
Q1A 3×3 'same' convolution (padding 1, stride 1) with 32 input channels and 64 output channels — how many weights (ignore biases)?
Q2Why does max-pooling help a classifier recognise an object regardless of where it sits in the image?
Q3Stacking conv→pool blocks makes deep layers powerful mainly because…

What to remember

  • A convolution slides one small learned kernel across the whole input — weight sharing turns a 150M-parameter dense layer into a few thousand reusable weights that work at any resolution.
  • Stride and padding control output size; channels stack many filters; pooling trades exact position for translation invariance.
  • Stacking conv → ReLU → pool grows the receptive field, building a hierarchy from edges to objects — the reason depth matters in vision.

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

FAQCommon questions

Questions about this lesson

Why use a CNN instead of a fully-connected network for images?

A dense layer learns a separate weight for every pixel-to-unit pair, so even a small image needs hundreds of millions of parameters and has to re-learn each feature in every position. A convolution learns one small kernel and slides it everywhere, so the same few weights detect a feature wherever it appears. That weight-sharing exploits the two facts that matter for images — locality and translation invariance — making the network both far smaller and far more data-efficient.

What is a feature map in a CNN?

A feature map is the output of sliding one kernel across the whole input: a grid whose value at each position is the dot product of the kernel with the patch underneath it. It is high where the input matched the kernel's pattern (an edge, a texture) and low elsewhere — literally a map of where that feature was found. A convolutional layer with 64 kernels produces 64 feature maps, stacked as 64 output channels.

What do stride and padding do in a convolution?

Stride is how far the kernel jumps each step: stride 1 visits every position, stride 2 skips every other one and halves the output size (cheap downsampling). Padding adds a border of zeros around the input so the kernel can centre on edge pixels; 'same' padding (1 pixel for a 3×3 kernel) keeps the output the same size as the input, which is what stops a deep stack from shrinking the image away to nothing.

How does a receptive field explain why deep CNNs work?

A receptive field is the region of the original image that influences one unit's value. Early layers have tiny receptive fields and can only see edges; each pooling or strided step enlarges the receptive field, so deep units summarise large patches and can represent whole object parts. Stacking conv→ReLU→pool therefore builds a hierarchy — edges → textures → parts → objects — which is the reason depth helps in vision.

Practice this in an interview

All questions
How do you count the number of trainable parameters in a convolutional layer?

Each filter has k*k*C_in weights plus one bias, and a layer with C_out filters therefore has (k*k*C_in + 1)*C_out parameters. This count is independent of the input's spatial dimensions H and W, which is what makes CNNs so parameter-efficient.

What are filters and feature maps in a CNN, and what do they represent?

A filter (kernel) is the set of learned weights that the network applies at each spatial position; a feature map is the spatial grid of responses produced when one filter slides over the input. Each filter detects one type of pattern, and the full stack of feature maps across all filters constitutes the layer's output representation.

What is pooling, and when would you choose max pooling over average pooling?

Pooling downsamples a feature map by aggregating values in a local window, reducing spatial dimensions and building position tolerance. Max pooling takes the strongest activation in each window; average pooling takes the mean. Max pooling dominates in classification backbones; average pooling is preferred in global summarisation and smooth feature maps.

What is a 1x1 convolution and why is it useful?

A 1x1 convolution applies a learned linear combination across channels at each spatial position, without looking at any spatial neighbourhood. It is used to change the number of channels cheaply, add non-linearity between pointwise operations, and build the bottleneck blocks at the core of Inception and ResNet-50+.

What is the receptive field of a neuron in a CNN and how does it grow with depth?

The receptive field is the region in the original input that influences a single activation in a given layer. With a 3x3 kernel and stride 1, each layer adds 2 pixels to the receptive field in each direction; stacking L layers gives a theoretical receptive field of (2L+1) x (2L+1). Stride, pooling, and dilated convolutions all expand the receptive field faster.

Describe the ResNet architecture and explain the key design choices that made it work.

ResNet (He et al., 2015) stacks residual blocks that add the input directly to the block's output, enabling stable training of 50–152+ layer networks. The key design choices are the residual shortcut for gradient flow, batch normalisation after each convolution, and bottleneck blocks (1x1 → 3x3 → 1x1) that control parameter count in deeper variants.

What are skip connections in ResNet and why were they necessary?

Skip connections add the input of a block directly to its output — bypassing the conv-BN-ReLU stack — so gradients can flow straight back to early layers without passing through every weight matrix. They solved the degradation problem that made deeper plain networks perform worse than shallower ones, not because of overfitting but because of optimisation difficulty.

What do stride and padding control in a convolutional layer?

Stride sets how many positions the kernel jumps between applications, controlling output resolution — stride 2 roughly halves spatial dimensions. Padding adds values (usually zeros) around the border so the kernel can be applied to edge pixels, letting you choose whether the output shrinks, stays the same, or (rarely) grows relative to the input.

What does a convolution operation do in a CNN?

A convolution slides a small learned weight matrix (kernel) across the input, computing a dot product at each position to produce a feature map. Each kernel learns to detect one spatial pattern — an edge, a corner, a texture — regardless of where it appears in the image.

Why do CNNs outperform fully-connected networks on image data?

CNNs exploit three structural properties of images — local correlation, translation invariance, and compositional hierarchy — through parameter sharing and local receptive fields. A dense network treats every pixel as independent, ignoring spatial structure and requiring orders of magnitude more parameters.

Related lessons

Explore further

Skip to content