4. 动量(Momentum):让梯度下降不再“原地踏步”
各位好,欢迎来到第四章。
上一章我们聊了学习率,那个控制步长大小的旋钮。但光有步长还不够,你想想看,如果梯度方向一直在震荡,步子再大也走不快,对吧?
动量(Momentum)就是来解决这个问题的。它不只看当前的梯度,还会把历史梯度也考虑进来。说白了,就是给优化过程加一点“惯性”。
4.1 动量原理:为什么需要“惯性”?
先看一个场景。假设你的损失函数是个狭长的“山谷”,长这样:
标准 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 有预见性。
我的经验:在 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 实际使用建议
说了这么多,到底怎么用?我总结了几条经验:
- 默认从 β=0.9 开始。这是 PyTorch、TensorFlow 等框架的默认值,经过大量验证。
- 如果损失曲线震荡严重,尝试增大 β 到 0.95-0.99。但要注意,β 越大,收敛可能越慢。
- 如果模型不收敛,试试减小 β 到 0.8-0.85。让模型更关注当前梯度。
- Nesterov 优先。除非你发现 Nesterov 导致不稳定,否则用 Nesterov 版本。
- 配合学习率衰减。动量 + 学习率衰减是经典组合,效果通常比单独用好。
一句话总结:动量让梯度下降从“随机漫步”变成“定向滑行”。β 控制滑行的惯性,Nesterov 让滑行更有预见性。两者配合,你的模型会跑得更稳、更快。