Skip to content
AI360Xpert
Beta

Basic CNN Operations

Convolutions slide small weight templates across an image to spot local patterns, while strides, padding, and pooling govern how feature maps shrink.

A 3x3 kernel slides over a 4x4 image with stride 1 and no padding to write a 2x2 map, and 2x2 max pooling keeps the strongest value 2
A 3x3 kernel slides over a 4x4 image with stride 1 and no padding to write a 2x2 map, and 2x2 max pooling keeps the strongest value 2

Why Does This Exist?

A dense layer treats an image as a flat list of independent numbers, and that breaks twice. First on size: feeding a 1024×10241024 \times 1024 color image with 3 channels into one dense layer of 1,0241{,}024 units needs 1024×1024×3×1024≈3.22×1091024 \times 1024 \times 3 \times 1024 \approx 3.22 \times 10^9 weights, about 12.8 gigabytes in single precision for a single layer. Second on position: shift an edge five pixels right and the dense layer sees a brand-new input it must relearn from scratch.

Convolutions fix both with two constraints: each neuron looks only at a small local patch, and every location reuses the same weights. Stride, padding, and pooling then give exact control over how fast the spatial map shrinks and how fast the receptive field grows.

This page covers the elementwise arithmetic: the sliding sum, the output-size rule, and pooling. The sliding-window picture itself lives in convolutional layers; what each channel of the result means lives in feature maps. Size conventions in isolation are in padding and stride.

Think of It Like This

One rubber stamp swept across a mosaic wall

You must grade every patch of a huge mosaic for one pattern, say a vertical edge. Instead of hiring a separate judge per patch, you carve one small 3×33 \times 3 rubber stamp and press it everywhere, writing one score per position. The same stamp finds the edge in the top-left corner and the bottom-right corner, because the stamp never changes, only its position does.

Padding is a blank foam border pinned around the wall so the stamp can center on edge tiles. Stride is how many tiles you hop between presses. Pooling is replacing each group of four scores with just the loudest one so the notebook stays small.

The analogy stops at learning: a rubber stamp is carved once by hand, while a convolution kernel is carved by gradient descent, reshaped a little every training step.

How It Actually Works

Frameworks implement cross-correlation (no kernel flip), though everyone calls it convolution. For an input XX with CinC_{\text{in}} channels and spatial size H×WH \times W, a filter bank WW with CoutC_{\text{out}} filters of size Kh×KwK_h \times K_w, bias bb (one value per output channel), stride SS, and zero-padding PP, the output at channel coutc_{\text{out}} and position (i,j)(i, j) is:

Y[cout,i,j]=b[cout]+∑c=0Cin−1∑m=0Kh−1∑n=0Kw−1X[c,i⋅Sh+m−Ph,j⋅Sw+n−Pw]⋅W[cout,c,m,n]Y[c_{\text{out}}, i, j] = b[c_{\text{out}}] + \sum_{c=0}^{C_{\text{in}}-1} \sum_{m=0}^{K_h-1} \sum_{n=0}^{K_w-1} X[c, i \cdot S_h + m - P_h, j \cdot S_w + n - P_w] \cdot W[c_{\text{out}}, c, m, n]

In words: center the kernel at each output position, multiply overlapping pairs, add everything plus the bias. Out-of-bounds reads hit the zero padding.

The output-size rule

The spatial dimensions follow floor arithmetic exactly:

Hout=⌊Hin−Kh+2PhSh⌋+1,Wout=⌊Win−Kw+2PwSw⌋+1H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} - K_h + 2P_h}{S_h} \right\rfloor + 1, \quad W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} - K_w + 2P_w}{S_w} \right\rfloor + 1

Three settings cover nearly all practice with square kernels (Kh=Kw=KK_h = K_w = K, Sh=Sw=SS_h = S_w = S):

  • Valid padding (P=0P = 0): no border added, so with S=1S = 1 each side shrinks by K−1K - 1.
  • Same padding (P=(K−1)/2P = (K - 1) / 2, odd KK, S=1S = 1): output size equals input size.
  • Downsampling stride (S=2S = 2): roughly halves each side while the kernel still overlaps fully.

Pooling slides a parameter-free window over the map. Max pooling keeps max⁡\max over each window, preserving the strongest response and tolerating shifts up to the window size. Average pooling keeps the mean, smoothing responses; applied over the whole map it becomes global average pooling, the usual bridge to a classifier head. Modern nets often swap pooling for stride-2 convolution so the downsampler itself can learn.

Worked example

Trace a single-channel 4×44 \times 4 input XX with a 3×33 \times 3 vertical-edge kernel KK, stride S=1S = 1, padding P=0P = 0, bias 00:

X=[1201031221021031],K=[10−110−110−1]X = \begin{bmatrix} 1 & 2 & 0 & 1 \\ 0 & 3 & 1 & 2 \\ 2 & 1 & 0 & 2 \\ 1 & 0 & 3 & 1 \end{bmatrix}, \quad K = \begin{bmatrix} 1 & 0 & -1 \\ 1 & 0 & -1 \\ 1 & 0 & -1 \end{bmatrix}

