共计 2460 个字符,预计需要花费 7 分钟才能阅读完成。
梯度消失问题的本质
当神经网络层数加深时,使用 sigmoid 激活函数会导致梯度在反向传播时呈指数级衰减,这是因为 sigmoid 的导数最大值为 0.25。根据链式法则,多层梯度连乘后数值可能趋近于零,使得浅层参数无法有效更新。这种现象在超过 15 层的传统 CNN 中尤为明显,严重限制了模型的深度和性能。

残差连接的核心思想
传统 CNN 的梯度流动可以表示为:
$$\frac{\partial L}{\partial x_l} = \frac{\partial L}{\partial x_{l+1}} \cdot \frac{\partial F(x_l, W_l)}{\partial x_l}$$
而 ResNet 引入跳跃连接后:
$$\frac{\partial L}{\partial x_l} = \frac{\partial L}{\partial x_{l+1}} \cdot (1 + \frac{\partial F(x_l, W_l)}{\partial x_l})$$
这个 ”1+” 项保证了梯度至少能以恒等路径回传,从根本上避免了梯度消失。实验表明,在 34 层网络上,ResNet 的梯度幅值比传统 CNN 高 2 - 3 个数量级。
关键实现细节
残差块结构对比
-
BasicBlock(适合浅层网络):
class BasicBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm2d(out_channels) # 处理维度变化的跳跃连接 self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride), nn.BatchNorm2d(out_channels) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) # 关键跳跃连接 return F.relu(out) -
Bottleneck(适合深层网络):
class Bottleneck(nn.Module): def __init__(self, in_channels, out_channels, stride=1, expansion=4): super().__init__() mid_channels = out_channels // expansion self.conv1 = nn.Conv2d(in_channels, mid_channels, kernel_size=1) self.bn1 = nn.BatchNorm2d(mid_channels) self.conv2 = nn.Conv2d(mid_channels, mid_channels, kernel_size=3, stride=stride, padding=1) self.bn2 = nn.BatchNorm2d(mid_channels) self.conv3 = nn.Conv2d(mid_channels, out_channels, kernel_size=1) self.bn3 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride), nn.BatchNorm2d(out_channels) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = F.relu(self.bn2(self.conv2(out))) out = self.bn3(self.conv3(out)) out += self.shortcut(x) return F.relu(out)
训练效果对比
在 CIFAR-10 数据集上的实验表明:
- 训练曲线(20 个 epoch):
- 传统 34 层 CNN 最终准确率:68.2%
- ResNet-34 最终准确率:82.7%
- 梯度热力图显示:
- ResNet 的浅层梯度幅值维持在 1e- 4 级别
- 传统 CNN 的浅层梯度幅值衰减至 1e- 7 以下
实践中的关键技巧
- 维度匹配方案 :
- 当跳跃连接两端通道数不同时,使用 1 ×1 卷积进行升维 / 降维
-
stride>1 时需要在 shortcut 路径同步下采样
-
小数据集优化 :
- 添加 Dropout 层(概率 0.2-0.5)
- 使用更强的数据增强(CutMix、AutoAugment)
- 降低初始学习率(通常设为 0.01)
延伸思考
虽然残差连接解决了梯度消失,但当网络深度超过 1000 层时:
1. 前向传播的噪声积累问题凸显
2. GPU 显存限制导致 batch size 过小
在 NLP 领域的迁移应用中:
1. Transformer 中的 Add & Norm 本质是残差连接
2. 可尝试在 RNN 的隐藏状态间添加跳跃连接
残差思想启示我们:有时候让信息 ” 抄近道 ”,反而能走得更远。这种设计哲学正在深刻影响各类深度学习架构的发展。
