共计 2296 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:标准 BP 梯度下降的局限性
反向传播 (BP) 算法的核心是链式求导法则,其梯度计算可表示为:

$$
\frac{\partial L}{\partial w} = \frac{\partial L}{\partial a_{n}} \cdot \frac{\partial a_{n}}{\partial a_{n-1}} \cdots \frac{\partial a_{2}}{\partial a_{1}} \cdot \frac{\partial a_{1}}{\partial w}
$$
当使用 sigmoid 激活函数时,由于其导数最大值仅为 0.25,多层连乘会导致梯度指数级衰减。例如 5 层网络:
$$
\frac{\partial L}{\partial w} \leq 0.25^5 \cdot \frac{\partial L}{\partial a_{5}} \approx 0.00098\frac{\partial L}{\partial a_{5}}
$$
这解释了为何深层网络容易出现梯度消失现象。更糟糕的是,如果权重初始化过大,sigmoid 会进入饱和区(导数接近 0),导致训练完全停滞。
技术对比:主流优化器效果分析
实验设置
# TensorBoard 记录工具
from torch.utils.tensorboard import SummaryWriter
def test_optimizer(optimizer, name):
writer = SummaryWriter(f'runs/{name}')
model = SimpleNet() # 简单测试网络
criterion = nn.CrossEntropyLoss()
for epoch in range(100):
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
writer.add_scalar('Loss', loss.item(), epoch)
收敛曲线对比
-
SGD with Momentum:
$$
v_t = \gamma v_{t-1} + \eta \nabla_\theta J(\theta)
$$
$$
\theta = \theta – v_t
$$
能缓解震荡但仍有梯度消失风险 -
Adam:
$$
m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t
$$
$$
v_t = \beta_2 v_{t-1} + (1-\beta_2) g_t^2
$$
自适应学习率效果最好但可能早熟
核心实现:PyTorch 优化器进阶技巧
梯度裁剪实现
def train():
optimizer.zero_grad()
loss.backward()
# 关键代码:限制梯度最大 L2 范数为 1.0
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
学习率 warmup 策略
from torch.optim.lr_scheduler import CosineAnnealingLR
scheduler = CosineAnnealingLR(optimizer, T_max=100)
for epoch in range(100):
# 前 5 个 epoch 线性 warmup
if epoch < 5:
lr_scale = (epoch + 1) / 5
for param_group in optimizer.param_groups:
param_group['lr'] = lr_scale * base_lr
scheduler.step()
生产实践关键点
多 GPU 训练梯度聚合
model = nn.DataParallel(model)
# 梯度会自动在 device[0]上聚合
output = model(input)
loss = criterion(output, target)
# 反向传播前需注意:loss = loss.mean() # 各 GPU 返回的 loss 需要平均
loss.backward()
AMP 混合精度陷阱
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
output = model(input)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
# 检查梯度溢出
if scaler.get_scale() < 1:
print("警告:检测到梯度溢出,当前缩放因子:", scaler.get_scale())
避坑指南
-
梯度未清零:
# 错误示范 for i in range(10): loss.backward() # 梯度会累计 # 正确做法 optimizer.zero_grad() loss.backward() -
误用 detach():
# 错误示范(计算图断裂)hidden = lstm(x).detach() # 正确做法(需保留梯度)hidden = lstm(x) -
BatchNorm 状态混淆:
# 训练模式 model.train() # 测试模式必须切换 model.eval()
开放性问题
当 batch size 超过 1 万时,传统梯度方差修正方法(如 Adam 的 $\hat{v}_t = v_t/(1-\beta_2^t)$)可能因样本多样性下降而失效。是否需要重新设计修正系数?建议通过以下实验验证:
- 在不同 batch size 下记录梯度方差
- 对比修正前后的参数更新量分布
- 测试移除修正项对收敛的影响
期待读者分享实验结果!