Output shape first: Hout=⌊(4−3+0)/1⌋+1=2H_{\text{out}} = \lfloor (4 - 3 + 0) / 1 \rfloor + 1 = 2, so YY is 2×22 \times 2. The kernel reads left column minus right column at each stop:

  1. Top-left patch X[0:3,0:3]X[0:3, 0:3]: (1+0+2)−(0+1+0)=3−1=2(1 + 0 + 2) - (0 + 1 + 0) = 3 - 1 = 2.
  2. Top-right patch X[0:3,1:4]X[0:3, 1:4]: (2+3+1)−(1+2+2)=6−5=1(2 + 3 + 1) - (1 + 2 + 2) = 6 - 5 = 1.
  3. Bottom-left patch X[1:4,0:3]X[1:4, 0:3]: (0+2+1)−(1+0+3)=3−4=−1(0 + 2 + 1) - (1 + 0 + 3) = 3 - 4 = -1.
  4. Bottom-right patch X[1:4,1:4]X[1:4, 1:4]: (3+1+0)−(2+2+1)=4−5=−1(3 + 1 + 0) - (2 + 2 + 1) = 4 - 5 = -1.
Y=[21−1−1]Y = \begin{bmatrix} 2 & 1 \\ -1 & -1 \end{bmatrix}

Positive values mark where the left side is brighter, the vertical-edge signature. A 2×22 \times 2 max pool with stride 22 over YY returns max⁡(2,1,−1,−1)=2\max(2, 1, -1, -1) = 2, one number summarizing the whole map.

Code

The snippet below replays the worked example in PyTorch and confirms the hand-computed map plus the pooled value:

import torchimport torch.nn as nn
x = torch.tensor([[[[1.0, 2.0, 0.0, 1.0],                    [0.0, 3.0, 1.0, 2.0],                    [2.0, 1.0, 0.0, 2.0],                    [1.0, 0.0, 3.0, 1.0]]]])
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0, bias=False)weight = torch.tensor([[[[1.0, 0.0, -1.0],                         [1.0, 0.0, -1.0],                         [1.0, 0.0, -1.0]]]])with torch.no_grad():    conv.weight.copy_(weight)
out_conv = conv(x)print(out_conv.shape)# -> torch.Size([1, 1, 2, 2])print(out_conv.squeeze().tolist())# -> [[2.0, 1.0], [-1.0, -1.0]]
out_pool = nn.MaxPool2d(kernel_size=2, stride=2)(out_conv)print(out_pool.squeeze().item())# -> 2.0

Watch Out For

Dropping the floor in the output-size formula

symptom: a reshape or concatenation crashes several layers downstream of the layer you mis-sized, so the traceback points nowhere near the real mistake. The cause is computing (H−K+2P)/S+1(H - K + 2P) / S + 1 as a float: with H=7H = 7, K=3K = 3, S=2S = 2, P=0P = 0 the true size is ⌊4/2⌋+1=3\lfloor 4/2 \rfloor + 1 = 3, but the un-floored 3.03.0 looks identical and lulls you into skipping the floor where it matters, for example H=6H = 6 giving ⌊3/2⌋+1=2\lfloor 3/2 \rfloor + 1 = 2, not 2.52.5.

Fix it by always applying integer floor division in size arithmetic and asserting the shape of every feature map once, right after building the model.

Even kernels under symmetric same padding

An even kernel such as 2×22 \times 2 or 4×44 \times 4 has no single center pixel, so (K−1)/2(K - 1) / 2 is fractional and symmetric padding cannot preserve size exactly: padding 11 on a 2×22 \times 2 kernel over a HinH_{\text{in}} map yields Hin+1H_{\text{in}} + 1, shifting every receptive-field center half a pixel. That silent shift breaks weight imports across frameworks and misaligns feature-pyramid concatenations.

Prefer odd kernels (3×33 \times 3, 5×55 \times 5, 7×77 \times 7) so P=(K−1)/2P = (K - 1) / 2 is an integer and same padding truly preserves alignment.

The Quick Version

  • Convolution slides one shared kernel over the image, multiplying overlapping pairs and summing, which buys local detail plus position reuse.
  • Output size obeys Hout=⌊(Hin−K+2P)/S⌋+1H_{\text{out}} = \lfloor (H_{\text{in}} - K + 2P) / S \rfloor + 1 per side; never skip the floor.
  • Same padding with odd kernels preserves size; stride 22 halves it while the weights still learn.
  • Max pooling keeps the strongest response per window; average pooling smooths, and global average pooling feeds classifier heads.
  • The worked 4×44 \times 4 input through a 3×33 \times 3 edge kernel gives a 2×22 \times 2 map reading 2,1,−1,−12, 1, -1, -1, pooled to 22.
  • Even kernels shift receptive-field centers under symmetric padding, so default to odd sizes.