4. 动量(Momentum):让梯度下降不再“原地踏步”

各位好,欢迎来到第四章。

上一章我们聊了学习率,那个控制步长大小的旋钮。但光有步长还不够,你想想看,如果梯度方向一直在震荡,步子再大也走不快,对吧?

动量(Momentum)就是来解决这个问题的。它不只看当前的梯度,还会把历史梯度也考虑进来。说白了,就是给优化过程加一点“惯性”。

4.1 动量原理:为什么需要“惯性”?

先看一个场景。假设你的损失函数是个狭长的“山谷”,长这样:

标准 SGD 在山谷中的震荡路径 起点 终点 梯度方向来回震荡,收敛极慢 动量可以平滑掉这些震荡

标准 SGD 在陡峭方向上来回震荡,在平缓方向上却挪不动步子。为什么会这样?因为梯度在陡峭方向正负交替,导致更新方向反复横跳。

动量怎么做的?它维护一个速度变量 v,每次更新时:

v = β * v + (1 - β) * g
θ = θ - lr * v

其中 β 是动量系数,g 是当前梯度。你看,v 累积了历史梯度的加权平均。如果某个方向一直有梯度,v 就会越来越大;如果方向来回变,v 就会被抵消掉。

核心思想:动量相当于给梯度加了一个低通滤波器,高频震荡被抑制,低频趋势被保留。

4.2 标准动量 vs Nesterov 动量

这里有个有意思的变种。我刚开始用动量时,只知道标准版本。后来在训练一个图像分类模型时,发现收敛总差那么一点。同事提醒我:“试试 Nesterov?”

两者的区别,说白了就是“看问题的角度”不同:

对比维度 标准动量 Nesterov 动量
计算梯度的位置 当前位置 θ 预估未来位置 θ + β * v
更新公式 v = βv + (1-β)g
θ = θ - lr·v
v = βv + (1-β)g(θ+βv)
θ = θ - lr·v
直观理解 根据历史趋势往前走 先往前探一步,再根据“看到的”调整
收敛速度 较快 通常更快,尤其在凸优化中
震荡抑制 好 更好,有“刹车”效果

Nesterov 的“先看一步”有什么好处?我举个例子。假设你正冲向一个悬崖,标准动量因为惯性还会往前冲一段;Nesterov 呢,它先“看一眼”前面是悬崖,就会提前减速。说白了,Nesterov 有预见性。

标准动量 vs Nesterov 动量对比 标准动量 Nesterov 动量 标准动量(蓝)震荡幅度较大,容易“冲过头” Nesterov(红)因为提前“看”了未来梯度 震荡更小,路径更平滑

我的经验:在 CV 任务中,Nesterov 通常比标准动量快 10-20% 收敛。但在 NLP 任务里,标准动量有时更稳定。建议先试 Nesterov,如果震荡严重再切回标准动量。

4.3 动量系数的选择

动量系数 β 控制着历史梯度的“记忆长度”。β 越大,历史梯度的影响越持久;β 越小,模型越“健忘”。

常见的取值:

β 值 有效记忆步数 特点 适用场景
0.9 ≈ 10 步 最常用,平衡性好 大多数任务默认值
0.99 ≈ 100 步 记忆长,平滑强 梯度噪声大的任务
0.5 ≈ 2 步 记忆短,响应快 非平稳目标、在线学习
0.0 0 步 退化为标准 SGD 对比实验基线

这里有个细节。β=0.9 时,有效记忆步数大约是 1/(1-β) = 10 步。也就是说,当前更新主要受最近 10 步梯度的影响。β=0.99 时,记忆扩展到 100 步。

避坑指南:我曾经在一个强化学习任务中把 β 设成 0.999,结果模型完全不动了。因为历史梯度累积太大,新梯度根本改变不了方向。β 不是越大越好,要根据任务调。

4.4 实际使用建议

说了这么多,到底怎么用?我总结了几条经验:

  1. 默认从 β=0.9 开始。这是 PyTorch、TensorFlow 等框架的默认值,经过大量验证。
  2. 如果损失曲线震荡严重,尝试增大 β 到 0.95-0.99。但要注意,β 越大,收敛可能越慢。
  3. 如果模型不收敛,试试减小 β 到 0.8-0.85。让模型更关注当前梯度。
  4. Nesterov 优先。除非你发现 Nesterov 导致不稳定,否则用 Nesterov 版本。
  5. 配合学习率衰减。动量 + 学习率衰减是经典组合,效果通常比单独用好。

一句话总结:动量让梯度下降从“随机漫步”变成“定向滑行”。β 控制滑行的惯性,Nesterov 让滑行更有预见性。两者配合,你的模型会跑得更稳、更快。