深度学习入门站 · 理解深度学习
首页 › 第 11 章

残差网络

网络越深反而越差?残差连接(ResNet)如何让"深"变得可行。
图 11-1:残差网络的跳跃连接
图 11-1:残差网络的跳跃连接

这一章要回答一个特别反直觉的问题:神经网络是不是越深越好?答案竟然是否定的——当网络深到一定程度,训练效果反而会变差,这就是著名的退化问题(degradation)。更妙的是,作者们只加了一条"抄近道"的连线,就把深层网络救活了。这条连线就是残差连接(skip connection)。学完这一章,你会明白为什么"越深越差"、残差连接是怎么让梯度"抄近道"回流的,以及为什么今天几乎所有大模型(包括 Transformer)身上都有它的影子。

越深反而越差:退化问题

上一章我们讲到,把卷积网络从 8 层的 AlexNet 加深到 18 层的 VGG,图像分类准确率明显提升了。于是大家很自然地想:那继续加深到 50 层、100 层呢?实验结果却泼了一盆冷水:性能不升反降。更关键的是,这种下降在训练集上就出现了——也就是说,不是"记太多导致泛化变差"的过拟合,而是深层网络根本训练不动,连训练数据都拟合不好。

为什么训练不动?一个流行的解释是"梯度碎片化"。在纯顺序网络里,输出对第一层的导数是一条长长的链式乘积:

$$\frac{\partial y}{\partial f_1} = \frac{\partial f_4}{\partial f_3} \cdot \frac{\partial f_3}{\partial f_2} \cdot \frac{\partial f_2}{\partial f_1}$$

这个式子意思是:第一层的一点小改动,要穿过后面每一层才能影响到输出。网络越深,这条链越长,中间任何一环的梯度变得不稳定,乘积就乱套了。结果就是:早期层的更新方向每个样本都不一样、毫无规律,梯度下降就像在黑夜里乱走,一步都迈不出去。深层网络的损失表面不再是一个平滑的下坡,而像是一片布满微小山峰的山脉,每一步都可能跳到一个和刚才完全无关的地方。

残差连接:学"增量",不学"全部"

普通的网络层是这样处理的:输入 $x$ 经过一系列变换得到 $f[x]$,然后直接作为输出传给下一层。而残差块只改了一件事——把输入也加回去:

$$y = x + f[x, \phi]$$

这个式子意思是:$f$ 不再负责"从头算出答案",它只需要算出还差多少——也就是输入和理想输出之间的"差",这个差值在数学上就叫残差(residual)。至于输入 $x$ 本身,原封不动地从旁边"绕"过去,直接加到输出上。这条绕行的旁路就是残差连接,也叫跳跃连接(skip connection)。

🍲
生活类比
普通网络层就像厨师每次都要从零做一道完整的新菜;残差块则像"尝一口,然后补调料"——菜已经基本好了,$f$ 只负责判断"还差多少盐、多少糖"。大部分时候答案是"什么都不用加",$f$ 学成 $f[x] \approx 0$ 就行,这比从零学一个复杂变换容易太多了。

把多个残差块串起来展开看,输出其实是输入加上一串小网络的"总和":

$$y = x + f_1[x] + f_2[x + f_1[x]] + f_3[\cdots] + f_4[\cdots]$$

换句话说,深层残差网络并不是一条不可分割的超长流水线,而是许多条长度不同的捷径的组合——有的路径只有一层,有的穿过多层。信号总能找到一条"近路"到达输出。

为什么管用:梯度"抄近道"回流

残差连接最大的好处,藏在反向传播里。还是看输出对 $f_1$ 的导数,这次由于每条路径都可以独立贡献一项,导数变成了:

$$\frac{\partial y}{\partial f_1} = 1 + \frac{\partial f_2}{\partial f_1} + \frac{\partial f_3}{\partial f_2}\frac{\partial f_2}{\partial f_1} + \frac{\partial f_4}{\partial f_3}\frac{\partial f_3}{\partial f_2}\frac{\partial f_2}{\partial f_1}$$

注意最前面那个孤零零的 1!它来自"输入直接加回输出"的那条捷径:无论后面几层的乘积项多么混乱、多么接近 0,梯度都有一条不经过任何中间层的通道,直接流回第一层。链式乘积容易"消失",而这个常数 1 永远不会消失。深层网络终于能收到有效梯度,也就终于训练得动了。

💡
**核心思想**:残差连接 $y = x + f[x]$ 让网络学会"增量"而不是"完整映射"。梯度由此获得一条恒等捷径——不管网络多深,梯度都能"抄近道"直接回流,缓解了深层网络难训练的问题。

ResNet:把残差思想做成冠军

2016 年,何恺明等人把残差块堆成了 152 层的 ResNet,在 ImageNet 图像分类上大获全胜:前五错误率 4.8%,碾压 VGG 的 6.8% 和 AlexNet 的 16.4%,成为最早超越人类水平(5.1%)的网络之一。后来他们还训练出了 200 层的 ResNet。残差连接大概能把能训练的网络深度翻一倍,但更深的网络会碰到新麻烦:把残差加回输入会让激活值的方差逐层翻倍、指数膨胀,所以残差块通常要配上批量归一化(BatchNorm),把每层激活重新拉回均值为 0、方差为 1 的"标准身材"。顺带一提,为了让残差块既能加正数也能加负数,操作顺序也做了调整:先激活、后卷积,而不是常见的先卷积、后激活。

深层网络参数太多怎么办?ResNet 用了瓶颈块:先用 $1\times1$ 卷积把通道数"压缩"下来,中间用 $3\times3$ 卷积处理,最后再用 $1\times1$ 卷积把通道数"还原"回去——就像先把行李压扁,过了安检门再重新撑开,省力又不丢信息。

import torch
import torch.nn as nn

class ResidualBlock(nn.Module):
    """一个最简残差块:f[x] 学增量,x 走捷径直接加回来"""
    def __init__(self, channels):
        super().__init__()
        self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
        self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
        self.relu = nn.ReLU(inplace=True)

    def forward(self, x):
        fx = self.relu(self.conv1(x))   # 处理分支
        fx = self.conv2(fx)
        y = x + fx                      # 残差连接:输入直接加到输出
        return self.relu(y)

x = torch.randn(1, 3, 32, 32)   # 一张 3 通道 32x32 的假图片
block = ResidualBlock(channels=3)
print(block(x).shape)           # 输出形状不变:torch.Size([1, 3, 32, 32])

注意看最后一行:残差块的输出形状和输入完全一样,所以可以一层层无限叠下去——这正是 ResNet 能堆到上百层的前提。

残差思想的"后裔":Transformer 也靠它

如今,残差连接早已成为深度学习的标配,几乎每个现代架构里都有它。Transformer 的每个子层都用"Add & Norm"结构:输出 = 输入 + 子层结果,再归一化——这不就是残差连接的直系后代吗?图像分割常用的 U-Net 把编码器的特征"跳过"整个中间层直接接到解码器;DenseNet 更是让每一层都看到前面所有层的输出。可以说,没有残差连接,就没有今天动辄千亿参数的深模型——它是让"深度"真正变成"力量"的那把钥匙。

🎯 小测验

1. 深层网络在训练集上的误差反而更高,这说明了什么?
2. 残差块 $y = x + f[x]$ 中,$f[x]$ 学习的是什么?
3. 为什么残差连接能让深层网络更容易训练?