chapter 04 / cnn-vision · 预计学习时间 120 分钟

CNN 与计算机视觉
先验焊进架构

AUDIO // 本章语音导读
本章目录
  1. 全连接的灾难:为什么图像需要新架构
  2. 卷积:局部连接 + 权重共享
  3. 交互实验室:亲手当一回卷积核
  4. 卷积层的工程细节:尺寸、通道、感受野
  5. 架构演进:LeNet → ResNet 的五次顿悟
  6. 残差连接:梯度的高速公路
  7. ViT:当图像变成 token 序列
  8. 代码实战:PyTorch 实现 ResNet 块
  9. 章节测验

全连接的灾难:为什么图像需要新架构

把第 3 章的 MLP 直接用在图像上试试:一张 224×224 的 RGB 图展平后是 150,528 维。第一个隐藏层哪怕只要 1000 个单元,参数就是 1.5 亿——只是一层。更糟的是结构性的浪费:

解决思路是本章的灵魂概念:归纳偏置(inductive bias)——把我们对数据的先验知识直接焊进网络结构,让参数不必浪费在重新发现「图像是平移规律的、局部相关的」这种常识上。

卷积:局部连接 + 权重共享

卷积层用两条结构约束实现这个先验:

$$ (I * K)(i, j) = \sum_{m}\sum_{n} I(i+m,\, j+n)\, K(m, n) $$

一个小窗口(卷积核,如 3×3 的 9 个权重)在图像上滑动,每个位置算一次加权和。两条约束的含义:

参数量对比,感受一下归纳偏置的威力:全连接层 150528×1000 ≈ 1.5 亿参数;一个 64 通道的 3×3 卷积层是 3×3×3×64 = 1728 个参数。十万倍的压缩,换来的不是表达力损失,而是把「平移不变」这个真理免费送给了模型。第 1 章偏差-方差语言:归纳偏置 = 主动增大偏差换方差暴跌——当先验正确时,这笔交易稳赚。

关键认知:卷积核的 9 个数字就是可学习的权重 $\theta$。训练前它是随机噪声,训练后它「长成」边缘检测器、纹理检测器……没人手工设计它们——这正是与传统图像处理(人工设计 Sobel 算子)的分水岭。下面你将亲手扮演一次「训练好的卷积核」,体会一个核能看见什么、看不见什么。

交互实验室:亲手当一回卷积核

必做实验:① Sobel-X 与 Sobel-Y 对比——看竖直边缘和水平边缘如何被分别「点亮」(方块的四条边只亮两条);② 渐变带的沉默——平滑变化的区域边缘检测输出接近 0,卷积检测的是「突变」不是「亮度」;③ 把视图切到 ReLU——这就是真实 CNN 里下一层收到的东西:负响应被砍掉,只留「检测到了」的信号;④ 手动改数字——把 9 个权重清零只留中心为 1(恒等核),再随机乱填,体会「未训练的核」输出的是无意义噪声。

conv2d.apply(kernel)

中间的 3×3 就是卷积核的 9 个权重,直接编辑数字,或用预设。右侧 feature map 用磷光绿表示激活强度(它是神经元的兴奋程度,不是照片)。

输入图像(程序生成的边缘标本集)
Sobel-X
feature map(输出)
VIDEO 01
But what is a convolution?(卷积到底是什么)
3Blue1Brown 23:01
观看指南
  • 08:22 图像上的滑动窗口与模糊/边缘核——刚才实验室操作的动画版。
  • 12:30 卷积定理与 FFT 加速——选听,但能让你明白「卷积」这个数学词为何如此通用。
  • 注意:深度学习框架里的「卷积」严格说是互相关(不翻转核),因为核是学出来的,翻不翻转无所谓。

卷积层的工程细节:尺寸、通道、感受野

输出尺寸公式

$$ O = \left\lfloor \frac{N + 2P - F}{S} \right\rfloor + 1 $$

$N$ 输入尺寸、$F$ 核尺寸、$P$ 补零圈数(padding)、$S$ 步幅(stride)。两个常用配置背下来:3×3 核 + $P{=}1$ + $S{=}1$ → 尺寸不变;$S{=}2$ → 尺寸减半(现代架构常用它取代池化做下采样)。

通道:一层不止一个核

一个核只能找一种模式,所以每层并行学几十上百个核。输入 $C_{in}$ 通道时,每个核实际是 $F \times F \times C_{in}$ 的小立方体,输出堆成 $C_{out}$ 个 feature map。参数量 $= F^2 C_{in} C_{out} + C_{out}$。RGB 图第一层 64 核:3×3×3×64+64 = 1792。

池化与感受野

2×2 最大池化把尺寸减半:保留「这附近有没有检测到」、扔掉「精确在哪」——花小代价买局部平移不变性。更重要的副产品是感受野的扩张:堆叠 3×3 卷积,第二层的每个神经元间接看到 5×5、第三层 7×7……配合下采样,深层神经元的感受野覆盖大半张图。层级特征由此涌现:浅层学边缘 → 中层把边缘拼成纹理和部件(眼睛、轮子)→ 深层把部件拼成物体。第 1 章视频里 3B1B 画的「层层抽象」假想图,在 CNN 里是真实可视化的(Zeiler & Fergus 2014)。

两层 3×3(感受野 5×5,参数 $2\times 9C^2{=}18C^2$,夹两次非线性)严格优于一层 5×5(参数 $25C^2$,一次非线性)——更少参数、更深的非线性、同样的视野。这就是 VGG 的全部秘密:只用 3×3,靠深度堆感受野。此后「小核堆深」成为共识。

