4. 优化器选择:SGD、Momentum、Adam、RMSprop 的收敛特性对比与实战场景

优化器选不对,模型练到崩溃。这话我入行第三年就深有体会了。

当时调一个图像分类网络,Loss 死活降不下去。我换了学习率、改了网络结构,折腾了两天。最后同事路过看了一眼,说:「你试试换 Adam?」结果换了之后,半小时就收敛了。嗯,从那以后我再也不敢小看优化器的选择。

这一章,咱们就把四个最常用的优化器掰开揉碎讲清楚。不搞花架子,直接上干货。

4.1 四大优化器的核心差异

先看一张对比表,心里有个全局印象。

优化器 核心思想 自适应学习率 动量机制 典型收敛速度 内存开销
SGD 纯梯度下降,无任何加速 否 否 慢 低
Momentum 引入历史梯度方向,加速收敛 否 是(单动量) 中等 低
RMSprop 按参数历史梯度平方调整学习率 是 否 快 中
Adam 动量 + 自适应学习率,集大成者 是 是(双动量) 很快 高

说白了,SGD 是「裸奔」,Momentum 是「加了惯性」,RMSprop 是「给每个参数配了独立油门」,Adam 则是「惯性 + 独立油门」全都要。

4.2 各优化器深入解析

4.2.1 SGD:最朴素的基线

SGD(随机梯度下降)是所有优化器的老祖宗。它的更新公式极其简单:

θ = θ - η * ∇L(θ)

其中 η 是学习率,∇L(θ) 是当前梯度。

优点就一个:简单、稳定、可解释性强。很多论文里的理论分析都是基于 SGD 做的。

缺点也很明显:

  • 收敛慢:尤其在鞍点附近,梯度接近零,直接卡死
  • 震荡严重:在陡峭方向上反复横跳
  • 学习率敏感:调不好就发散或者龟速
避坑指南:我曾经在训练一个 NLP 模型时,直接用 SGD 跑了 100 个 epoch,Loss 还在 2.3 附近晃悠。后来换成 Adam,20 个 epoch 就降到 0.8。不是说 SGD 不好,而是它真的不适合所有场景。

4.2.2 Momentum:给梯度加个惯性

Momentum 的思路很直观——你想想看,一个小球从山坡滚下来,它不会因为遇到一个小坑就停住,而是会借着惯性冲过去。

更新公式:

v = β * v + η * ∇L(θ)
θ = θ - v

β 通常取 0.9,表示保留 90% 的历史动量。

我个人习惯在以下场景用 Momentum:

  • 数据量不大,想精细调参
  • 需要更好的泛化性能(相比 Adam)
  • 已经对学习率有比较好的预估

4.2.3 RMSprop:给每个参数配独立油门

RMSprop 的核心贡献是「自适应学习率」。它解决了 SGD 一个老大难问题:不同参数应该用不同的学习率。

更新逻辑:

G = α * G + (1-α) * (∇L(θ))²
θ = θ - η / √(G + ε) * ∇L(θ)

说白了,梯度大的参数,学习率自动变小;梯度小的参数,学习率自动变大。这个机制在 RNN 训练中特别管用。

实战经验:我在做时间序列预测时,RNN 用 SGD 经常梯度爆炸,换成 RMSprop 之后稳定多了。α 一般取 0.9,ε 取 1e-8 防止除零。

4.2.4 Adam:当前工业界的事实标准

Adam = Momentum + RMSprop。它同时保留了历史梯度方向和自适应学习率两个优点。

更新公式(简化版):

m = β₁ * m + (1-β₁) * ∇L(θ)
v = β₂ * v + (1-β₂) * (∇L(θ))²
θ = θ - η * m / (√v + ε)

默认参数 β₁=0.9, β₂=0.999, ε=1e-8。

Adam 为什么这么火?因为它「开箱即用」。你不需要花太多精力调学习率,默认值 0.001 就能跑出不错的结果。

核心观点:如果你不确定选哪个优化器,先上 Adam。它虽然不是每个任务的最优解,但绝对是「最不差」的选择。

4.3 知识体系结构图

下面这张图帮你理清四个优化器的演进关系和适用场景:

