上一讲第 5 讲:用 CNN 做图像分类 下一讲第 7 讲:循环神经网络

第 6 讲:CNN 架构

CNN Architectures —— 补全 CNN 的全部积木(归一化层、Dropout、激活函数),回顾 AlexNet 与 VGG,深入剖析 ResNet 的残差连接,再到权重初始化、数据增强、迁移学习与超参数调试的完整训练实践

本讲概览:

主讲人 Zain(Ehsan 与 Fei-Fei 联合指导的四年级博士生)把本讲拆成两大主题:如何搭建 CNN(把前面学过的卷积层、池化层、全连接层,与本讲新讲的归一化层、Dropout、激活函数拼装成完整的架构,并回顾 AlexNet、VGG、ResNet 这条经典演进路线),以及如何训练 CNN(权重初始化、数据预处理、数据增强、迁移学习与超参数搜索等一整套极其实用的工程技巧)。本讲后半部分"干货密度"较低,但全是做课程项目乃至任何视觉任务时都应照搬的实战流程。

一、开胃回顾:卷积层与池化层

老师先快速回顾了上一讲的核心内容。卷积层(convolutional layer)拥有一组预先定义数量的滤波器(filters,例如 6 个),每个滤波器的深度与输入数据的通道数匹配——比如 \(32 \times 32\) 的 RGB 图像有 3 个通道,第一层的每个滤波器就是 \(3 \times 32 \times 32\) 这种跨全通道的结构。滤波器在图像上滑动,在每个位置做点积(对应元素相乘求和)再加偏置,得到输出激活图(activation map)上的一个值;每个滤波器各产生一张激活图,最后通常接一个 ReLU 等非线性激活。有同学问"输入深度是 3,为什么输出深度是 6"——答案是输出深度等于滤波器个数;若后面再接一层卷积,新滤波器就必须横跨全部 6 张激活图,即输入深度变成 6。

池化层(pooling layer)则简单得多:典型做法是用 \(2 \times 2\)、步幅 2 的窗口滑过图像,每个窗口取最大值(max pooling)或平均值(average pooling)。两种都常用,设计新架构时可以都试一下看哪个更好。它的作用是在高、宽维度上做压缩整合,逐步缩小空间尺寸。

至此,卷积层、池化层、全连接层(本质上就是一次矩阵乘法加一个激活函数)我们都已学过;本讲上半场就是补上剩余的常用层——归一化层与 Dropout——再系统梳理激活函数。

二、归一化层:LayerNorm、BatchNorm 与家族成员

归一化层(normalization layer)的基本思想是:计算输入数据的统计量(均值、标准差),用它们把数据归一化,然后再学习一个"最优分布"。所有归一化层都遵循同一个两步流程:

  1. 归一化:减均值、除以标准差,把数据变成均值 0、标准差 1 的标准高斯分布;
  2. 缩放平移:乘以可学习的缩放参数、加上可学习的平移参数(通过梯度 descent 学出),让模型自己决定这一层"最想要"的分布。

各种归一化层的区别只有一个:用哪一部分数据来计算均值和标准差

2.1 LayerNorm:逐样本归一化

老师重点讲了 LayerNorm(层归一化)——如今深度学习尤其是 Transformer 中最常用的归一化层。设输入是 \(n\) 个样本组成的批次,每个样本是 \(D\) 维向量。LayerNorm 对每个样本单独计算一个均值和一个标准差(沿维度 \(D\) 方向),再用学习到的参数对该样本做缩放和平移。推广到 CNN 的情形(数据有通道 \(C\)、高 \(H\)、宽 \(W\) 三个维度),LayerNorm 对每个样本在所有通道、所有空间位置上算出一对统计量,然后统一应用。

2.2 一张图看懂全家:BatchNorm / InstanceNorm / GroupNorm

老师推荐了 Group Normalization 论文中那张著名的示意图:把 \((N, C, H, W)\) 想成一个立方体,图中蓝色标出的区域就是"计算统计量并同时应用统计量"的范围。

