ResNet(Residual Neural Network)是2015年由Kaiming He等人提出的革命性深度神经网络架构,在ImageNet竞赛中以3.57%的错误率夺冠,并深刻影响了深度学习的发展方向。
1. 深度神经网络的关键问题
1.1 梯度消失/爆炸问题
在传统深度网络中:
- 反向传播时梯度需要逐层传递
- 梯度值可能指数级减小(消失)或增大(爆炸)
- 导致深层网络难以训练
数学表达:
对于L层网络,第l层的梯度:
∂
L
o
s
s
∂
W
l
=
∂
L
o
s
s
∂
f
L
⋅
∏
k
=
l
L
−
1
∂
f
k
+
1
∂
f
k
⋅
∂
f
l
∂
W
l
\frac{\partial Loss}{\partial W_l} = \frac{\partial Loss}{\partial f_L} \cdot \prod_{k=l}^{L-1} \frac{\partial f_{k+1}}{\partial f_k} \cdot \frac{\partial f_l}{\partial W_l}
∂Wl∂Loss=∂fL∂Loss⋅k=l∏L−1∂fk∂fk+1⋅∂Wl∂fl
当层数L很大时,连乘积项极易变得极小或极大
1.2 网络退化问题
实验发现:
- 并非网络越深性能越好
- 56层网络比20层网络在训练集和测试集上表现都更差
- 这不是过拟合问题(训练误差也更高)
2. ResNet核心创新
2.1 残差学习框架
传统映射:直接学习H(x)
残差映射:学习F(x) = H(x) - x,因此H(x) = F(x) + x
关键优势:
- 当最优映射接近恒等映射时,网络只需使F(x)→0
- 相比学习H(x)→x,学习F(x)→0更容易
2.2 跳跃连接(Shortcut Connection)
实现形式:
y
=
F
(
x
,
{
W
i
}
)
+
x
y = F(x, \{W_i\}) + x
y=F(x,{Wi})+x
两种实现方式:
- 恒等映射(Identity Shortcut):当输入输出维度相同
# PyTorch实现 out = F(x) + x
- 投影映射(Projection Shortcut):当维度不同时
# 使用1×1卷积调整维度 shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride), nn.BatchNorm2d(out_channels) ) out = F(x) + shortcut(x)
2.3 残差块详细结构
基本残差块(Basic Block)
Input
│
├──────────────┐
↓ │
Conv3×3 │
BN │
ReLU │
Conv3×3 │
BN │
│ │
└─────(+)──────┘
│
ReLU
│
Output
参数计算:对于C通道输入输出
参数量 = 3×3×C×C + 3×3×C×C = 18C²
瓶颈残差块(Bottleneck Block)
Input
│
├────────────────┐
↓ │
Conv1×1 (降维至C/4) │
BN │
ReLU │
Conv3×3 │
BN │
ReLU │
Conv1×1 (升维至C) │
BN │
│ │
└──────(+)───────┘
│
ReLU
│
Output
参数计算:对于C通道输入输出
参数量 = 1×1×C×C/4 + 3×3×C/4×C/4 + 1×1×C/4×C = C²/4 + 9C²/16 + C²/4 ≈ 0.81C²
对比:当C=256时,Basic Block参数量≈1.18M,Bottleneck≈0.53M
3. 完整网络架构
3.1 ResNet-34详细结构
Layer Name | Output Size | 34-layer | 参数计算 |
---|---|---|---|
conv1 | 112×112 | 7×7, 64, stride 2 | (7×7×3)×64 = 9,408 |
56×56 | 3×3 max pool, stride 2 | - | |
conv2_x | 56×56 | 3×3, 64 ×3 | (3×3×64)×64×3 = 110,592 |
conv3_x | 28×28 | 3×3, 128 ×4 | 下采样块: (3×3×64)×128 + (1×1×64)×128 = 73,728 + 8,192 |
普通块: (3×3×128)×128×3 = 442,368 | |||
conv4_x | 14×14 | 3×3, 256 ×6 | 下采样块: 类似计算 |
conv5_x | 7×7 | 3×3, 512 ×3 | 类似计算 |
1×1 | global avg pool | - | |
fc | - | 1000-d fc | 512×1000 = 512,000 |
总参数量:约21.8M
3.2 不同版本ResNet配置
Network | Layers | Basic/Bottleneck Blocks | Params (M) | FLOPs (G) |
---|---|---|---|---|
ResNet-18 | 18 | Basic: [2,2,2,2] | 11.7 | 1.8 |
ResNet-34 | 34 | Basic: [3,4,6,3] | 21.8 | 3.6 |
ResNet-50 | 50 | Bottleneck: [3,4,6,3] | 25.6 | 3.8 |
ResNet-101 | 101 | Bottleneck: [3,4,23,3] | 44.5 | 7.6 |
ResNet-152 | 152 | Bottleneck: [3,8,36,3] | 60.2 | 11.3 |
4. 训练细节与技巧
4.1 初始化方法
采用Kaiming初始化(He初始化):
- 针对ReLU的初始化方法
- 权重从N(0, √(2/n))采样,其中n是输入单元数
def init_weights(m):
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
4.2 学习率策略
使用热身学习率(Learning Rate Warmup):
- 前5个epoch线性增加学习率:0 → 初始学习率
- 然后按cosine衰减
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)
4.3 正则化技术
- 权重衰减:0.0001
- BatchNorm:ε=1e-5,momentum=0.1
- 标签平滑(Label Smoothing):0.1
criterion = nn.CrossEntropyLoss(label_smoothing=0.1)
5. 数学原理深入
5.1 残差连接的理论解释
考虑传统网络第l层到第L层的映射:
x
L
=
x
l
+
∑
i
=
l
L
−
1
F
(
x
i
,
W
i
)
x_L = x_l + \sum_{i=l}^{L-1} F(x_i, W_i)
xL=xl+i=l∑L−1F(xi,Wi)
反向传播时梯度:
∂
L
o
s
s
∂
x
l
=
∂
L
o
s
s
∂
x
L
⋅
(
1
+
∂
∂
x
l
∑
i
=
l
L
−
1
F
(
x
i
,
W
i
)
)
\frac{\partial Loss}{\partial x_l} = \frac{\partial Loss}{\partial x_L} \cdot \left(1 + \frac{\partial}{\partial x_l} \sum_{i=l}^{L-1} F(x_i, W_i)\right)
∂xl∂Loss=∂xL∂Loss⋅(1+∂xl∂i=l∑L−1F(xi,Wi))
关键点:
- 梯度包含直接传播项(1)和残差项
- 即使残差项很小,梯度也不会完全消失
5.2 与Highway Networks对比
Highway Networks:
y
=
H
(
x
)
⋅
T
(
x
)
+
x
⋅
(
1
−
T
(
x
)
)
y = H(x) \cdot T(x) + x \cdot (1-T(x))
y=H(x)⋅T(x)+x⋅(1−T(x))
其中T(x)是变换门
ResNet可视为T(x)=1的简化版本,实验表明这种简化反而更有效
6. 现代改进与变体
6.1 ResNeXt (2017)
引入**基数(Cardinality)**概念:
- 在残差块中使用分组卷积
- 超参数:基数C表示并行路径数
class ResNeXtBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, cardinality=32):
super().__init__()
D = out_channels // 2 # 每组通道数
self.conv1 = nn.Conv2d(in_channels, D, kernel_size=1)
self.conv2 = nn.Conv2d(D, D, kernel_size=3, stride=stride,
padding=1, groups=cardinality)
self.conv3 = nn.Conv2d(D, out_channels, kernel_size=1)
def forward(self, x):
out = F.relu(self.conv1(x))
out = F.relu(self.conv2(out))
out = self.conv3(out)
# ... 跳跃连接 ...
6.2 Res2Net (2019)
多尺度残差块:
- 在单个残差块内构建层级残差连接
- 将特征图分成几组,逐级处理
6.3 EfficientNet (2019)
结合ResNet思想与网络缩放:
- 复合缩放深度、宽度和分辨率
- 使用MBConv(含残差连接)
7. 实践应用指南
7.1 迁移学习技巧
-
不同数据集的处理:
- 大数据集:微调所有层
- 小数据集:只微调最后几层
-
学习率设置:
# 预训练层使用较小学习率
optim.SGD([
{'params': model.conv1.parameters(), 'lr': lr*0.1},
{'params': model.fc.parameters(), 'lr': lr}
], momentum=0.9)
7.2 可视化工具
梯度流向分析:
# 注册钩子记录梯度
def backward_hook(module, grad_input, grad_output):
print(f"{module.__class__.__name__} grad norm: {grad_output[0].norm()}")
for layer in model.children():
layer.register_full_backward_hook(backward_hook)
8. 前沿研究方向
-
神经架构搜索(NAS)优化ResNet:
- 自动搜索最优残差连接模式
- 如EfficientNet的MBConv块
-
动态ResNet:
- 根据输入动态调整残差路径
- 如SkipNet、CondConv等
-
跨模态ResNet:
- 将残差思想应用于多模态学习
- 如CLIP中的图像-文本残差连接
ResNet的成功证明了简单而有效的设计可以产生深远影响,其核心思想仍在不断启发新的神经网络架构创新。