问题与目标
深层网络中,信号和梯度要连续穿过多层变换。它们若逐层缩小,前面的层几乎无法更新;若逐层放大,损失可能剧烈震荡或变成 nan。另一类问题是训练数据表现持续改善,验证表现却已经恶化。
本篇将这些现象与参数初始化、梯度裁剪、权重衰减、BatchNorm、Dropout 和早停对应起来。目标是根据证据选处理方式,而不是把所有技巧同时加到模型。

对数坐标下,连续小于 1 的局部梯度使信号逐层衰减,连续大于 1 则会让梯度迅速放大。
核心概念
梯度消失与梯度爆炸
反向传播会连续相乘各层局部梯度。长期小于 1 会让梯度越来越小,长期大于 1 则可能迅速放大。诊断时不只看总损失,还可记录每层梯度范数:
for name, parameter in model.named_parameters():
if parameter.grad is not None:
print(name, parameter.grad.norm().item())
稳定接近 0 的梯度与某次刚好为 0 含义不同。ReLU 可以因当前输入为负而产生零梯度,应结合多个批次和多层趋势判断。
初始化要与激活特性配合
- Xavier/Glorot 初始化常与 Tanh 等近似对称激活配合。
- He/Kaiming 初始化考虑 ReLU 将负区间截断的特性。
- 偏置通常可从 0 开始,但不应将同一层的所有权重设为相同值,否则神经元容易始终学习相同功能。
PyTorch 的常用网络层已有默认初始化。不需要为了“显式”而无条件覆盖,自定义初始化应对应可观察问题。
梯度裁剪限制更新信号
loss.backward()
total_norm = torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
按总范数裁剪可以防止某一批次梯度过大,RNN 等序列模型中尤其常见。它只限制后果,不会自动修复过大学习率、异常输入或错误损失。
BatchNorm、Dropout 和权重衰减解决不同问题
| 方法 | 主要作用 | 训练/推理差异 |
|---|---|---|
| BatchNorm | 标准化批次激活并学习缩放平移 | 训练用批统计,推理用运行统计 |
| Dropout | 训练时随机置零部分激活 | 推理时关闭 |
| 权重衰减 | 限制参数过度增大 | 通过优化器更新生效 |
| 早停 | 验证表现不再改善时终止 | 保留最佳验证检查点 |
小批次下 BatchNorm 的统计可能不稳定。序列与 Transformer 中常见的 LayerNorm 按单个样本的特征维归一化,不依赖批次统计。
可运行实现
import torch
from torch import nn
torch.manual_seed(7)
class StableNet(nn.Module):
def __init__(self) -> None:
super().__init__()
self.layers = nn.Sequential(
nn.Linear(20, 32),
nn.BatchNorm1d(32),
nn.ReLU(),
nn.Dropout(p=0.3),
nn.Linear(32, 2),
)
nn.init.kaiming_normal_(self.layers[0].weight, nonlinearity="relu")
nn.init.zeros_(self.layers[0].bias)
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
return self.layers(inputs)
model = StableNet()
optimizer = torch.optim.AdamW(model.parameters(), lr=0.01, weight_decay=1e-3)
loss_fn = nn.CrossEntropyLoss()
features = torch.randn(64, 20) * 4
labels = torch.randint(0, 2, (64,))
model.train()
optimizer.zero_grad()
loss = loss_fn(model(features), labels)
loss.backward()
gradient_norm = nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
print("loss:", round(loss.item(), 4))
print("norm before clipping:", round(float(gradient_norm), 4))
sample = features[:4]
model.train()
train_output_1 = model(sample)
train_output_2 = model(sample)
model.eval()
with torch.inference_mode():
eval_output_1 = model(sample)
eval_output_2 = model(sample)
print("train outputs equal:", torch.allclose(train_output_1, train_output_2))
print("eval outputs equal:", torch.allclose(eval_output_1, eval_output_2))
训练模式中 Dropout 每次随机丢弃不同激活,两次输出通常不同;评估模式关闭 Dropout 并使用 BatchNorm 运行统计,同一输入应得到一致输出。
将处理方式与现象对应
- 梯度范数偶发性过大:先查数据和学习率,再评估梯度裁剪。
- 训练准确率高、验证低:考虑更多数据、减小模型、权重衰减、Dropout 或早停。
- 深层网络前几层梯度长期极小:检查激活、初始化和网络结构。
- 训练正常而推理波动:先检查
eval()和输入预处理,不要立即重训。
常见问题与排查
- BatchNorm 放在维度不对的位置:
BatchNorm1d(C)需要正确识别特征/通道维。 - 批次只有一条样本时 BatchNorm 报错或不稳定:调整批次、丢弃最后小批或选用不依赖批统计的归一化。
- Dropout 比例过高:模型可能连训练集也学不好,应根据曲线调整。
- 梯度裁剪之后仍然
nan:追查首个非有限值出现的层和输入,裁剪不能修复无效数学运算。 - 同时加入多个方法后分数改善:无法确定哪项有效,应保留基线并逐项对照。
小结
初始化帮助信号在网络刚开始时稳定传递,梯度裁剪限制极端更新,BatchNorm 调整中间激活分布,Dropout、权重衰减和早停主要应对过拟合。每个方法都有自己的现象与边界,训练稳定来自可观察、可对照的调整过程。
许可协议:CC BY-NC 4.0
更新于 1 小时前
觉得文章有帮助?点个赞吧!
0 条评论