优化器演进与选择框架图 优化器演进与选择框架 SGD Momentum RMSprop Adam SGD / Momentum 适合:小数据、需要精细调参、追求泛化 RMSprop 适合:RNN、时间序列、梯度不稳定场景 Adam 适合:大多数场景、快速验证、开箱即用 选择策略:先 Adam 快速出基线 → 换 SGD/Momentum 精细调优 → 特殊场景用 RMSprop (如果收敛不稳定,优先检查学习率和数据预处理,别急着换优化器)

4.4 实战场景选择指南

光讲理论不够,咱们直接看场景。我根据这些年踩过的坑,总结了一个选择矩阵:

场景 推荐优化器 理由
快速验证想法 Adam 默认参数就能跑,省时间
图像分类(大模型) SGD + Momentum 泛化性能更好,业界共识
RNN / LSTM RMSprop 有效抑制梯度爆炸
GAN 训练 Adam 稳定性好,收敛快
强化学习 Adam 适应非平稳目标
小样本学习 SGD 防止过拟合,更鲁棒
重要提醒:我曾经在一个推荐系统项目里,直接用 Adam 跑出了 SOTA 指标,但上线后发现线上效果远不如离线。后来排查发现,Adam 在训练后期学习率太小,导致模型没有充分收敛到最优解。最后换成 SGD + 余弦退火学习率调度,才解决了问题。

4.5 代码实战:对比四种优化器的收敛行为

光说不练假把式。下面用 PyTorch 写一个简单的对比实验,在同一个任务上观察四种优化器的表现。

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import matplotlib.pyplot as plt

# 生成模拟数据
torch.manual_seed(42)
X = torch.randn(1000, 20)
y = torch.randn(1000, 1)

dataset = TensorDataset(X, y)
loader = DataLoader(dataset, batch_size=64, shuffle=True)

# 定义一个简单的线性模型
model = nn.Linear(20, 1)
criterion = nn.MSELoss()

# 四种优化器
optimizers = {
    'SGD': optim.SGD(model.parameters(), lr=0.01),
    'Momentum': optim.SGD(model.parameters(), lr=0.01, momentum=0.9),
    'RMSprop': optim.RMSprop(model.parameters(), lr=0.001),
    'Adam': optim.Adam(model.parameters(), lr=0.001)
}

# 训练并记录 Loss
history = {name: [] for name in optimizers.keys()}

for name, opt in optimizers.items():
    model.apply(lambda m: m.reset_parameters() if hasattr(m, 'reset_parameters') else None)
    for epoch in range(50):
        epoch_loss = 0.0
        for batch_X, batch_y in loader:
            opt.zero_grad()
            pred = model(batch_X)
            loss = criterion(pred, batch_y)
            loss.backward()
            opt.step()
            epoch_loss += loss.item()
        history[name].append(epoch_loss / len(loader))

# 绘制收敛曲线
for name, losses in history.items():
    plt.plot(losses, label=name)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Optimizer Convergence Comparison')
plt.show()
个人习惯:我每次做新项目,都会先跑这样一个对比脚本。花 10 分钟看看四种优化器的收敛曲线,比凭经验瞎猜靠谱得多。你想想看,数据分布不同,最优优化器可能完全不同。

4.6 避坑总结

最后,把我这些年踩过的坑整理成清单,你直接拿去用:

  • 不要盲目迷信 Adam:它收敛快,但泛化不一定最好。最终上线前,试试 SGD + Momentum。
  • 学习率是王道:优化器再强,学习率不对也是白搭。先用学习率范围测试(LR Range Test)找到合理区间。
  • 注意 Adam 的偏差校正:早期迭代时,Adam 的动量估计有偏差,PyTorch 默认做了校正,但如果你自己实现,别忘了加。
  • RMSprop 的 α 参数:默认 0.9 通常够用,但如果梯度变化剧烈,可以试试 0.99。
  • Momentum 的 β 别太大:超过 0.99 容易震荡,0.9 是个安全值。

优化器选择没有银弹。我的建议是:先用 Adam 快速出基线,再根据任务特性换到更合适的优化器。记住,工具是死的,人是活的。