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 做的。
缺点也很明显:
- 收敛慢:尤其在鞍点附近,梯度接近零,直接卡死
- 震荡严重:在陡峭方向上反复横跳
- 学习率敏感:调不好就发散或者龟速
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 训练中特别管用。
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 就能跑出不错的结果。
4.3 知识体系结构图
下面这张图帮你理清四个优化器的演进关系和适用场景:
4.4 实战场景选择指南
光讲理论不够,咱们直接看场景。我根据这些年踩过的坑,总结了一个选择矩阵:
| 场景 | 推荐优化器 | 理由 |
|---|---|---|
| 快速验证想法 | Adam | 默认参数就能跑,省时间 |
| 图像分类(大模型) | SGD + Momentum | 泛化性能更好,业界共识 |
| RNN / LSTM | RMSprop | 有效抑制梯度爆炸 |
| GAN 训练 | Adam | 稳定性好,收敛快 |
| 强化学习 | 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()
4.6 避坑总结
最后,把我这些年踩过的坑整理成清单,你直接拿去用:
- 不要盲目迷信 Adam:它收敛快,但泛化不一定最好。最终上线前,试试 SGD + Momentum。
- 学习率是王道:优化器再强,学习率不对也是白搭。先用学习率范围测试(LR Range Test)找到合理区间。
- 注意 Adam 的偏差校正:早期迭代时,Adam 的动量估计有偏差,PyTorch 默认做了校正,但如果你自己实现,别忘了加。
- RMSprop 的 α 参数:默认 0.9 通常够用,但如果梯度变化剧烈,可以试试 0.99。
- Momentum 的 β 别太大:超过 0.99 容易震荡,0.9 是个安全值。
优化器选择没有银弹。我的建议是:先用 Adam 快速出基线,再根据任务特性换到更合适的优化器。记住,工具是死的,人是活的。