共计 2504 个字符,预计需要花费 7 分钟才能阅读完成。
为什么需要残差结构?
刚接触深度学习的同学可能会发现:当网络层数增加到 20 层以上时,模型性能不升反降。这种现象在 1 维信号(如音频、ECG)处理中尤为明显,其本质是 梯度消失问题。
通过链式法则计算梯度时,连续的小梯度相乘会导致梯度指数级衰减。例如对于一个 L 层的网络:
$$\frac{\partial \mathcal{L}}{\partial \mathbf{W}1} = \frac{\partial \mathcal{L}}{\partial \mathbf{f}_L} \cdot \prod}^L \frac{\partial \mathbf{fl}{\partial \mathbf{f}$$}} \cdot \frac{\partial \mathbf{f}_1}{\partial \mathbf{W}_1
当激活函数的导数 $|\frac{\partial \mathbf{f}l}{\partial \mathbf{f}| < 1$ 时,深层网络的梯度会趋于 0。}
残差连接如何解决问题
残差块通过引入 跳跃连接(Shortcut Connection),将原始输入直接传递到输出端。其数学表达为:
$$\mathbf{y} = \mathcal{F}(\mathbf{x}, {W_i}) + \mathbf{x}$$
这使得梯度可以直接通过恒等映射路径回传:
$$\frac{\partial \mathcal{L}}{\partial \mathbf{x}} = \frac{\partial \mathcal{L}}{\partial \mathbf{y}} \cdot \left(1 + \frac{\partial \mathcal{F}}{\partial \mathbf{x}}\right)$$
即使 $\frac{\partial \mathcal{F}}{\partial \mathbf{x}}$ 很小,梯度也不会完全消失。
两种实现方式
- Identity Mapping:当输入输出维度相同时,直接相加
- Projection Shortcut:维度不同时通过 1 ×1 卷积调整通道数

PyTorch 实现详解
下面我们实现一个完整的 1D 残差模块,包含卷积、BN、ReLU 的标准组合:
import torch
import torch.nn as nn
class BasicBlock1D(nn.Module):
def __init__(self, in_channels, out_channels, stride=1):
super().__init__()
self.conv1 = nn.Conv1d(
in_channels, out_channels,
kernel_size=3, stride=stride,
padding=1, bias=False
)
self.bn1 = nn.BatchNorm1d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = nn.Conv1d(
out_channels, out_channels,
kernel_size=3, stride=1,
padding=1, bias=False
)
self.bn2 = nn.BatchNorm1d(out_channels)
# 处理维度不匹配的情况
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv1d(
in_channels, out_channels,
kernel_size=1, stride=stride,
bias=False
),
nn.BatchNorm1d(out_channels)
)
def forward(self, x):
residual = self.shortcut(x) # [B, C, L] -> [B, C', L']
out = self.conv1(x) # [B, C, L] -> [B, C', L']
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out) # 保持维度不变
out = self.bn2(out)
out += residual # 残差相加
out = self.relu(out)
return out
关键点说明
- 维度匹配 :通过
stride和1x1 卷积确保主分支与 shortcut 的输出维度一致 - BN 层位置:每个卷积后立即接 BN 层,这是稳定训练的关键
- ReLU 应用:仅在残差相加后使用一次激活函数
实验验证
在 MIT-BIH 心律失常数据集上的对比实验显示:
- 无残差连接的 34 层网络:测试准确率 82.3%
- 带残差连接的 34 层 ResNet:测试准确率 89.7%
三大常见错误
- 遗漏 BN 层:残差分支中缺少 BN 会导致训练初期梯度爆炸
# 错误示例
self.shortcut = nn.Conv1d(in_channels, out_channels, 1, stride)
- stride 不匹配:下采样时主分支和 shortcut 的 stride 必须一致
# 正确做法
self.conv1 = nn.Conv1d(..., stride=stride)
self.shortcut = nn.Sequential(nn.Conv1d(..., stride=stride), ...
)
- padding 计算错误:1D 卷积的 padding 应保持特征图尺寸
# 对于 kernel_size=3
padding = (kernel_size - 1) // 2 # 得到 1
拓展思考
当输入输出维度差异较大时,可以:
- 使用多个 1 ×1 卷积级联实现维度变换
- 在 shortcut 中添加平均池化层降低序列长度
- 尝试不同的残差组合方式(如 ResNeXt 的基数扩展)
建议读者在自己的时序数据(如传感器信号、股票价格)上尝试调整:
- 残差块的堆叠数量
- 通道数的扩展比例
- 是否使用瓶颈结构(Bottleneck)
结语
通过本文的代码实践,相信大家已经掌握了 1D ResNet 的核心思想。残差连接这种看似简单的设计,实则是深度学习发展史上的重要突破。建议初学者多动手修改网络结构,观察训练过程中的梯度变化,这对理解深层神经网络的工作原理大有裨益。
