如果你读过《前向传播是什么》,应该还记得那个比喻——神经网络像一条流水线:数据从输入层进,每层加工一下交给下一层。听起来“层越多,加工越细,效果越好”对吧?但 2015 年何恺明团队撞上了一件反直觉的怪事:把网络从 20 层堆到 56 层,精度反而更差了。注意这不是过拟合(训练集上也在变差),而是网络干脆“学不动了”——这个现象有个专门的名字,叫退化问题(degradation problem)。
他们的解决方案,就是这一篇的主角——残差连接(residual connection,又叫 skip connection / 捷径连接)。就这一个改动,让网络能一路堆到 152 层还照常训练,催生它的论文《Deep Residual Learning for Image Recognition》也成了 AI 史上被引用最多的论文之一。
一句话定义:残差连接是在网络层旁边架一条“捷径”,把这一层的输入
图 1:退化问题——普通网络堆到 56 层训练误差反而比 20 层更高;引入残差后,层越深误差越低。
主类比:在原稿上改,而不是重写
想象一份文档依次过 10 位编辑。每位编辑有两种工作方式:
- 没有捷径:每位编辑把上一位的稿子整个重写一遍。哪怕只想改一个标点,也得把全文重新誊抄——10 个人誊抄下来,错别字越攒越多,原意都走样了。
- 有捷径:每位编辑只在原稿上标改动(画个圈、加个批注),原稿本身一路原封不动往下传。最终稿子 = 原稿 + 所有批注。
残差连接走的就是第二条路。原稿就是
公式:朴素得惊人,却改变了深度学习
残差连接的核心公式,朴素得让人意外:
符号逐项解读:
—— 这一层的输入(上一层传过来的东西,对应类比里的“原稿”)。 —— 这一层主干路径学到的东西(对应“批注”)。 可以是一两层卷积或全连接,也可以是 Transformer 里的一个注意力子层。 —— 残差连接(捷径):把输入绕过 直接加到输出上。注意是“加”不是“拼接”——所以 和 的形状必须一样大;不一样时,主干会额外加一个小变换把 调成和 同形状再相加(ResNet 里的常见做法是 卷积,这篇了解即可)。 —— 这一层的输出,交给下一层。
为什么这能解决退化? 直觉就一句:深层最差也是恒等映射(也就是原样输出
它还顺手缓解了另一个老大难——梯度消失。训练时梯度沿
符号逐项解读:
—— 偏导数符号, 读作"y 对 x 的偏导数",表示 变一点时 跟着变多少,即反向传播里的梯度。
那个
图 2:反向传播时,主干路径的梯度 ∂F/∂x 逐层缩放、容易衰减;残差捷径每层白送一个 +1,给误差信号留了一条永不归零的直通路。
手算一遍:捷径到底加了什么
假装某一层的输入是一个 2 维特征
没有残差连接(普通层):输出就是
原始信号
有残差连接:把输入直接加回来:
自检: 结果
用 PyTorch 写,这条捷径就是一行加法:
y = self.main(x) + x # F(x) + x:主干 + 捷径图 3:把数字画成条形图:输出 y 正好是输入 x 各维「挪一点点」的结果,残差学的是微调量而非从零构造。
它在 AI 里的位置:无处不在的“标准件”
残差连接如今是深度学习的基础设施,几乎所有现代网络都在用:
- ResNet(它诞生的地方):让图像识别网络从二十几层跨到 152 层,拿下 2015 年 ImageNet 冠军,从此“深”才真的深得起来。
- Transformer / 大模型:你用的 ChatGPT、Claude 背后都是 Transformer,它的每一个子层——无论自注意力(self-attention)还是前馈网络(FFN)——外面都套了一圈残差连接,再接一层归一化,业内合称“Add & Norm”。没有它,几十上百层的 Transformer 根本训不动。
- 几乎所有现代架构:从图像(ResNet 家族)、检测分割,到语音、推荐,再到如今的大语言模型,“层 + 残差”几乎是默认搭配。
一句话总结它在 AI 体系里的位置:它是让“超深网络”从“理想”变成“现实”的那块关键拼图——和注意力机制、反向传播等概念紧密关联,可以把它当成理解现代深度网络结构的必经一站。
小结
残差连接是在网络层旁边架的一条捷径:把输入
完整代码
下面定义一个最小的残差块(两层全连接当主干 + 一条捷径),和普通块对比,跑一次前向 + 一步训练。重点看 ResidualBlock.forward 里 out + x 那一行——它就是
import torch
import torch.nn as nn
# ===== 残差块:主干 F(x) 是两层全连接,捷径把 x 直接加回来 =====
class ResidualBlock(nn.Module):
def __init__(self, dim):
super().__init__()
# 主干 F(x):两个全连接层,保持输出维度=输入维度,才能和 x 相加
self.fc1 = nn.Linear(dim, dim)
self.fc2 = nn.Linear(dim, dim)
def forward(self, x):
F = torch.relu(self.fc1(x)) # 第一层:加权求和 + ReLU
F = self.fc2(F) # 第二层:再过一层全连接(不激活,准备相加)
out = F + x # ★ 残差连接:y = F(x) + x,就这一行
return torch.relu(out) # 相加后再过 ReLU(ResNet 残差块的常规做法)
# ===== 对比:普通块(没有捷径,纯堆两层)=====
class PlainBlock(nn.Module):
def __init__(self, dim):
super().__init__()
self.fc1 = nn.Linear(dim, dim)
self.fc2 = nn.Linear(dim, dim)
def forward(self, x):
out = torch.relu(self.fc1(x)) # 第一层
out = self.fc2(out) # 第二层
return torch.relu(out) # 没有 + x:输入 x 在这里就被冲掉了
# ===== 跑一次前向 + 一步训练,看残差块怎么工作 =====
torch.manual_seed(42)
block = ResidualBlock(dim=4) # 4 维输入
x = torch.randn(2, 4) # 假数据:2 条样本,每条 4 维
out = block(x) # 前向传播:y = F(x) + x
print("残差块输出形状:", out.shape) # 和输入 x 形状一致,能继续堆更多块
# 演示一步训练,体会梯度能顺着捷径 +1 流回输入
target = torch.randn(2, 4)
loss_fn = nn.MSELoss()
opt = torch.optim.SGD(block.parameters(), lr=0.1)
loss = loss_fn(out, target) # 算损失
opt.zero_grad()
loss.backward() # 反向传播:梯度沿 out=F+x 流回,∂out/∂x = ∂F/∂x + 1
opt.step() # 更新权重
print(f"训练一步后的 loss: {loss.item():.4f}")
print("输入 x:", x[0].detach())
print("输出 y:", out[0].detach(), "(≈ x 加上主干 F(x) 的一点微调)")运行后你会看到输出 loss.backward() 能顺畅跑完,背后就是那条捷径在给梯度“托底”。
参考资料
- Deep Residual Learning for Image Recognition - He et al., 2015 https://arxiv.org/abs/1512.03385
- 残差网络(ResNet)- 《动手学深度学习》 https://zh.d2l.ai/chapter_convolutional-modern/resnet.html
- Residual neural network - Wikipedia https://en.wikipedia.org/wiki/Residual_neural_network