共计 1690 个字符,预计需要花费 5 分钟才能阅读完成。
标准 BP 算法的梯度更新缺陷
在传统 BP 算法中,参数更新公式为:
$$\theta_{t+1} = \theta_t – \eta \cdot \nabla J(\theta_t)$$
这种更新方式存在两个典型问题:
- 在峡谷地形(某一维度梯度远大于其他维度)会产生之字形震荡
- 容易陷入局部最优点的 ” 盆地 ” 区域难以跳出
动量因子的物理意义
引入动量因子 (momentum) 后的更新公式:
$$v_t = \gamma v_{t-1} + \eta \cdot \nabla J(\theta_t)$$
$$\theta_{t+1} = \theta_t – v_t$$
其中 $\gamma$ 即动量因子,其物理意义类比物体运动时的惯性:
- 当前更新方向会保留部分历史梯度信息
- 在梯度方向不变的维度加速前进
- 在梯度方向变化的维度抑制震荡
主流动量算法对比
经典 Momentum

- 在峡谷地形能快速沿长轴方向下降
- 但容易在最优解附近 ” overshooting”
Nesterov Accelerated Gradient(NAG)
$$v_t = \gamma v_{t-1} + \eta \cdot \nabla J(\theta_t – \gamma v_{t-1})$$
- 先根据动量项预测下一步位置
- 在预测位置计算梯度
- 对凸函数有更好的理论收敛保证
PyTorch 实现动态调节
class DynamicMomentumOptimizer(torch.optim.Optimizer):
def __init__(self, params, lr=0.01, base_momentum=0.9, max_momentum=0.99):
defaults = dict(lr=lr, base_momentum=base_momentum,
max_momentum=max_momentum)
super().__init__(params, defaults)
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
for group in self.param_groups:
# 动态计算当前 momentum 值
progress = self.state['step'] / self.state['max_steps']
momentum = group['max_momentum'] - (group['max_momentum'] - group['base_momentum']
) * (1 - progress)**2
for p in group['params']:
if p.grad is None:
continue
# 梯度裁剪
grad = torch.nn.utils.clip_grad_norm_(p, 1.0)
# 更新速度项
param_state = self.state[p]
if 'momentum_buffer' not in param_state:
buf = param_state['momentum_buffer'] = torch.zeros_like(p.data)
else:
buf = param_state['momentum_buffer']
buf.mul_(momentum).add_(grad)
# 参数更新
p.data.add_(-group['lr'], buf)
return loss
实验对比分析
在 MNIST 数据集上测试不同动量因子的效果:
| 动量因子 | 收敛步数 | 测试准确率 |
|---|---|---|
| 0.5 | 3200 | 98.2% |
| 0.9 | 2100 | 98.5% |
| 0.99 | 1800 | 98.3% |
与 Adam 优化器的对比:
生产环境注意事项
- 批量归一化耦合:
- BN 层会改变梯度分布
-
建议初始阶段使用较小动量(0.5-0.7)
-
分布式训练同步:
- 各 worker 需同步动量缓冲区
- PyTorch 中需设置
broadcast_buffers=True
诊断思考题
当出现周期性振荡时,可采取以下诊断步骤:
- 可视化不同参数维度的梯度变化
- 检查振荡周期是否与动量衰减周期相关
- 临时调低动量因子观察振荡幅度变化
- 检查是否与学习率过大形成共振效应
最终给出调优建议:
- 初期使用较小动量 (0.5) 稳定探索
- 中后期逐步提升至 0.9-0.95 加快收敛
- 配合学习率 cosine 衰减效果更佳
正文完
