CNN foundations, receptive fields & backbones
CNN 基础、感受野与骨干网络
Lessons 03–05 hand-engineered everything: corner detectors, descriptors, the camera model, epipolar geometry. The pipeline worked but was brittle — every stage a heuristic, every failure a new patch. This lesson takes the opposite bet. Bake the few things we know are true about images into an architecture, then learn the rest from data. The convolutional neural network is that bet, and — strikingly — its first layers relearn the edge and blob detectors lesson 03 wrote by hand, while its deeper layers learn the parts and objects no one could hand-code.
第 03–05 课把一切都手工设计了出来:角点检测器、描述子、相机模型、对极几何。这套流水线能用,但很脆——每一级都是一个启发式规则,每一次失败都要打一个新补丁。本课押上相反的赌注:把我们已知的、关于图像的少数几件真理烘焙进架构里,其余的则从数据中学出来。卷积神经网络就是这个赌注,而且——引人注目的是——它的头几层重新学出了第 03 课手写的边缘和斑点检测器,而更深的层则学到了没人能手工编码出来的部件和物体。
1 · The inductive bias: locality, weight sharing, equivariance vs invariance
1 · 归纳偏置:局部性、权重共享、等变性与不变性
A fully connected layer on a 224×224×3 image has ~150k inputs per neuron; it treats the pixel at (0,0) and (223,223) as unrelated coordinates and must learn from scratch that a cat in the corner is the same as a cat in the centre. That's statistically hopeless and ignores two facts we know about images: locality (a pixel's meaning is set by its neighbours — edges, textures, parts are local) and stationarity (a useful pattern like an edge is useful everywhere, not just where it was first seen). Convolution bakes both in. A conv layer slides one small kernel (e.g. 3×3) across all positions, computing a weighted sum at each — so it only looks locally (locality), and it uses the same weights everywhere (weight sharing → stationarity). Three terms to fix now: a kernel is the small grid of weights; stride is how many pixels it hops between placements; padding is the border of zeros added around the input so edge pixels get covered. The recurring sliding-weighted-sum from lesson 02, now with learned weights instead of a hand-picked Sobel kernel (the edge-detector weights from lesson 03).
在一张 224×224×3 的图像上,一个全连接层每个神经元有约 15 万个输入;它把 (0,0) 处和 (223,223) 处的像素当作互不相关的坐标,必须从零学起才能明白角落里的猫和中心的猫是同一回事。这在统计上毫无希望,而且忽略了我们已知的关于图像的两个事实:局部性(一个像素的含义由它的邻居决定——边缘、纹理、部件都是局部的)和平稳性(像边缘这样有用的模式在任何地方都有用,而不只是在它第一次出现的地方)。卷积把两者都烘焙了进去。一个卷积层用一个小小的卷积核(比如 3×3)滑过所有位置,在每个位置计算一个加权和——所以它只局部地观察(局部性),并且在任何地方都使用同一套权重(权重共享 → 平稳性)。现在先钉死三个术语:卷积核是那个小小的权重网格;步幅是它在两次放置之间跳过多少个像素;填充是在输入四周补上的一圈零,好让边缘像素也能被覆盖到。这就是第 02 课反复出现的滑动加权求和,只不过现在用的是学出来的权重,而不是手挑的 Sobel 核(第 03 课的边缘检测器权重)。
One output number, by hand. Lesson 02 built this arithmetic in full; here is the one-line reminder so this lesson stands on its own. Lay a 3×3 kernel [[0,−1,0],[−1,5,−1],[0,−1,0]] (a sharpen) over a 3×3 input patch [[10,10,10],[10,20,10],[10,10,10]] and multiply-accumulate: 0·10 + (−1)·10 + 0·10 + (−1)·10 + 5·20 + (−1)·10 + 0·10 + (−1)·10 + 0·10 = 100 − 40 = 60. That single number is the output pixel at the window's centre; slide the window one step and repeat. Slide that one kernel over every position and you get a full H×W output map; use Cout different kernels and you get Cout such maps, stacked into the H×W×Cout output tensor. A conv layer is millions of these, with the kernel weights set by gradient descent instead of chosen by hand.
手算一个输出数。第 02 课把这套算术完整地推过一遍;这里给出一句话的提醒,好让本课能独立成篇。把一个 3×3 卷积核 [[0,−1,0],[−1,5,−1],[0,−1,0]](一个锐化核)盖在一个 3×3 的输入块 [[10,10,10],[10,20,10],[10,10,10]] 上,做乘加:0·10 + (−1)·10 + 0·10 + (−1)·10 + 5·20 + (−1)·10 + 0·10 + (−1)·10 + 0·10 = 100 − 40 = 60。这一个数就是窗口中心处的输出像素;把窗口滑动一步,再重复一遍。让这一个卷积核滑过每一个位置,你就得到一整张 H×W 的输出图;用 Cout 个不同的卷积核,你就得到 Cout 张这样的图,堆叠成 H×W×Cout 的输出张量。一个卷积层就是数以百万计的这种运算,只不过卷积核的权重是由梯度下降定出来的,而不是手工选的。
What "channels" means. A feature map — the output of a conv layer — is a tensor of shape H×W×C: at every one of the H×W spatial locations sits a length-C vector. So "64 channels" means 64 different learned feature detectors, each evaluated at every pixel — channel 7 might respond to vertical edges, channel 31 to a skin-tone blob, and so on. A conv kernel mixes across all input channels at each spatial step, and a 1×1 conv is the degenerate case that does only that: it has no spatial extent, so at a fixed pixel it linearly recombines the C channel values into a new set of channels — a per-pixel mix along the channel axis.
"通道"是什么意思。一张特征图——卷积层的输出——是一个形状为 H×W×C 的张量:在 H×W 个空间位置的每一个上,都坐着一个长度为 C 的向量。所以"64 个通道"意味着 64 个不同的、学出来的特征检测器,每一个都在每个像素处求值——第 7 个通道可能对竖直边缘响应,第 31 个通道对肤色斑点响应,如此等等。一个卷积核在每个空间步上都会跨所有输入通道做混合,而 1×1 卷积是只做这件事的退化情形:它没有空间范围,所以在一个固定像素处,它把 C 个通道值线性地重新组合成一组新的通道——沿通道轴的逐像素混合。
This weight sharing has a precise, load-bearing consequence:
这种权重共享有一个精确的、承重的推论:
2 · The output-size formula
2 · 输出尺寸公式
A conv (or pool) layer with kernel size k, stride s, padding p (added to each side), and dilation δ (gaps inserted between kernel taps, so the kernel covers δ(k−1)+1 input pixels) maps an input of size n to:
一个卷积(或池化)层,卷积核尺寸为 k、步幅为 s、填充为 p(每侧各加),空洞率为 δ(在卷积核的抽头之间插入间隙,使卷积核覆盖 δ(k−1)+1 个输入像素),会把一个尺寸为 n 的输入映射为:
Earn it: the effective kernel spans δ(k−1)+1 pixels; with padding the usable input length is n + 2p; the number of full window placements is how many strides of s fit after the first placement, hence the −(δ(k−1)+1), divide by s, floor, plus one for the initial placement. Worked numbers: input n=224, a 7×7 conv stride 2 pad 3, no dilation: (224 + 6 − 6 − 1)/2 + 1 = ⌊223/2⌋ + 1 = 111 + 1 = 112 — the ResNet stem (the early conv layers that quickly downsample), halving resolution. A 3×3 stride-1 pad-1 conv: (n + 2 − 2 − 1)/1 + 1 = n — "same" padding, size preserved. A 2×2 stride-2 max-pool: (n − 2)/2 + 1 = n/2 — halves it. Stack these and resolution falls geometrically while channels rise: the funnel shape of every backbone (the stack of conv layers that turns an image into feature maps, reused across tasks).
来把它挣到手:有效卷积核跨越 δ(k−1)+1 个像素;加上填充后,可用的输入长度是 n + 2p;完整窗口放置的次数,就是在第一次放置之后还能容下多少个 s 步幅,于是有了 −(δ(k−1)+1),除以 s、向下取整,再加一(对应最初的那次放置)。带数字算一遍:输入 n=224,一个 7×7 卷积、步幅 2、填充 3、无空洞:(224 + 6 − 6 − 1)/2 + 1 = ⌊223/2⌋ + 1 = 111 + 1 = 112——这就是 ResNet 的主干起始段(stem)(快速下采样的那几个早期卷积层),把分辨率减半。一个 3×3、步幅 1、填充 1 的卷积:(n + 2 − 2 − 1)/1 + 1 = n——"same"(同尺寸)填充,尺寸保持不变。一个 2×2、步幅 2 的最大池化:(n − 2)/2 + 1 = n/2——把它减半。把这些堆起来,分辨率就按几何级数下降、通道数则上升:这正是每个骨干网络(把图像变成特征图、并在各任务间复用的那一叠卷积层)的漏斗形状。
3 · The receptive field — the keystone
3 · 感受野——拱心石
The receptive field (RF) of an output activation is the region of the input image that can influence it. A single 3×3 conv has RF 3. Stack a second 3×3 conv: each of its outputs sees a 3×3 window of the first layer, and each of those sees a 3×3 of the input — the two overlap into a 5×5 input region. A third gives 7×7. The RF grows with depth, and that growth is exactly how a network built from tiny local kernels eventually "sees" a whole object. Two quantities track it: the RF size r, and the jump j — the spacing, in input pixels, between adjacent outputs of the current layer, which equals the product of all strides up to and including that layer. (So the table's j column and the KPI both report the jump after the last layer's stride — the same number.) The recurrence (per layer ℓ with kernel k, stride s, dilation δ):
一个输出激活的感受野(RF),是输入图像中能够影响它的那块区域。单个 3×3 卷积的感受野是 3。再叠一个 3×3 卷积:它的每个输出都看到第一层的一个 3×3 窗口,而其中每一个又都看到输入的一个 3×3——两者叠加成输入上一个 5×5 的区域。第三个给出 7×7。感受野随深度增长,而这种增长正是一个由微小局部卷积核搭起来的网络,最终得以"看见"整个物体的方式。有两个量在追踪它:感受野尺寸 r,以及跳距 j——当前层相邻输出之间、以输入像素计的间距,它等于到该层为止(含该层)所有步幅的乘积。(所以表格里的 j 列和 KPI 报告的都是最后一层步幅之后的跳距——同一个数。)递推式(对第 ℓ 层,卷积核 k、步幅 s、空洞率 δ)为:
Read the RF line: each new layer's kernel reaches δ(k−1) taps beyond the centre, but each tap step is jℓ−1 input-pixels wide (because earlier strides have spread things out), so the RF grows by δ(k−1)·jℓ−1. Worked numbers — three 3×3 stride-1 convs: j stays 1; r: 1 → 1+2·1=3 → 3+2·1=5 → 5+2·1=7. Add a stride-2 conv next: j: 1→2, then a following 3×3 grows RF by 2·2=4 per layer, not 2 — stride accelerates RF growth, which is why downsampling is the cheap way to see context. Dilation grows RF without adding parameters or downsampling: a 3×3 with δ=2 reaches like a 5×5 (used heavily in segmentation, lesson 10). Two stacked 3×3s have the same RF (5×5) as one 5×5 but fewer parameters (2·9=18 vs 25, per channel pair; multiply by Cin·Cout for the full count) and an extra nonlinearity — the VGG insight.
读一读感受野那一行:每个新层的卷积核在中心之外伸出 δ(k−1) 个抽头,但每一步抽头有 jℓ−1 个输入像素那么宽(因为先前的步幅已经把东西摊开了),所以感受野增长 δ(k−1)·jℓ−1。带数字算一遍——三个 3×3、步幅 1 的卷积:j 保持为 1;r: 1 → 1+2·1=3 → 3+2·1=5 → 5+2·1=7。接着加一个步幅 2 的卷积:j: 1→2,那么随后的一个 3×3 每层让感受野增长 2·2=4,而不是 2——步幅会加速感受野的增长,这就是为什么下采样是看到上下文的廉价办法。空洞能在不增加参数、也不下采样的情况下增大感受野:一个 δ=2 的 3×3,触及范围像个 5×5(在分割中大量使用,第 10 课)。两个堆叠的 3×3 与一个 5×5 有相同的感受野(5×5),但参数更少(每个通道对 2·9=18 对 25;乘以 Cin·Cout 得到完整数目),还多了一次非线性——这就是 VGG 的洞见。
⭐ Interactive · receptive-field growth
⭐ 交互 · 感受野的增长
Stack conv layers with the sliders below; the widget runs the recurrence, prints r and j with the numbers plugged in at every layer, and draws the RF footprint of a single deep output back onto the input grid. Start at the default and add layers — watch the footprint expand. Turn on stride and watch it leap; turn on dilation and watch it spread faster — the kernel's taps fan apart, so the same number of weights covers a wider input region. The recurrence is the lesson.
用下面的滑块堆叠卷积层;小部件会运行递推式,在每一层打印出代入了数字的 r 和 j,并把某个深层输出的感受野足迹画回到输入网格上。从默认值开始,添加层数——看着足迹扩张。打开步幅,看它一跃而起;打开空洞,看它铺展得更快——卷积核的抽头向外散开,于是同样多的权重覆盖了更宽的输入区域。递推式本身就是本课的精髓。
4 · Backbone design patterns (lessons 07–13 lean on these)
4 · 骨干网络的设计模式(第 07–13 课都倚重它们)
| Pattern模式 | What it does它做什么 | Why it exists / trade-off为何存在 / 取舍 |
|---|---|---|
| Residual connection残差连接 | Output = x + F(x): a layer learns a correction to identity输出 = x + F(x):一层学的是对恒等映射的一个修正 | Gradients flow through the identity path, so very deep nets train; the easy default is "do nothing," not "destroy the signal." Costs memory traffic.梯度可以穿过恒等路径流动,于是很深的网络也能训练;省力的默认动作是"什么都不做",而不是"毁掉信号"。代价是内存带宽开销。 |
| Bottleneck block瓶颈块 | 1×1 reduce channels → 3×3 conv → 1×1 restore1×1 降通道 → 3×3 卷积 → 1×1 恢复 | Does the expensive 3×3 in a low-channel space → far fewer FLOPs; too narrow a waist bottlenecks representation.在低通道空间里做昂贵的 3×3 → 大幅减少 FLOPs;腰身太窄则会瓶颈住表示能力。 |
| 1×1 convolution1×1 卷积 | Per-pixel linear mix across channels (no spatial extent)跨通道的逐像素线性混合(无空间范围) | Cheap channel projection — change channel count, fuse feature maps, add a nonlinearity, with RF unchanged.廉价的通道投影——改变通道数、融合特征图、添加一次非线性,而感受野保持不变。 |
| Depthwise-separable conv深度可分离卷积 | Depthwise (one filter per channel, spatial only) + pointwise 1×1 (channel mix)逐深度(每通道一个滤波器,只做空间) + 逐点 1×1(通道混合) | Factorizes a normal conv → ~k²× fewer ops; the basis of mobile nets. Slightly lower capacity per layer.把普通卷积分解开 → 运算量约减少 k² 倍;移动端网络的基石。每层容量略低。 |
| Feature pyramid (FPN)特征金字塔(FPN) | Combine deep low-res semantic maps with shallow high-res detailed maps via top-down + lateral connections通过自顶向下 + 横向连接,把深层低分辨率的语义图与浅层高分辨率的细节图结合起来 | Deep layers know what but lost where/small things (downsampling + ERF); FPN restores multi-scale localization. Crucial for detection (09) and segmentation (10).深层知道是什么,却丢了在哪儿/小物体(下采样 + 有效感受野);FPN 恢复了多尺度的定位能力。对检测(09)和分割(10)至关重要。 |
Residuals, concretely. Before ResNet, stacking past ~20 layers made training error go up — not overfitting, an optimization failure: deep plain nets couldn't even learn the identity map. Writing each block as x + F(x) makes identity the zero-effort default (just push F→0) and gives gradients a direct highway back to early layers. That single trick unlocked 50–150+ layer networks and is now in essentially every architecture, transformers included (lesson 13).
具体谈残差。在 ResNet 之前,堆叠超过约 20 层会让训练误差上升——这不是过拟合,而是一次优化失败:很深的朴素网络甚至学不出恒等映射。把每个块写成 x + F(x),就让恒等映射成了零成本的默认动作(只要把 F→0 推过去即可),并给了梯度一条直通早期层的高速路。仅这一个技巧就解锁了 50–150 层乃至更多层的网络,如今它基本上出现在每一种架构里,包括 Transformer(第 13 课)。
Why backbones transfer. Early conv layers learn general image statistics — color contrasts, oriented edges, blobs (the very Gabor/Sobel/blob detectors of lesson 03, now learned). Middle layers compose these into textures and object parts; late layers into task-specific object detectors. Because the early/middle representations are shared across almost all vision tasks, you can pretrain a backbone once (on ImageNet — the 1.2M-image, 1000-class classification benchmark — or self-supervised, lesson 14) and reuse it: freeze or fine-tune the early blocks, and train a new head (the small task-specific output sub-network on top of the backbone) for your task. This is why "download a pretrained ResNet/ViT and fine-tune" is the default starting move for classification (08), detection (09), and segmentation (10).
骨干网络为何能迁移。早期卷积层学的是通用的图像统计特性——颜色对比、有向边缘、斑点(正是第 03 课的 Gabor/Sobel/斑点检测器,如今是学出来的)。中间层把这些组合成纹理和物体部件;后期层则组合成任务特定的物体检测器。由于早期/中间的表示在几乎所有视觉任务间是共享的,你可以只预训练一次骨干网络(在 ImageNet——那个 120 万张图、1000 类的分类基准——上,或用自监督方式,第 14 课)然后复用它:冻结或微调早期的块,并为你的任务训练一个新的头(骨干网络之上那个小小的、任务特定的输出子网络)。这就是为什么"下载一个预训练的 ResNet/ViT 再微调"是分类(08)、检测(09)和分割(10)的默认起手式。
Where this points next
这将指向何处
We now have the architecture: an inductive bias (locality + equivariance), the math of how information aggregates (output size + receptive field), the patterns that make it deep, efficient, and multi-scale (residual / bottleneck / depthwise / 1×1 / FPN), and the reason it transfers. But an architecture is inert. The same backbone can be a world-class classifier or a useless one depending entirely on the data, loss, and optimization wrapped around it. Lesson 07 supplies exactly that: train/val/test splits and leakage, annotation and label QA, label-preserving augmentation, normalization and initialization, regularization, class imbalance, the mechanics of transfer/fine-tuning the backbone we just built, mixed precision, and how to debug training when the loss won't move. Then lesson 08 puts the trained backbone to its simplest use — naming the whole image.
现在我们有了架构:一个归纳偏置(局部性 + 等变性)、关于信息如何聚合的数学(输出尺寸 + 感受野)、让它得以变深、高效、多尺度的那些模式(残差/瓶颈/深度可分离/1×1/FPN),以及它能迁移的原因。但架构本身是惰性的。同一个骨干网络,究竟是世界级的分类器还是一个废物,完全取决于包裹在它周围的数据、损失和优化。第 07 课提供的正是这些:训练/验证/测试集划分与数据泄漏、标注与标签质检、保标签的数据增强、归一化与初始化、正则化、类别不平衡、迁移/微调我们刚搭好的这个骨干网络的具体做法、混合精度,以及当损失不肯下降时如何调试训练。随后第 08 课把训练好的骨干网络用在它最简单的用途上——给整幅图像命名。
Interview prompts
面试题
- Why is convolution a good inductive bias for images? (Locality + weight sharing match how image structure works; vastly fewer params, and patterns generalize across positions.)为什么卷积对图像是一个好的归纳偏置?(局部性 + 权重共享与图像结构的运作方式相吻合;参数量大幅减少,且模式能跨位置泛化。)
- Difference between translation equivariance and invariance? (Conv: output shifts with input — location kept. Pooling/stride: output unchanged by small shifts — location forgotten.)平移等变性和不变性有什么区别?(卷积:输出随输入平移——位置被保留。池化/跨步:输出对小幅平移不变——位置被遗忘。)
- Work the receptive field of three stacked 3×3 stride-1 convs. (1→3→5→7; same as one 7×7 but fewer params + more nonlinearity.)算一算三个堆叠的 3×3、步幅 1 卷积的感受野。(1→3→5→7;与一个 7×7 相同,但参数更少 + 更多非线性。)
- How do stride and pooling affect small-object detection? (They enlarge RF and context but discard spatial resolution; small objects can vanish — hence FPN / high-res branches / dilation.)步幅和池化如何影响小物体检测?(它们增大感受野和上下文,但丢弃了空间分辨率;小物体可能消失——因此才有 FPN/高分辨率分支/空洞卷积。)
- Why do residual connections make deeper networks trainable? (Identity path gives gradients a highway and makes "do nothing" the default, fixing the deep-plain-net optimization failure.)为什么残差连接能让更深的网络可训练?(恒等路径给了梯度一条高速路,并让"什么都不做"成为默认动作,修复了很深的朴素网络的优化失败。)
- What is the effective receptive field and why is it smaller than the theoretical one? (Forward contributions fall off ~Gaussian from the center because central paths overlap — not a gradient effect.)什么是有效感受野,它为什么比理论感受野小?(前向贡献从中心向外大致按高斯衰减,因为中心的路径相互叠加——这不是梯度效应。)