跳到主要内容

深入理解动量(Momentum)算法

动量(Momentum)是深度学习优化器中最重要的概念之一。本文将像拆解手表一样,把每个零件(参数)拿出来仔细分析。

核心公式

动量算法包含两个核心公式:

  1. 第一步:计算现在的"速度" (vtv_t)
  2. 第二步:更新实际的"位置" (θt+1\theta_{t+1})

第一步:计算速度 (The Velocity Update)

vt=γvt1惯性/历史+ηθJ(θ)当前推力v_{t} = \underbrace{\gamma \cdot v_{t-1}}_{\text{惯性/历史}} + \underbrace{\eta \cdot \nabla_\theta J(\theta)}_{\text{当前推力}}

这个公式决定了我们这一次要"迈多大一步",以及"往哪个方向迈"。

参数详解

1. vtv_t (Velocity at time tt):当前速度

  • 含义: 这是我们在第 tt 步时,准备用来更新参数的累积向量
  • 关键点: 它不仅仅是当前的梯度,它是**"过去的余威"加上"现在的推力"的混合体**

2. γ\gamma (Gamma):动量系数 (Momentum Coefficient)

  • 常见值: 通常设为 0.9(或者 0.99)
  • 物理含义: 摩擦力记忆衰减率
  • 通俗解释:
    • 它决定了我们要保留多少之前的速度
    • 如果 γ=0.9\gamma = 0.9,意思是:"把上一步速度的 90% 继承下来"
    • 如果 γ=0\gamma = 0,公式就变成了 vt=η梯度v_t = \eta \cdot \text{梯度},这就退化成了普通的随机梯度下降(SGD),完全没有惯性

3. vt1v_{t-1}:上一时刻的速度

  • 含义: 这是我们在上一次更新时计算出来的速度
  • 作用: 它代表了之前的方向。如果之前一直在下坡,这个值会很大;如果之前在转弯,这个值会包含之前的方向信息

4. η\eta (Eta):学习率 (Learning Rate)

  • 含义: 步长,或者说是我们对当前梯度的信任程度
  • 物理含义: 这里的 η\eta 相当于力的作用时间或者推力的大小。它决定了当前的坡度能给速度增加多少新的能量

5. θJ(θ)\nabla_\theta J(\theta):当前梯度

  • 含义: 此时此刻脚下的坡度(加速度)
  • 作用: 这是新的动力源。它告诉球:"现在这里比较陡,你得往这边加速!"

深度解析:公式一在做什么?

vt=0.9vt1+0.1梯度v_{t} = 0.9 \cdot v_{t-1} + 0.1 \cdot \text{梯度}

(假设 γ=0.9,η=0.1\gamma=0.9, \eta=0.1

这个公式在做向量加法

  1. 惯性项 (γvt1\gamma v_{t-1}):我想继续沿着原来的方向跑(比如向东)
  2. 梯度项 (η梯度\eta \text{梯度}):但是脚下的地形要把我往南推
  3. 结果 (vtv_t):我的新方向是东南方向(合力方向)

为什么能抑制震荡?

  • 如果上一秒向左,这一秒向右,惯性(向左)和新推力(向右)会互相抵消,导致横向速度变小
  • 如果一直向下,惯性(向下)和新推力(向下)会叠加,导致下坡速度越来越快

第二步:更新位置 (The Parameter Update)

θt+1=θtvt\theta_{t+1} = \theta_t - v_{t}

算出速度后,这一步就简单了:就是单纯的位移

参数详解

1. θt+1\theta_{t+1}:新的参数值

  • 含义: 下一时刻模型的状态(我们在山上的新位置)

2. θt\theta_t:旧的参数值

  • 含义: 当前模型的状态(现在的落脚点)

3. - (减号):逆梯度方向

  • 含义: 因为梯度指向"高处",我们要去"低处",所以要减去这个速度向量

物理类比:冰上推箱子

想象你在冰面上推一个箱子(参数 θ\theta)去目标点(谷底)。

普通 SGD(没有惯性)

  • 你看一下地势,推一下箱子
  • 因为摩擦力极大,你一停手,箱子立刻停下
  • 每一步都完全取决于你现在的推力

动量 SGD(有惯性)

  • 冰面很滑(γ=0.9\gamma = 0.9,阻力小)
  • 你推了一下(梯度),箱子开始滑行(vtv_t
  • 下一秒,箱子还在滑(0.9vt10.9 \cdot v_{t-1}),你虽然发现方向偏了一点,往旁边推了一下(新的梯度),但箱子不会立刻90度大转弯,而是划出一道平滑的弧线,融合了原本的滑行方向和你新推的方向
  • 如果你不再推了(梯度为0,比如到了平地),箱子还会靠着惯性继续滑一段距离(有机会冲过平地)

参数直觉总结

符号名称作用典型值
vt1v_{t-1}历史速度记录了历史的行进路线-
θJ(θ)\nabla_\theta J(\theta)当前梯度根据当下的地形微调方向-
γ\gamma动量系数决定刹车灵不灵。γ\gamma 越大,刹车越难,冲劲越猛0.9
η\eta学习率当前推力的大小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')

动量的优势

  1. 加速收敛:在相同方向上不断累积速度
  2. 抑制震荡:在反复波动的方向上相互抵消
  3. 逃离局部最优:惯性帮助冲过平坦区域
  4. 更平滑的更新:减少随机梯度带来的噪声

相关链接