第 11 章 残差网络(Residual networks)
本章一句话:当网络越叠越深、性能反而下降时,残差(跳跃)连接 \(y=x+f(x)\) 让每一层只学“增量”、并给梯度开一条直达通道,配合批归一化(BatchNorm)稳住信号方差,就能训练上百甚至上千层的网络。
读这章的收获:明白为什么单纯堆深度会失败(梯度破碎),理解残差块如何把网络变成“浅网络的集成”从而让梯度顺畅,搞清残差网络为何会让方差指数爆炸、BatchNorm 怎么补救(及其训练/推理差异),并认识 ResNet、DenseNet、U-Net 三大常见架构。
本章速览
- 深度悖论:第 10 章里加层能提升性能(VGG 19 层 > AlexNet 8 层),但层数继续加到 56 层反而比 20 层更差——而且训练集和测试集都变差,说明问题出在难训练,不是过拟合。
- 梯度破碎(shattered gradients):网络越深,早期层一个微小改动就会让输出梯度剧烈、不可预测地变化,损失曲面像“密密麻麻的小山尖”,梯度下降寸步难行。
- 残差/跳跃连接:让每层的输出加回自己的输入,\(x_{k+1}=x_k+f_k[x_k]\)。每层只学一个增量变化,于是要求输出尺寸与输入一致。
- 解开(unravel)网络 = 集成视角:把残差网络展开,输出等于输入加上一堆长短不一的小网络之和;梯度里始终有个恒等项 \(I\),外加许多短链,所以不再破碎。
- 方差爆炸:每个残差块把(不相关的)两支信号相加,方差翻倍 → 随深度指数增长,前向数值溢出、反向梯度爆炸。
- 批归一化(BatchNorm):按批统计把每个激活重新中心化、再缩放,使方差只随深度线性增长;还顺带平滑损失曲面(可用更大学习率)、注入噪声起正则作用。训练用批统计、推理用全数据集的固定统计。
- 三大架构:ResNet(瓶颈残差块堆出 200+ 层)、DenseNet(用拼接代替相加,复用所有前层)、U-Net(编码-解码 + 跳跃连接,做图像分割)。
- 为何有效:深度不是唯一原因——更浅更宽的残差网络常能打平更深更窄的;真正的功劳可能是“浅网络集成 + 更平滑的损失曲面”。
11.1 顺序处理的问题(Sequential processing)
到目前为止我们见过的所有网络都是顺序处理的:每一层接收上一层的输出,处理后再交给下一层。一个三隐层网络可以写成:
\[ \begin{aligned} h_1 &= f_1[x,\phi_1] \\ h_2 &= f_2[h_1,\phi_2] \\ h_3 &= f_3[h_2,\phi_3] \\ y &= f_4[h_3,\phi_4] \end{aligned} \]
其中 \(x\) 是输入,\(y\) 是输出,\(h_1,h_2,h_3\) 是中间隐层,\(f_k[\cdot,\phi_k]\) 负责处理(在标准网络里是“线性变换 + 激活函数”,在卷积网络里是“卷积 + 激活函数”,参数 \(\phi_k\) 就是权重/偏置或卷积核)。因为处理是串行的,也可以把整个网络看成一串嵌套函数:
\[ y = f_4\Big[f_3\big[f_2[f_1[x,\phi_1],\phi_2],\phi_3\big],\phi_4\Big] \]
图示直觉:把它想象成一条传送带,数据从左到右依次经过 \(f_1\to f_2\to f_3\to f_4\),前一站的产出直接喂给下一站,没有任何旁路。
11.1.1 顺序处理的局限
原则上想加多少层都行。第 10 章也确实看到:卷积网络加层能提升性能(VGG 19 层胜过 AlexNet 8 层)。但继续加层,图像分类性能反而下降:一个 56 层的卷积网络在 CIFAR-10 上不如 20 层的。关键在于——训练集和测试集都变差。这说明问题不是“深网络泛化不了”,而是“深网络根本难训练”。
这个现象至今没有被完全理解,但有一个有说服力的猜想:刚初始化时,改动早期层的参数,会让损失梯度发生不可预测的剧变。即便用了合适的初始化(第 7.5 节),单点的梯度本身是合理的(不爆炸也不消失);可“导数”假设的是无穷小的参数改动,而优化算法走的是有限步长。任何合理步长都可能一步跨到一个梯度完全不相关的新位置——损失曲面看起来像“一大片密密麻麻的小山尖”,而不是一个光滑、好下降的大碗。算法因此无法像在平滑曲面上那样稳步前进。
这一猜想有实证支持。对“单输入单输出”的网络观察输出对输入的梯度 \(\partial y/\partial x\):
- 浅网络:改变输入时,梯度变化缓慢;相邻输入的梯度高度相关。
- 深网络:输入一点微小变化,梯度就面目全非;用自相关函数衡量,相邻梯度的相关性迅速跌到零。
这就是梯度破碎(shattered gradients) 现象。它之所以出现,是因为网络越深,早期层的改动会以越来越复杂的方式影响输出。以式 (11.1) 为例,输出 \(y\) 对第一层 \(f_1\) 的导数是一长串连乘:
\[ \frac{\partial y}{\partial f_1}=\frac{\partial f_2}{\partial f_1}\frac{\partial f_3}{\partial f_2}\frac{\partial f_4}{\partial f_3} \]
当我们改动决定 \(f_1\) 的参数时,由于 \(f_2,f_3,f_4\) 本身都是从 \(f_1\) 算出来的,这一串导数全都会在略微不同的位置取值。于是每个训练样本上更新后的梯度都可能完全不同,损失函数变得“脾气暴躁”、极难优化。
11.2 残差连接与残差块(Residual connections and residual blocks)
残差连接(residual connection),又叫跳跃连接(skip connection),是在计算路径上开一条旁路:把每层 \(f[\cdot]\) 的输入直接加回它的输出。仿照式 (11.1),残差网络写成:
\[ \begin{aligned} h_1 &= x + f_1[x,\phi_1] \\ h_2 &= h_1 + f_2[h_1,\phi_2] \\ h_3 &= h_2 + f_3[h_2,\phi_3] \\ y &= h_3 + f_4[h_3,\phi_4] \end{aligned} \]
每行右边第一项就是残差连接。用通式写就是
\[ x_{k+1}=x_k+f_k[x_k,\phi_k] \]
每个 \(f_k\) 学的是对当前表示的一个加性增量——所以它的输出必须和输入同尺寸。这种“输入 + 处理结果”的组合叫一个残差块(residual block) 或残差层。
图示直觉:数据走两条路。一条是“主干旁路”(恒等映射,直接把 \(x\) 搬过去),一条是“支路”(\(f_k\) 做点加工),最后两路相加。\(f_k\) 只需负责“在原表示上修修补补”,而不是从头重造。
“解开”网络:残差网络 ≈ 浅网络的集成
把上面的式子层层代入,可以把整个网络展开成一个大式子(为简洁省略参数):
\[ \begin{aligned} y = \;& x + f_1[x] \\ &+ f_2\big[x+f_1[x]\big] \\ &+ f_3\Big[x+f_1[x]+f_2\big[x+f_1[x]\big]\Big] \\ &+ f_4[\cdots] \end{aligned} \]
这叫解开(unravel)网络。可以看出:最终输出 = 输入 + 若干个大小不一的小网络之和。一种解读是——残差连接把原网络变成了这些小网络的“集成(ensemble)”,把它们的输出加起来得到结果。等价地,也可以把它看成输入到输出之间有很多条不同长度的路径(4 个残差块就有 16 条路径),每条路径经过的变换数目不同。
这种结构对梯度极其友好。输出对第一层 \(f_1\) 的导数变成:
\[ \frac{\partial y}{\partial f_1}=I+\frac{\partial f_2}{\partial f_1}+\frac{\partial f_3}{\partial f_1}+\frac{\partial f_4}{\partial f_1}+\cdots \]
注意右边那个恒等项 \(I\):它意味着第一层参数的改动会直接贡献到输出,而不必非得穿过所有后续层。再加上一堆短链导数的贡献(短路径上的梯度通常更乖),残差网络因此大大缓解了梯度破碎。
11.2.1 残差块里的操作顺序
残差块里的 \(f[\cdot]\) 必须含一个非线性激活(如 ReLU),否则整个网络退化成线性。但在典型层里 ReLU 放在最后,输出非负——这样每个残差块只能往上加正值。为避免这种偏向,常把顺序反过来:先激活、再做线性变换,让残差块既能加正也能加负。代价是:如果输入全为负,开头的 ReLU 会把信号整个清零,所以网络开头通常先放一个线性变换(而非残差块)打底。实际中一个残差块里常含好几层处理,但一般以线性变换收尾。
11.2.2 残差连接能让网络多深?
加残差连接,大约能把“可有效训练的深度”翻一倍才开始退化。但我们还想更深。要弄清残差连接为什么不能让深度无限加大,必须看两件事:前向传播时激活的方差怎么变,以及反向传播时梯度的幅度怎么变。这就引出下一节。
11.3 残差网络中的梯度爆炸(Exploding gradients in residual networks)
第 7.5 节讲过:初始化至关重要。若不小心,前向传播中间值会指数级增大或缩小,反向梯度也会爆炸或消失。He 初始化专为 ReLU 设计:偏置 \(\beta\) 设零,权重 \(\Omega\) 取均值 0、方差 \(2/D_h\) 的正态分布(\(D_h\) 是上一层隐单元数),从而让每层前后的方差保持不变。
在残差网络里,我们不必担心方差或梯度消失——因为总存在一条直达通路让每层直接贡献到输出。但问题反过来了:即使残差块内部用 He 初始化,前向值仍会随深度指数级增大。
原因很简单:每个残差块把“处理结果”加回“输入”。两支信号各带(互不相关的)波动,相加时方差相加。在 ReLU + He 初始化下,残差块内部的处理让方差保持不变,于是和输入一相加,方差就翻倍——随残差块数目指数增长。这很快就会撑爆浮点精度(前向),反向梯度也同理爆炸。
所以即便用了 He 初始化,残差网络仍有前向不稳定、梯度爆炸的问题。一个直接的补救是:用 He 初始化,然后把每个残差块的合并输出乘以 \(1/\sqrt{2}\),恰好抵消翻倍。但更常用的做法,是批归一化。
图示直觉:把方差想成滚雪球。普通残差网络每滚一层雪球大小翻倍(指数爆炸);乘 \(1/\sqrt 2\) 相当于每层把雪球削回原大小;而 BatchNorm(下节)则让雪球每层只增加“一个单位”,变成线性增长。
11.4 批归一化(Batch normalization)
批归一化(BatchNorm) 的做法是:对每个激活 \(h\),先按一个批次 \(\mathcal{B}\) 内的统计量把它标准化,再缩放和平移,使其均值、方差变成训练中学到的值。
第一步,算出批内的经验均值 \(m_h\) 和标准差 \(s_h\):
\[ m_h=\frac{1}{|\mathcal{B}|}\sum_{i\in\mathcal{B}}h_i,\qquad s_h=\sqrt{\frac{1}{|\mathcal{B}|}\sum_{i\in\mathcal{B}}(h_i-m_h)^2} \]
第二步,用它们把这批激活标准化为均值 0、方差 1:
\[ h_i\leftarrow\frac{h_i-m_h}{s_h+\epsilon}\qquad \forall i\in\mathcal{B} \]
其中 \(\epsilon\) 是防止除零的小常数(万一一批里 \(h_i\) 全相等、\(s_h=0\))。第三步,再用可学习的缩放 \(\gamma\) 与平移 \(\delta\) 调回去:
\[ h_i\leftarrow\gamma h_i+\delta\qquad \forall i\in\mathcal{B} \]
经此一变,激活在整批上均值为 \(\delta\)、标准差为 \(\gamma\),而 \(\gamma,\delta\) 都是训练时学出来的。
几个工程要点:
- BatchNorm 对每个隐单元独立进行。一个 \(K\) 层、每层 \(D\) 个隐单元的全连接网络,就有 \(KD\) 个 \(\delta\) 和 \(KD\) 个 \(\gamma\)。在卷积网络里,统计量是跨批次和跨空间位置一起算的,\(K\) 层、每层 \(C\) 通道则有 \(KC\) 个 \(\delta\) 与 \(KC\) 个 \(\gamma\)。
- 训练 vs. 推理的差异(重要):测试时只来一个样本,没有“一批”可供统计。解决办法是——把 \(m_h\)、\(s_h\) 改成在整个训练集上计算(而非某个批),并冻结在最终网络里使用。
11.4.1 BatchNorm 的代价与好处
代价:BatchNorm 让网络对“缩放某个激活的权重和偏置”不敏感——若把它们翻倍,激活翻倍、估计的 \(s_h\) 也翻倍,标准化又把这变化抵消掉。于是会出现一大族产生相同效果的权重/偏置(冗余),再加上每个单元多出的 \(\gamma,\delta\) 两个参数,模型略显臃肿、有点低效。但它带来的好处更值:
- 稳定前向传播:若把 \(\delta\) 初始化为 0、\(\gamma\) 初始化为 1,则每个输出激活初始方差为 1。在残差网络里方差仍会随新加入的扰动增加,但变成线性增长——第 \(k\) 层在已有方差 \(k\) 上只加 1 个单位(对比 11.3 节的指数翻倍)。一个副作用是:初始化时靠后的层贡献很小、接近恒等映射,网络“有效深度”一开始较浅;训练推进后,网络可以调大靠后层的 \(\gamma\),从而自己控制有效深度。
- 可用更大学习率:理论与实验都表明 BatchNorm 让损失曲面及其梯度变化更平滑(即减少梯度破碎)。曲面更可预测,就能用更大的学习率,而更大学习率往往能提升测试性能。
- 正则化:BatchNorm 注入了噪声——某个样本的归一化依赖于同批其它成员,每次迭代的批组成不同,归一化的量也随之抖动。这种噪声有助于泛化。
11.5 常见残差架构(Common residual architectures)
残差连接如今是深度学习流水线的标配。下面看三个代表性架构。
11.5.1 ResNet
残差块最早就是用在做图像分类的卷积网络里,由此得名残差网络(ResNet)。在 ResNet 里,一个标准残差块依次是:BatchNorm → ReLU → 3×3 卷积,再把这套序列重复一遍,最后加回输入。
对很深的网络,参数量会大得离谱。瓶颈残差块(bottleneck block) 用三个卷积更省参数:先用 1×1 卷积降通道,再用 3×3 卷积在小表示上处理,最后用 1×1 卷积把通道升回原数——这样仍能在 3×3 区域上整合信息,但参数少得多。
ResNet-200 含 200 层,整体结构类似 AlexNet/VGG,但用瓶颈块替代普通卷积层:随网络加深周期性地降低空间分辨率、同时增加通道数(用步长 2 的卷积降分辨率;用补零或额外 1×1 卷积升通道)。网络开头是一个 7×7 卷积 + 下采样,结尾是全连接层映到长度 1000 的向量,再过 softmax 得到类别概率。它在 ImageNet 上 top-5 错误率 4.8%、top-1 错误率 20.1%,胜过 AlexNet(16.4% / 38.1%)和 VGG(6.8% / 23.7%),并且是最早超过人类水平(top-5 约 5.1%) 的网络之一。不过它生于 2016 年,早已不是最强;写作时该任务上最佳模型的 top-1 错误率已降到约 9.0%,而如今榜首都是基于 Transformer(见第 12 章)。
11.5.2 DenseNet
残差块的做法是把处理结果加回输入。另一种思路是把处理结果与原信号拼接(concatenate) 起来。拼接会让表示变大(卷积网络里就是通道数增多),但可以再接一个 1×1 卷积把尺寸压回去——这样模型可以选择相加、加权和或更复杂的组合方式。
DenseNet 就用拼接:某一层的输入是前面所有层输出的拼接;处理出新表示后,又把它和之前的表示拼在一起传给下一层。如此一来,早期层对输出有直接贡献,损失曲面表现良好。
代价是通道数(及处理它们所需的参数)越拼越多,所以实际上只能维持几层;缓解办法是在 3×3 卷积前用 1×1 卷积先降通道。在卷积网络里还会周期性下采样,而不同空间尺寸之间无法拼接,所以拼接链会在下采样处断开、从更小的表示开始新链。DenseNet 在图像分类上能与 ResNet 一较高下,相同参数量下甚至更好——大概是因为它能更灵活地复用早期层的处理结果。
图示直觉:DenseNet 像“滚动累积的文件夹”——每经过一层就把新产出叠进文件夹,后面的层永远能翻到所有前面的成果。
11.5.3 U-Net 与沙漏网络
第 10.5.3 节讲过一种做语义分割的编码-解码(沙漏) 结构:编码器不断下采样,使感受野变大、整合全图信息;解码器再上采样回原图大小,最终在每个像素上输出类别概率。这种结构有个毛病:中间那个低分辨率表示必须“记住”所有高分辨率细节,结果才会精确。如果用残差连接把编码器的表示直接搬给解码器对应层,就不必硬记了。
U-Net 正是这样一个编码-解码架构:把编码器里每个尺度上最后的表示拼接到解码器里同尺度的第一个表示(编码器用普通卷积下采样,解码器用转置卷积上采样)。原版用了“valid”卷积,每过一个 3×3 卷积空间尺寸就缩小两像素,所以上采样后的表示比编码器里的对应物略小,需要裁剪后再拼接;后来的实现改用补零就免去了裁剪。U-Net 是全卷积的,训练后可以跑任意大小的图像。它最初是为医学图像分割设计的,现已广泛用于计算机图形与视觉,还是扩散模型(第 18 章)的关键部件。
沙漏网络(hourglass) 与 U-Net 类似,但在跳跃连接里再做几层卷积,并且把结果相加(而非拼接)回解码器。把多个沙漏块串起来就是堆叠沙漏网络,它在局部与全局视角间反复切换,常用于姿态估计:网络为每个关节预测一张“热力图(heatmap)”,热力图的峰值就是该关节的估计位置。
11.6 残差连接为何如此有效?(Why do nets with residual connections perform so well?)
残差网络能训练深得多的网络——ResNet 甚至能扩到 1000 层还训得动。最初大家把性能提升归功于深度,但有两条证据反驳了这一观点:
- 更浅更宽常常打平更深更窄:在参数量相当时,层数更少、但每层通道更多的残差网络,有时反而更好。
- 超长路径的梯度传不动:在“解开”后的网络里,很长路径上的梯度在训练中其实传不有效。换句话说,一个很深的残差网络,行为上更像一堆较浅网络的组合。
目前的看法是:残差连接本身就有独立价值,而不只是“让网络能更深”。一个有力佐证是——在极小值附近,带跳跃连接的残差网络损失曲面,比去掉跳跃连接的同款网络更平滑、更可预测。曲面更平滑,就更容易学到一个泛化良好的好解。
图示直觉:把损失曲面想成地形。去掉跳跃连接,地形坑坑洼洼、布满悬崖,找不到稳定的谷底;加上跳跃连接,地形被“熨平”成一个开阔的盆地,优化器轻松滑到底,且对参数的小扰动也更鲁棒。
本章小结
无限增加网络深度,会让图像分类的训练和测试性能双双下降。原因很可能是:早期层参数的损失梯度,相对于更新步长来说变化太快、太不可预测(梯度破碎),导致优化无法稳步前进。
残差连接把处理后的表示加回它自己的输入。于是每层既直接、又间接地贡献到输出——梯度不必非穿过所有层,损失曲面也更平滑。残差网络因此不再有梯度消失问题。
但它带来新麻烦:前向传播中激活方差指数级增大,反向也对应梯度爆炸。常见解法是加批归一化:它按批补偿经验均值与方差,再用学到的 \(\gamma,\delta\) 缩放平移;只要参数初始化得当,就能训练非常深的网络。有证据表明残差连接和 BatchNorm 都让损失曲面更平滑(从而能用更大学习率),而批统计的随机抖动还带来一份正则化。
残差块已被广泛融入卷积网络,让网络更深、图像分类性能相应提升。它的变体包括用拼接复用所有前层的 DenseNet,以及把残差连接引入编码-解码模型的 U-Net。
关键术语对照
| 中文 | English | 一句话含义 |
|---|---|---|
| 残差 / 跳跃连接 | Residual / Skip connection | 把层的输入加回其输出,开一条直达旁路 |
| 残差块 | Residual block | “输入 + 处理结果”的组合单元,\(x_{k+1}=x_k+f_k[x_k]\) |
| 梯度破碎 | Shattered gradients | 深网络中相邻梯度迅速变得互不相关,难优化 |
| 解开网络 / 集成视角 | Unraveling / Ensemble view | 展开后输出 = 输入 + 多个长短不一小网络之和 |
| 批归一化 | Batch normalization (BatchNorm) | 按批统计标准化激活,再用学到的 \(\gamma,\delta\) 缩放平移 |
| He 初始化 | He initialization | 为 ReLU 设计的权重初始化,保持各层方差不变 |
| 瓶颈残差块 | Bottleneck block | 用 1×1 降通道 → 3×3 → 1×1 升通道,省参数 |
| ResNet | ResNet | 用残差块堆出的深层图像分类网络 |
| DenseNet | DenseNet | 用拼接而非相加,复用所有前层输出 |
| U-Net | U-Net | 编码-解码 + 跳跃连接,常用于图像分割 |
| 沙漏 / 堆叠沙漏网络 | Hourglass / Stacked hourglass | 局部-全局反复切换,常用于姿态估计 |
| 内部协变量偏移 | Internal covariate shift | BatchNorm 的最初动机(后被实证质疑) |
返回目录 | 上一章:第 10 章 卷积网络 | 下一章:第 12 章 Transformer