所有归一化层做的事情本质上一样:先归一化、再学习缩放与平移参数;唯一区别在于统计量是在输入数据的哪个子集上计算的。能读懂 Group Norm 论文那张蓝色阴影示意图,就等于理解了所有归一化层。Channel(通道)即深度方向,即每个空间位置上取值的个数。

三、Dropout:训练时制造随机,测试时全部启用

Dropout(随机失活)是内建在模型架构中的正则化技术。核心做法:在训练阶段的每次前向传播中,随机把该层一部分输出激活值置为 0;唯一的超参数是丢弃概率 \(p\)(0.5 最常见,0.25 也常用),每个训练步丢弃哪些值完全随机、不需要人为选择。测试阶段则不再丢弃任何值,但要把激活值乘以 \(p\),保证送入下一层的数值量级与训练时一致——否则训练时每个节点只见到一半输入,测试时突然见到全部,数值幅度会暴增,出现很奇怪的行为。反向传播层面,被置零的位置梯度也为 0,与 ReLU 类似:计算图中那些路径不需要回传,被丢弃激活所关联的权重在该步不会被更新(实现上还可以用掩码直接跳过计算,反正 0 乘任何数都是 0)。

为什么 dropout 有效?老师坦言这更多是经验性的发现、理论并不完备,但可以建立一个很好的直觉:

深入

强迫网络学习冗余表示。设想 CNN 在输出层之前学到一组特征:有耳朵、有尾巴、毛茸茸、有爪子……模型要输出"是猫"的概率。如果没有 dropout,模型可能死记"有耳朵 + 毛茸茸 ⇒ 几乎总是猫"这种特征共现的强相关性而过拟合。加入 dropout 后,这些特征会随机缺席,模型无法过度依赖某几个特定特征,必须学习特征与输出类别之间更宽泛的一组对应关系,从而在新数据上泛化更好。

有同学问"只看到尾巴和爪子,模型怎么判断?"——要点是:dropout 确实让模型在训练集上表现更差(信息被随机删掉了),但在测试集上表现更好(测试时不丢弃、且按 \(p\) 缩放)。这正是"训练上更差、测试上更好"这一正则化通式的又一次体现。也有同学问"能不能直接给图像加噪声?"——可以,这正是后面数据增强要做的事。

四、激活函数:从 sigmoid 到 GELU

激活函数的意义在于引入非线性:卷积、矩阵乘法都只是乘加,全是线性运算,不加非线性再深的堆叠也等价于一个线性层。激活函数统一放置在线性算子之后——全连接层之后、卷积层之后。

4.1 sigmoid 的问题:梯度饱和

历史上 sigmoid 曾是最常用的激活函数,但它有一个致命问题:在输入非常大或非常小(曲线两端)的区域,导数几乎为 0。老师现场提问"sigmoid 在哪些区域梯度极小",同学正确答出"非常负和非常正的取值处"。由于它只在中间很窄的区间有非零梯度,几乎整个输入空间上梯度都接近 0;经过多层 sigmoid 反向传播,梯度层层缩小,越靠近网络早期梯度越小——这正是梯度消失(vanishing gradient)现象,也是 sigmoid 被淘汰的主因。

4.2 ReLU 与它的"半边零"问题

ReLU 在正半区导数恒为 1,彻底消除了正半区的梯度衰减;负半区导数为 0,但"一半输入域梯度为 1、一半为 0"仍远好于 sigmoid 的"几乎处处接近 0"。另外,计算 \(\max(0, x)\) 也比算指数便宜得多。这两点让 ReLU 大受欢迎。但它仍有缺陷:负输入一律零梯度。

4.3 GELU 与 SiLU/SELU:把折角磨平滑

