Skip to content
Max Pooling
step 1/5

Reading — step 1 of 5

~3 min readArchitectures

Why pool at all

Convolutional feature maps are large and highly redundant — nearby pixels in a feature map usually encode very similar information (an edge detector fires similarly at adjacent positions along an edge). Max pooling aggressively downsamples a feature map by keeping only the strongest activation in each local window, which:

  • shrinks the spatial dimensions (fewer parameters and less compute downstream),
  • adds a small amount of translation invariance (a feature shifted by a pixel or two still triggers the same pooled output, since the max over a slightly shifted window is usually the same value or close to it),
  • and — unlike average pooling — keeps the sharpest signal in each region rather than blurring it away.

The computation

For each output position (r, c), with pooling window kH × kW and stride s:

out[r][c] = max over (u, v) in [0,kH) x [0,kW) of  image[r·s + u][c·s + v]

This is structurally identical to convolution's sliding-window loop, except instead of a weighted sum against a learned kernel, you're taking a plain max() over the window — there's no kernel to learn here, no weights at all.

Stride defaults to non-overlapping windows

Unlike convolution (where stride commonly defaults to 1), pooling conventionally defaults its stride to the window size itself — stride = kH — so that windows tile the image without overlapping. A 2×2 pool with default stride divides the image into a grid of disjoint 2×2 blocks and reduces each to one number, cutting both dimensions in half. If you don't get an explicit STRIDE, fall back to this convention rather than assuming stride=1.

Output size

Same formula shape as convolution, but without padding (pooling is rarely padded):

out_dim = ((in_dim - kernel_dim) / stride) + 1

Implementation notes

  1. Read the image and pooling window dimensions.
  2. Determine the stride: use the explicit value if given, otherwise default to the window's height (kH), matching the non-overlapping convention.
  3. Compute output dimensions with the formula above.
  4. For each output position, scan the corresponding window in the input and track the running maximum.
  5. Print each output row, comma-separated, formatted to 4 decimals.

Edge cases:

  • Initialize your running max to the window's first element (image[r·s][c·s]), not to 0 or -infinity implemented incorrectly — activations can legitimately be negative, so seeding with 0 would silently produce wrong answers whenever an entire window is negative.
  • Double-check the stride default: if the protocol omits STRIDE, don't silently treat it as 1 — for max pooling the implicit default is the kernel height, producing non-overlapping tiles.
  • Just like convolution, remember to multiply the output index by the stride when computing which input window to examine — indexing with the wrong multiplier is the most common source of off-by-one/off-by-stride bugs in pooling code.

Max pooling has no learnable parameters and no gradient to compute during the forward pass shown here, but during backprop the gradient only flows back through whichever position held the max in each window — everything else in that window gets zero gradient. That routing behavior is worth keeping in mind once you get to backpropagating through a full CNN.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…