前言
直接从 Deep Learning 概览跳到 ResNet 会有一点跳跃,因为 ResNet 不只是一个 CNN 结构,它还依赖正向传播、反向传播和梯度流这些训练机制。如果你还不熟悉“网络是函数复合”“损失如何反传到参数”这些概念,可以先看正向传播与反向传播:神经网络到底如何学习。
ResNet(Residual Network,残差网络)是经典视觉网络中绕不开的模型,因为它解决的不是“怎么设计一个更复杂的卷积层”,而是一个更基础的问题:网络变深以后,为什么反而更难训练?
直觉上,深层网络至少不应该比浅层网络差。假设一个 20 层网络已经能学到不错的函数,那么 56 层网络完全可以让后面 36 层近似恒等映射,理论上应该复现 20 层网络的效果。但在 ResNet 论文之前,实验中经常观察到相反现象:网络加深后,训练误差也会上升。这不是单纯的过拟合,因为过拟合通常表现为训练误差低、测试误差高;这里的问题是训练集本身都拟合不好。
这个现象通常被称为退化问题(Degradation Problem)。ResNet 的核心贡献,就是用一种非常简单的结构改写了深层网络的优化目标。
先看普通网络在学什么
设某一组卷积层想学习的目标函数是:
$$ H(x) $$这里的 $x$ 是输入特征图,$H(x)$ 是这几层网络最终应该输出的特征。普通卷积网络会直接让堆叠的层去拟合 $H(x)$。
ResNet 的想法是:不要直接学 $H(x)$,而是学习它和输入 $x$ 之间的差值:
$$ F(x) = H(x) - x $$于是目标输出可以改写成:
$$ H(x) = F(x) + x $$这个 $F(x)$ 就叫残差映射(Residual Mapping),而旁边那条把 $x$ 直接加回输出的路径,就是跳跃连接(Skip Connection) 或 Shortcut Connection。
普通网络的压力在于:每一组层都必须直接学出目标映射 $H(x)$。而 ResNet 把这个任务拆成两部分:主分支只学习“相对输入还需要改什么”,shortcut 分支直接把输入送到输出端。
为什么“学差值”更容易
残差连接最漂亮的地方在于:如果某几层暂时学不到有用变化,它们只需要让 $F(x) \approx 0$,整个模块就近似变成恒等映射:
$$ y = F(x) + x \approx x $$这比让一堆非线性卷积层自己精确拟合 $H(x)=x$ 要容易得多。换句话说,ResNet 给深层网络提供了一条“什么都不做也别添乱”的默认路径。
这解释了为什么深层 ResNet 更容易优化:新增的层不必一开始就学出复杂表示,它们可以先退回到接近恒等映射,再在训练中逐渐学习对任务有帮助的残差修正。
梯度为什么更容易传回去
残差块的前向传播可以写成:
$$ y = F(x, W) + x $$其中 $W$ 表示残差分支里的卷积参数。如果先用标量直觉来看,反向传播时,损失函数 $L$ 对输入 $x$ 的梯度可以理解为:
$$ \begin{aligned} \frac{\partial L}{\partial x} &= \frac{\partial L}{\partial y} \left( 1 + \frac{\partial F(x, W)}{\partial x} \right) \end{aligned} $$如果把 $x$ 和 $y$ 看成向量,更严谨的写法会把 $1$ 换成恒等矩阵 $I$,并用 Jacobian 描述 $\partial F / \partial x$。这部分属于矩阵微积分,已经拆到 线性代数进阶:矩阵微积分如何服务反向传播 里,这里只保留 ResNet 所需的结论。
这个式子的重点不是说 ResNet 会“彻底消除梯度消失”,而是说它提供了一条更直接的梯度通路。即使残差分支里的梯度暂时不稳定,梯度仍然可以通过加法中的恒等项向前层传播。
这也是 ResNet 和普通深层 CNN 的关键区别:普通网络中,每一层都像一道必须穿过的门;ResNet 中,信息和梯度多了一条可以直接跨过去的路。
一个残差块长什么样
最基础的 ResNet 残差块通常由两层 $3 \times 3$ 卷积组成。下图是自绘结构图,不依赖外部版权图片:
如果输入和输出的通道数、空间尺寸一致,shortcut 可以直接使用恒等映射。但如果需要改变通道数或下采样,就不能简单地把 $x$ 加回来,因为两个张量形状不同。这时通常使用 $1 \times 1$ 卷积做投影:
$$ y = F(x, W) + W_s x $$其中 $W_s$ 是 shortcut 分支上的投影参数。
BasicBlock 与 Bottleneck
ResNet 常见有两类残差块。
BasicBlock 用两个 $3 \times 3$ 卷积,结构直接,常见于 ResNet-18 和 ResNet-34。
Bottleneck 用 $1 \times 1$、$3 \times 3$、$1 \times 1$ 三层卷积。第一个 $1 \times 1$ 卷积用于降维,中间的 $3 \times 3$ 卷积负责空间特征提取,最后一个 $1 \times 1$ 卷积再升维。ResNet-50、ResNet-101 和 ResNet-152 通常使用 Bottleneck,因为它能在网络很深时控制计算量。
可以把 Bottleneck 理解成一种“先压缩、再处理、再展开”的结构:
| 结构 | 主要用途 | 常见模型 |
|---|---|---|
| BasicBlock | 结构简单,适合较浅网络 | ResNet-18、ResNet-34 |
| Bottleneck | 控制计算量,适合很深网络 | ResNet-50、ResNet-101、ResNet-152 |
用 PyTorch 写一个 BasicBlock
下面是一个简化版 BasicBlock。它保留了 ResNet v1 的核心结构:卷积分支负责学习残差,shortcut 分支负责对齐维度,最后做逐元素相加。
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicBlock(nn.Module):
def __init__(self, in_channels: int, out_channels: int, stride: int = 1):
super().__init__()
self.conv1 = nn.Conv2d(
in_channels,
out_channels,
kernel_size=3,
stride=stride,
padding=1,
bias=False,
)
self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(
out_channels,
out_channels,
kernel_size=3,
stride=1,
padding=1,
bias=False,
)
self.bn2 = nn.BatchNorm2d(out_channels)
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(
in_channels,
out_channels,
kernel_size=1,
stride=stride,
bias=False,
),
nn.BatchNorm2d(out_channels),
)
else:
self.shortcut = nn.Identity()
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = self.shortcut(x)
out = self.conv1(x)
out = self.bn1(out)
out = F.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = out + residual
return F.relu(out)这段代码里最关键的是:
residual = self.shortcut(x)保存或变换原始输入。out = out + residual完成残差连接。- 当尺寸不一致时,
1x1 Conv让 shortcut 分支和主分支的输出形状对齐。
ResNet 为什么能做到 152 层
ResNet-152 并不是靠单纯堆更多卷积层取胜,而是靠残差块让深层网络的优化问题变得更接近“在已有表示上做修正”。这带来三个结果:
- 恒等映射更容易表示:新增层可以先学到接近 $F(x)=0$ 的状态,不会明显破坏已有表示。
- 梯度传播更顺畅:shortcut 提供直接路径,降低深层训练时梯度衰减带来的优化难度。
- Bottleneck 控制计算量:很深的网络仍然可以在可接受的计算成本下训练。
不过,不能把 ResNet 的成功只归因于一条 shortcut。Batch Normalization、ReLU、合理初始化、大规模数据和训练策略同样重要。残差连接解决的是深层网络优化中的关键瓶颈,但它不是脱离其他训练技术单独生效的魔法。
ResNet v1 与 Pre-activation ResNet
原始 ResNet 常被称为 ResNet v1,它的典型顺序是:
后来提出的 Pre-activation ResNet 把 BN 和 ReLU 放到卷积之前:
这样做的目标是让 shortcut 路径更接近真正的恒等映射,进一步改善极深网络中的信息流。入门理解 ResNet 时,先掌握 v1 的残差思想已经足够;等开始读更深的架构论文时,再比较 pre-activation 的细节会更自然。
总结
ResNet 的核心不是“网络越深越好”,而是:当我们想把网络做深时,必须让深度带来的优化困难可控。
普通网络让每一组层直接学习 $H(x)$;ResNet 改成学习残差 $F(x)=H(x)-x$,再通过 shortcut 把输入加回去。这个改写让恒等映射更容易表示,也为梯度提供了更直接的传播路径。因此,ResNet 才能把 CNN 推向 50 层、101 层甚至 152 层这样的深度,并成为后续视觉网络设计的重要基础。
参考
- Deep Residual Learning for Image Recognition - Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun
- Identity Mappings in Deep Residual Networks - Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun
- 正向传播与反向传播:神经网络到底如何学习 - 本站深度学习前置理论补充
- 线性代数进阶:矩阵微积分如何服务反向传播 - 本站矩阵微积分补充