近年的流行做法是在 0 附近构造一段非平坦的平滑曲线,让负半区也有非零梯度。GELU(Gaussian Error Linear Unit)计算 \(x \cdot \Phi(x)\),其中 \(\Phi\) 是标准高斯的累积分布函数(高斯曲线下的面积):\(x\) 很负时 \(\Phi(x)\) 接近 0,\(x\) 很大时 \(\Phi(x)\) 接近 1,因此两端极限恰好收敛到 ReLU,但中间过渡平滑、处处有非零梯度。GELU 是当今 Transformer 的主力激活函数。SiLU/SELU(\(x \cdot \mathrm{sigmoid}(x)\))形态与之非常相似——因为 sigmoid 本身与高斯 CDF 形状接近。老师总结:把这些曲线眯起眼看都很像——"两端渐近于恒等或零、中间平滑过渡",它们都属于同一个大家族。

$$\mathrm{GELU}(x) = x \cdot \Phi(x) \qquad \mathrm{SiLU}(x) = x \cdot \mathrm{sigmoid}(x)$$

五、AlexNet 与 VGG:深度学习爆发的起点

学完所有构件,进入架构巡礼。老师展示了一张经典图:横轴是时间,蓝色柱是各模型在 ImageNet 上的错误率,橙色三角是层数。可以清楚看到:在错误率大幅下降、首次超越人类表现的那个时间点,模型层数出现了爆发式增长——本讲要讲的正是"他们如何做到、经历了哪些设计挑战"。

AlexNet 是第一篇在 ImageNet 上真正奏效的 CNN 论文(此前课程讲过其历史:靠 GPU 完成训练)。老师将它与 VGG 并排画成块状图对比——画架构图时每个方块代表一层或一组堆叠的层,一眼就能看出宏观差异。要点如下:

老师感慨:这些模型里其实只用了三种层(卷积、池化、全连接),却远远胜过此前人们尝试过的任何东西。

5.1 深入:为什么是 3×3?——感受野的算术

深入

老师向全班提问:三个 \(3 \times 3\)、步幅 1 的卷积层堆叠后,第三层激活图 \(A_3\) 中每个值的有效感受野(effective receptive field)是多大?推导可视化非常直观:\(A_3\) 的每个值来自 \(A_2\) 的一个 \(3 \times 3\) 邻域,\(A_2\) 的每个值又来自 \(A_1\) 的 \(3 \times 3\) 邻域,\(A_1\) 的每个值来自输入的 \(3 \times 3\) 邻域。逐层展开:第一层感受野 \(3 \times 3\),第二层 \(5 \times 5\),第三层 \(7 \times 7\)。规律是:\(3 \times 3\)、步幅 1 的卷积每叠一层就把感受野加 2(向上、下、左、右各多看一格)。

结论:三个 \(3 \times 3\) 卷积的感受野 = 一个 \(7 \times 7\) 卷积,而且参数更少。设通道数保持 \(C\),一个 \(7 \times 7\) 卷积的参数量是 \(7^2 C^2 = 49C^2\);而三个 \(3 \times 3\) 卷积是 \(3 \times 3^2 C^2 = 27C^2\)。参数更少的同时还多了两次非线性变换(3 次激活 vs 1 次),表达能力更强——这就是"一堆小滤波器优于一个大滤波器"的道理。

有同学追问:这些设计是"事后解释"还是"先有直觉再设计实验"?老师回答:因架构而异——VGG 作者没有公开谈过这点;但接下来要讲的 ResNet,确实是由一个假设驱动了整个研究方向的典范。

拓展

这条谱系的更早起点是 1998 年 Yann LeCun 的 LeNet-5——手写数字识别时代就已确立"卷积—池化—全连接"的范式,可以说是一切的开端;与 VGG 同期的 GoogLeNet/Inception 则用多分支的 Inception 模块(并排 \(1 \times 1\)、\(3 \times 3\)、\(5 \times 5\) 卷积加池化再拼接)和 \(1 \times 1\) 卷积降维,把参数量压到 AlexNet 的约 \(1/12\)(约 500 万 vs 6000 万)。经典统计:AlexNet 8 层、top-5 错误率约 16.4%;VGG-16 16 层、138M 参数、约 7.3%;VGG-19 达 144M 参数。VGG 虽参数巨大,但结构规整、易复用,成为 2010 年代事实上的标准骨干与特征提取器。