架构演进:LeNet → ResNet 的五次顿悟

架构(年)层数顿悟
LeNet-5(1998)5卷积+池化+全连接的基本配方成立,读了美国 10% 的支票
AlexNet(2012)8同样的配方 × GPU × 大数据就能引爆;ReLU、Dropout 加持(序章讲过这一刻)
VGG(2014)19架构可以极简:从头到尾只用 3×3,深度即正义
GoogLeNet(2014)22并联多尺度分支(Inception),1×1 卷积做通道压缩省算力
ResNet(2015)152残差连接打破深度墙,错误率 3.6% 首超人类(5.1%)——详见下节

VGG 之后大家自然地继续堆深,然后撞墙了:56 层的普通网络训练误差反而比 20 层高。注意不是过拟合(训练误差都变差了),是优化失败——理论上 56 层至少能把多出的 36 层学成恒等映射来追平 20 层,但梯度下降找不到这个解。深度的收益被优化的困难吃掉了。

残差连接:梯度的高速公路

何恺明的解法是给优化「修路」。普通块要求层直接学目标映射 $H(x)$;残差块改为学修正量 $F(x) = H(x) - x$,输出:

$$ y = F(x) + x $$

两个立竿见影的效果:

残差连接可能是 2012 年以来泛化能力最强的单个架构发明:今天每个 Transformer 块里都有两条残差连接(第 6 章你会亲手写 x = x + attn(norm(x))),GPT 本质上是一摞残差块。「LLM 是 ResNet 的精神后裔」并不夸张。配方的另一半是 BatchNorm(第 3 章已推导):卷积后、激活前,conv→BN→ReLU 三件套。
VIDEO 02
Neural Networks Part 8: Image Classification with CNNs(CNN 图像分类)
StatQuest with Josh Starmer 15:24
观看指南
  • 03:00 卷积核滑动计算 feature map 的逐数字演示——和实验室的操作一一对应。
  • 07:30 最大池化为什么保留「检测到没有」、丢弃「精确位置」。
  • 11:00 整条管线串起来:conv→pool→flatten→全连接——LeNet 的完整骨架。

ViT:当图像变成 token 序列

2020 年的 Vision Transformer 提出了挑衅性的问题:卷积的归纳偏置是必需品还是拐杖?做法暴力而优雅:把图像切成 16×16 的 patch,每个 patch 展平后线性投影成一个向量——把 patch 当作「视觉单词」,喂给标准 Transformer(第 6 章的主角),让自注意力自己学习 patch 之间的关系。

结果完美呼应第 1 章的偏差-方差框架:中小数据集上 ViT 输给 CNN(没有平移先验,方差大);数据量到亿级后 ViT 反超——数据足够时,模型能自己学出比人工先验更好的「先验」,硬编码的偏置反而成了天花板。这也是《苦涩的教训》(附录信息源里 Sutton 那篇两页雄文)的又一次应验:通用方法 + 更多算力数据,长期总是赢过人工先验。2026 年的格局:纯视觉任务 CNN(含 ConvNeXt)与 ViT 并存,但多模态 LLM 的视觉编码器几乎清一色 ViT 系——因为它和语言塔共享同一种架构语言。

代码实战:PyTorch 实现 ResNet 块

python · resnet_block.py
import torch, torch.nn as nn

class ResidualBlock(nn.Module):
    """conv→BN→ReLU→conv→BN,再加上 shortcut,最后一起 ReLU"""
    def __init__(self, ch_in, ch_out, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(ch_in, ch_out, 3, stride, padding=1, bias=False)
        self.bn1   = nn.BatchNorm2d(ch_out)          # BN 自带偏置,conv 可省 bias
        self.conv2 = nn.Conv2d(ch_out, ch_out, 3, 1, padding=1, bias=False)
        self.bn2   = nn.BatchNorm2d(ch_out)
        # 尺寸/通道变化时,捷径用 1×1 卷积对齐形状,否则恒等
        self.shortcut = nn.Identity()
        if stride != 1 or ch_in != ch_out:
            self.shortcut = nn.Sequential(
                nn.Conv2d(ch_in, ch_out, 1, stride, bias=False),
                nn.BatchNorm2d(ch_out))

    def forward(self, x):
        out = torch.relu(self.bn1(self.conv1(x)))
        out = self.bn2(self.conv2(out))
        return torch.relu(out + self.shortcut(x))   # ← y = F(x) + x,全章的灵魂在这一行

# 验证输出尺寸公式:O = (N + 2P - F)/S + 1
x = torch.randn(1, 64, 56, 56)
print(ResidualBlock(64, 64)(x).shape)        # → (1, 64, 56, 56)  尺寸不变
print(ResidualBlock(64, 128, stride=2)(x).shape)  # → (1, 128, 28, 28) 减半且升通道
训练好的 CNN 第一层卷积核可视化出来几乎总是 Gabor 滤波器(各种朝向的边缘/条纹检测器),和哺乳动物初级视皮层 V1 的感受野惊人相似。这说明什么?
两个互不排斥的解释:①「边缘」是自然图像统计的客观最优基元——任何高效视觉系统(无论碳基硅基)在同样的数据分布下都会收敛到相似的第一层表示,这是任务决定的而非实现决定的(计算神经科学称为「趋同」证据);②它验证了层级特征假说:复杂识别可以也应该从简单局部模式逐级构建。有趣的延伸:ViT 在大数据上训练后,浅层注意力头也自发学出局部、类卷积的行为——先验没被硬编码时,数据会把它重新发明出来。

章节测验