深入理解动量(Momentum)算法
动量(Momentum)是深度学习优化器中最重要的概念之一。本文将像拆解手表一样,把每个零件(参数)拿出来仔细分析。
核心公式
动量算法包含两个核心公式:
- 第一步:计算现在的"速度" ()
- 第二步:更新实际的"位置" ()
第一步:计算速度 (The Velocity Update)
这个公式决定了我们这一次要"迈多大一步",以及"往哪个方向迈"。
参数详解
1. (Velocity at time ):当前速度
- 含义: 这是我们在第 步时,准备用来更新参数的累积向量
- 关键点: 它不仅仅是当前的梯度,它是**"过去的余威"加上"现在的推力"的混合体**
2. (Gamma):动量系数 (Momentum Coefficient)
- 常见值: 通常设为 0.9(或者 0.99)
- 物理含义: 摩擦力 或 记忆衰减率
- 通俗解释:
- 它决定了我们要保留多少之前的速度
- 如果 ,意思是:"把上一步速度的 90% 继承下来"
- 如果 ,公式就变成了 ,这就退化成了普通的随机梯度下降(SGD),完全没有惯性
3. :上一时刻的速度
- 含义: 这是我们在上一次更新时计算出来的速度
- 作用: 它代表了之前的方向。如果之前一直在下坡,这个值会很大;如果之前在转弯,这个值会包含之前的方向信息
4. (Eta):学习率 (Learning Rate)
- 含义: 步长,或者说是我们对当前梯度的信任程度
- 物理含义: 这里的 相当于力的作用时间或者推力的大小。它决定了当前的坡度能给速度增加多少新的能量
5. :当前梯度
- 含义: 此时此刻脚下的坡度(加速度)
- 作用: 这是新的动力源。它告诉球:"现在这里比较陡,你得往这边加速!"
深度解析:公式一在做什么?
(假设 )
这个公式在做向量加法:
- 惯性项 ():我想继续沿着原来的方向跑(比如向东)
- 梯度项 ():但是脚下的地形要把我往南推
- 结果 ():我的新方向是东南方向(合力方向)
为什么能抑制震荡?
- 如果上一秒向左,这一秒向右,惯性(向左)和新推力(向右)会互相抵消,导致横向速度变小
- 如果一直向下,惯性(向下)和新推力(向下)会叠加,导致下坡速度越来越快
第二步:更新位置 (The Parameter Update)
算出速度后,这一步就简单了:就是单纯的位移。
参数详解
1. :新的参数值
- 含义: 下一时刻模型的状态(我们在山上的新位置)
2. :旧的参数值
- 含义: 当前模型的状态(现在的落脚点)
3. (减号):逆梯度方向
- 含义: 因为梯度指向"高处",我们要去"低处",所以要减去这个速度向量
物理类比:冰上推箱子
想象你在冰面上推一个箱子(参数 )去目标点(谷底)。
普通 SGD(没有惯性)
- 你看一下地势,推一下箱子
- 因为摩擦力极大,你一停手,箱子立刻停下
- 每一步都完全取决于你现在的推力
动量 SGD(有惯性)
- 冰 面很滑(,阻力小)
- 你推了一下(梯度),箱子开始滑行()
- 下一秒,箱子还在滑(),你虽然发现方向偏了一点,往旁边推了一下(新的梯度),但箱子不会立刻90度大转弯,而是划出一道平滑的弧线,融合了原本的滑行方向和你新推的方向
- 如果你不再推了(梯度为0,比如到了平地),箱子还会靠着惯性继续滑一段距离(有机会冲过平地)
参数直觉总结
| 符号 | 名称 | 作用 | 典型值 |
|---|---|---|---|
| 历史速度 | 记录了历史的行进路线 | - | |
| 当前梯度 | 根据当下的地形微调方向 | - | |
| 动量系数 | 决定刹车灵不灵。 越大,刹车越难,冲劲越猛 | 0.9 | |
| 学习率 | 当前推力的大小 | 0.01 ~ 0.1 |
PyTorch 代码实现
import torch
import torch.nn as nn
import torch.optim as optim
# ==========================================
# 1. 准备工作
# ==========================================
# 假设有一个简单的线性模型 y = wx + b
model = nn.Linear(1, 1)
# 定义超参数(对应刚才的公式)
lr = 0.01 # 学习率 (η, Eta)
gamma = 0.9 # 动量系数 (γ, Gamma)
# ==========================================
# 2. 定义优化器 (关键步骤)
# ==========================================
# 【普通 SGD】
# 对应公式:θ = θ - η * 梯度
optimizer_normal = optim.SGD(model.parameters(), lr=lr)
# 【带动量的 SGD】(Momentum)
# 对应公式:v = γ*v + η*梯度; θ = θ - v
# 只需要加一个参数 momentum=0.9
optimizer_momentum = optim.SGD(model.parameters(), lr=lr, momentum=gamma)
print(f"优化器已创建: {optimizer_momentum}")
完整训练示例
import torch
import torch.nn as nn
import torch.optim as optim
# 创建简单数据
X = torch.randn(100, 1)
y = 2 * X + 1 + 0.1 * torch.randn(100, 1)
# 定义模型
model = nn.Linear(1, 1)
# 带动量的 SGD 优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
criterion = nn.MSELoss()
# 训练循环
for epoch in range(100):
# 前向传播
pred = model(X)
loss = criterion(pred, y)
# 反向传播
optimizer.zero_grad() # 清零梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数(使用动量)
if (epoch + 1) % 20 == 0:
print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}')
print(f'\n学到的参数: w = {model.weight.item():.4f}, b = {model.bias.item():.4f}')
print(f'真实参数: w = 2.0000, b = 1.0000')
动量的优势
- 加速收敛:在相同方向上不断累积速度
- 抑制震荡:在反复波动的方向上相互抵消
- 逃离局部最优:惯性帮助冲过平坦区域
- 更平滑的更新:减少随机梯度带来的噪声