六、ResNet:残差连接与百层网络

6.1 一个反常的实验发现

ResNet 的诞生源于一个经验发现:在普通(plain)CNN上不断叠加层数,20 层模型的测试误差与训练误差都低于 56 层模型。注意训练误差也更低——这说明不是过拟合,而是更深的网络更难优化。理论上说不通:更深的网络表达能力是浅网络的超集——大网络能表示的输入输出映射集合完全包含浅网络的那份,因为我们总可以把多余的层学成恒等映射(identity),让它们"什么都不做",于是深网络至少能和浅网络一样好。问题在于:普通架构下,优化器很难把层学成恒等映射,深网络实际会陷入糟糕的局部极小值。老师特别回答了"是不是训练不够久":不是——无论训练多久,56 层都收敛不到 20 层的水平。

6.2 残差块:把"学映射"改成"学差值"

解决思路:与其直接拟合期望的映射 \(H(x)\),不如拟合残差映射 \(F(x) = H(x) - x\)。具体做法是把输入 \(x\) 抄近路(skip connection),跨过两层 \(3 \times 3\) 卷积,与卷积输出逐元素相加:

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

这样一来,如果这两层"不需要学什么",只要把所有卷积滤波器学成接近,\(F(x) \approx 0\),输出就等于 \(x\)——恒等映射变成了一件轻而易举的事(学零比学恒等容易得多)。更实际的意义是:这些卷积层不再需要从头学出完整的目标映射,只需学"目标输出相对输入的差值(delta)"。需要注意的是,加法要求张量尺寸相同——这正是块内坚持 \(3 \times 3\)、步幅 1、padding 1 卷积的原因(每层尺寸不变);池化之后尺寸变了就无法朴素相加(除非做上采样之类的处理,所以残差块都放在池化之前)。在块的视角下,网络仍逐级获得更高层的抽象表示,只是每一块学的是"从上一层表示到更高层表示的增量"。

残差连接(residual connection / skip connection):把较早层的值复制到较晚的层并逐元素相加。它使深网络"至少不差于"浅网络成为优化上容易达成的事。Transformer 使用残差连接正是出于同样的理由;老师还指出,残差连接实际上让你更高效地利用更多数据,因为更容易建模更大范围的函数。

6.3 整体架构与训练细节

完整 ResNet 就是残差块的堆叠:每块为"\(3 \times 3\) conv → ReLU → \(3 \times 3\) conv",捷径相加后再接 ReLU。若干个块之后做一次降采样并将滤波器数量翻倍——想象一张很"扁宽"的图像在网络中空间越来越小、通道越来越深,最终变成一个向量用于分类。网络开头还有一个相对较大的卷积层,这是纯经验发现:加上它效果更好。ResNet 发布了 18/34/50/101/152 一整个家族:随着层数增加性能持续提升,但收益递减——从 101 到 152 只剩下约 1% 的边际改善。为什么停在 152?老师坦率说不知道作者如何选定这个数字,但推测是性能提升不再明显,同时 GPU 显存也装不下更大的模型了。实践中的通用做法就是:先训最小的,加层数看是否涨点,逐级递进(各深度的模型需要分别独立训练)。历史意义:这是第一次成功训练 100+ 层的模型;此后几乎所有视觉任务都用 ResNet 做骨干,其地位极其重要。

七、经典架构横向对比

架构年份层数参数量ImageNet top-5 错误率关键贡献
LeNet-519985(卷积)约 60K—(MNIST 时代)确立卷积网络范式
AlexNet20128约 60M约 16.4%GPU 训练、ReLU/Dropout,引爆深度学习
VGG-16201416约 138M约 7.3%统一 \(3 \times 3\) 小卷积、加深加宽
ResNet-1522015152约 60M约 3.6%残差连接,首次超越人类、可训百层网络

