Why CNNs: Convolution, Pooling & Receptive Fields¶
What was run¶
A real PyTorch nn.Conv2d layer, with its weights manually set to the exact same Sobel-X kernel from
Level 1, run against the same real image and compared directly to
cv2.Sobel() — plus real, measured checks of output-size formulas, shift-invariance under pooling, and
receptive-field growth across stacked layers.
A real CNN layer computing exactly the same thing as a classical filter¶
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1, bias=False)
conv.weight[:] = torch.from_numpy(sobel_x_kernel).view(1, 1, 3, 3)
REAL CNN conv layer (manually set to Sobel-X weights) vs cv2.Sobel (full image): max diff=923.000000
REAL same comparison, interior pixels only (excluding the 1px border): max diff=0.000000
A real, exact match at every interior pixel (0.000000) — once the comparison excludes the image border. The
real 923.0 discrepancy on the full image is, investigated directly, the exact same kind of real border-padding
mismatch Level 1, Lesson 3 already found: PyTorch's padding=1 pads
with zeros, while cv2.Sobel() reflects at the border — two real, different conventions, not a math error.
Interior pixels, unaffected by either convention, prove the two are computing the identical real operation.
Real formulas, verified against real torch output¶
def expected_output_size(in_size, kernel, stride, padding):
return (in_size + 2 * padding - kernel) // stride + 1
kernel=3 stride=1 padding=0: formula predicts 30, real torch output is 30, match=True
kernel=3 stride=1 padding=1: formula predicts 32, real torch output is 32, match=True
kernel=3 stride=2 padding=0: formula predicts 15, real torch output is 15, match=True
kernel=5 stride=2 padding=2: formula predicts 16, real torch output is 16, match=True
Every real combination matched the textbook formula exactly.
Real pooling and real receptive-field growth, both measured directly¶
no_pool_diff = torch.mean(torch.abs(img_t - shifted_t)).item()
pool_diff = torch.mean(torch.abs(pool(img_t) - pool(shifted_t))).item()
1 stacked 3x3 conv layers: real measured receptive field area = 9 pixels (expected 3x3=9)
2 stacked 3x3 conv layers: real measured receptive field area = 25 pixels (expected 5x5=25)
3 stacked 3x3 conv layers: real measured receptive field area = 49 pixels (expected 7x7=49)
5 stacked 3x3 conv layers: real measured receptive field area = 121 pixels (expected 11x11=121)
A real, measurable reduction in sensitivity to a 1-pixel shift after pooling (5.7749 vs 7.0621), and real,
exact receptive-field areas — measured directly via gradient flow, not just computed from a formula — confirming
each stacked 3×3 layer genuinely grows the region of input that can influence one output unit by exactly 2
pixels per side.
How It Actually Works¶
A convolutional layer is not a different kind of operation from the classical filters in
Level 1 — it's the exact same sliding-window weighted sum, with the
one real difference that a CNN's weights are learned rather than hand-designed, as this lesson's manually-set
Sobel weights prove directly by matching cv2.Sobel() pixel-for-pixel. Pooling reduces spatial resolution and,
as this lesson measured, reduces sensitivity to small real translations — not eliminating it, but genuinely
dampening it, which is exactly the kind of approximate shift-tolerance a classifier benefits from. Stacking
layers grows the receptive field — how much of the real input ultimately influences one output value —
linearly with each additional 3×3 layer, confirmed here by directly measuring gradient flow rather than trusting
the formula alone.
Common mistakes¶
- Treating a CNN's internal filters as mysterious or fundamentally different from classical image filters — this lesson's real, exact interior-pixel match (manually set Sobel weights) proves a single conv layer is a classical filter; what's different is that, during training, the network discovers its own useful kernels instead of a human hand-designing them.
- Assuming max pooling makes a network fully shift-invariant — this lesson's real numbers show pooling reduces,
but does not eliminate, sensitivity to a 1-pixel shift (
5.7749, not0). - Comparing two convolution implementations on a full image without checking border-pixel handling separately — this lesson's real investigation (same discipline as Level 1, Lesson 3) is what correctly distinguished a real border-convention difference from an actual computation error.
Exercise¶
Manually set a second real nn.Conv2d layer's weights to the Laplacian kernel from
Level 1, Lesson 3, and verify it matches cv2.Laplacian() at every
interior pixel the same way this lesson verified Sobel — confirming the same real principle holds for a second,
different classical filter.