老师课上展示的趋势图正对应此表:错误率(蓝柱)随时间一路下降,在残差网络处首次超过人类水平;层数(橙色三角)在同一时刻从个位/十几层跃升到上百层。VGG 参数最多但结构最规整;ResNet 用远少于 VGG 的参数达到了深得多的网络和低得多的错误率。

拓展

ResNet 之后的效率路线包括 MobileNet(深度可分离卷积,把标准卷积拆成逐通道卷积 + 逐点卷积,大幅降低计算量,面向移动端)与 EfficientNet(同时缩放深度、宽度和输入分辨率)。2020 年 Vision Transformer(ViT) 把图像切块后交给纯 Transformer 处理,在大数据下超越 CNN;2022 年的 ConvNeXt 则吸收 Transformer 的现代化设计(更大卷积核、LayerNorm、GELU、倒瓶颈结构等)证明纯卷积网络同样能达到 Swin Transformer 级别的表现。残差连接则是所有这些架构(CNN 与 Transformer 皆然)共同的基石。此外,残差连接也让"差值学习"的思想以 LoRA(低秩适配)的形式延伸到了微调领域。

八、训练前的最后一步:权重初始化

架构搭好、准备训练前,还必须给各层权重赋初值,而初值取小了或大了都会出大问题。老师用一个 6 层、4096 维的全连接网络做实验:权重初始化为"单位高斯 × 0.01"(很小),逐层前向传播并绘制每层的均值/标准差——由于数值太小,激活的均值和标准差逐层衰减趋于 0(塌缩)。反过来若系数取得太大(如 0.05),激活逐层放大,到网络末端出现巨大的均值和标准差(爆炸)。对 152 层的 ResNet 来说这会立刻失控。我们希望的是:每一层激活的均值和标准差大致保持不变,这会让优化问题好解得多。为什么太大就爆炸?可以把每层看作"一组大数乘上一组大数"的递推关系:ReLU 不封顶,大方差输入截掉负半后均值不断向正偏移,数值越滚越大。

通解是 Kaiming 初始化(He 初始化)——由 ResNet 的作者何恺明(近十余年引用量最高的计算机视觉研究者之一)提出:权重按标准差正比于 \(\sqrt{2/n}\) 的高斯分布初始化,其中 \(n\) 是该层输入端的维度(全连接层就是输入维数;卷积层则是核尺寸 × 输入通道数,例如 \(3 \times 3 \times C\))。推导虽未展开(可以读原论文),但结论可靠:配合 ReLU,这一公式能保证各层均值和标准差基本恒定,几乎像一个"魔法公式"。判断初始化是否合适的标准就是:各层激活统计量是否恒定——既不向 0 塌缩也不爆炸。若发明了全新的层类型,才需要自己试验各种初始化方案;常规线性层和卷积层直接用 Kaiming 即可。

$$W \sim \mathcal{N}(0, \sigma^2), \quad \sigma = \sqrt{2/n}$$ 其中 \(n\) 为该层的输入维度(卷积层为 \(k \times k \times C_{\mathrm{in}}\))

九、训练实践(一):数据预处理与数据增强

9.1 图像归一化

图像的预处理非常简单:预先计算整个数据集红、绿、蓝三个通道各自的均值和标准差,对每张输入图像执行"减均值、除以标准差"。实践中很多人即使不用 ImageNet 训练也直接借用 ImageNet 的均值和标准差,这是最常见的默认选择;当然统计量本质上是数据集相关的。

9.2 数据增强:聪明的"加噪声"

数据增强(data augmentation)回应了之前那位同学"给图像加噪声"的想法:在把图像送入模型之前施加某种变换,让图像看起来不同但仍可辨认、类别不变。好处是等效扩充了数据集(模型每次见到同一张图的不同版本),提升泛化;代价是训练损失更高——模型更难死记硬背。这是"训练时加随机、测试时平均掉随机"这一正则化通式的又一次应用(与 dropout 同构)。常用增强手段:

测试时增强(test-time augmentation):想要榨干最后一点性能时,对同一张测试图取多个裁剪、缩放、翻转版本分别推理再平均预测,通常能稳定带来 1%~2% 的提升(会有边际递减),几乎适用于任何视觉问题。

十、训练实践(二):迁移学习

现实是:几乎没有人有 ImageNet 级别(百万张)的数据。没有大数据还能训 CNN 吗?能,但要聪明地做。关键洞察来自特征层级:CNN 前几层学到边缘、纹理、小形状等通用特征;把图像一路前传到最后一个分类层之前(如 4096 维或 2048 维向量),同类图像在这个特征空间中 \(L_2\) 距离非常近——可以在这个向量上跑最近邻或线性分类器就得到极好的效果,如同第 2 讲的 KNN,只是把像素换成了深度特征。于是有三种策略:

  1. 冻结局 + 线性探针(linear probe):拿一个 ImageNet(或更大网络级数据集)上的预训练模型,冻结除最后一层外的全部权重,把最后的分类层换成自己数据集的类别数(用 Kaiming 随机初始化),只训练这一层。等效于把冻结网络当作一个"预定义特征提取器"——非常像旧范式里手工特征的角色。适合数据少、但与预训练分布相似的情形。
  2. 微调全部层(fine-tuning):同样替换最后的分类层,但以预训练权重为初始化训练整个网络。数据较多时的最佳选择。老师自己的实践:手头通常只有一两百万样本,就从数十亿级数据预训练的模型出发微调——模型"见过更多数据"、特征提取器更好,微调后又足够贴合自己的问题,比从头训练更好。
  3. 数据分布差异大时:若数据多,可从头训练或试预训练初始化(需实验对比,无保证);若数据又少又分布差异大(如火星照片),最难受的情形——尽量找一个在相近领域预训练的模型再微调。域外泛化(out-of-domain generalization)仍是活跃研究领域,没有万能技术;老师提到语言模型在跨域上普遍泛化得更好。

关于偏差:用策略 2 时模型在与 ImageNet 相似的数据(笔记本电脑、教室、人物等日常物体)上表现最好,火星照片会差很多——预训练数据带来的偏差是确定的。资源方面,PyTorch Image Models(timm) 和 PyTorch 官方仓库提供了大量预训练模型。

拓展

介于"只训最后一层"和"全量微调"之间还有多种折中:只微调部分层(后面若干层)、给不同层设置不同学习率,以及 LoRA(Low-Rank Adaptation)——不改原权重,而是学习每层权重的低秩差值(与残差"学差值"的思想一脉相承),用极少量参数实现全层微调的效果,是大模型时代(Stable Diffusion 微调、LLM 适配)的标准技术,课程 Transformer 一讲可能展开。

十一、训练实践(三):超参数选择与调试

  1. 第一步永远是:先过拟合一个小样本。拿一两个数据点训练,看训练损失能否降到(接近)0。模型连一个样本都记不住,说明代码有 bug 或模型选错了。这同时能帮你找到学习率的合理区间。这是深度学习默认的调试策略。
  2. 粗网格搜索学习率:用几个不同学习率各训一个 epoch 左右,看训练损失曲线,选下降最持续、最陡的那个,再延长训练。之后才轮到其他超参数。
  3. 盯住训练/验证曲线:训练精度还在涨就继续训;若训练损失继续降而验证损失开始上升(过拟合),则要么加强正则化、要么搞更多数据;验证损失的最低点就是当前最好的模型。训练与验证精度还很接近时,可以放心继续训练。
  4. 随机搜索优于网格搜索:设想一个重要超参数维度和一个不重要维度,网格搜索会在不重要维度的取值上浪费大量重复实验;随机搜索则在相同预算下把重要参数的空间探索得更充分。实践中定义好各超参数的范围,随机采样组合,持续训练直到找到最佳模型即可。

本讲要点回顾

上一讲第 5 讲:用 CNN 做图像分类 下一讲第 7 讲:循环神经网络