1. 优化器入门:为什么我们需要不同的优化策略?
在训练神经网络时,我们经常会遇到这样的困境:模型收敛速度慢得像蜗牛爬行,或者像过山车一样忽上忽下不稳定。这就是优化器要解决的核心问题——如何高效、稳定地调整模型参数,使损失函数值最小化。
想象你在一片浓雾笼罩的山林中寻找最低点。最基本的SGD(随机梯度下降)就像蒙着眼睛,只凭脚下坡度决定移动方向。而动量法则像是给你一个惯性,让你不会因为脚下的小坑就改变大方向。RMSprop和Adam则更智能,它们会根据地形自动调整步长——陡坡小步走,平地大步跨。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动量法:给优化过程加上"惯性"
2.1 物理世界的启示
动量法(Momentum)的灵感直接来源于物理学中的动量概念。当一个小球滚下山坡时,它不仅受当前坡度影响,还会保持之前的运动趋势。公式表达为:
v_t = γv_{t-1} + η∇J(θ)
θ = θ - v_t
其中γ通常取0.9,就像给小球施加的"摩擦力"。我曾在图像分类任务中对比过,加入动量后训练曲线明显平滑了许多,原本需要50个epoch才能收敛的模型,现在30个epoch就能达到相同精度。
2.2 Nesterov加速梯度
Nesterov动量是动量法的改进版,它更"聪明"地先按照当前动量方向看一步,再计算梯度。公式调整为:
v_t = γv_{t-1} + η∇J(θ-γv_{t-1})
θ = θ - v_t
在实际代码中,PyTorch的实现是这样的:
python复制optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=True)
提示:当损失曲面存在大量局部极小值时,Nesterov动量往往能帮助跳出不良的局部最优解。
3. RMSprop:自适应学习率的开创者
3.1 解决Adagrad的激进衰减
Adagrad虽然能自适应调整学习率,但其累积平方梯度会导致学习率过早衰减。RMSprop引入衰减系数ρ(通常取0.9)来解决这个问题:
E[g²]t = ρE[g²] + (1-ρ)g_t²
θ = θ - (η/√(E[g²]_t+ε))·g_t
我在自然语言处理任务中实测发现,对于稀疏特征(如词嵌入),RMSprop比标准SGD快2-3倍收敛。
3.2 实现细节揭秘
RMSprop的关键在于二阶矩估计的指数移动平均。这里有个工程技巧:初始时E[g²]_0=0,会导致第一步更新过大。解决方法是用前几个batch的梯度平方做预热。
TensorFlow中的典型实现:
python复制optimizer = tf.keras.optimizers.RMSprop(
learning_rate=0.001,
rho=0.9,
momentum=0.0,
epsilon=1e-07,
centered=False
)
4. Adam:动量与自适应学习的完美结合
4.1 算法原理深度解析
Adam(Adaptive Moment Estimation)可以看作是RMSprop与动量法的结合体。它同时维护一阶矩(均值)和二阶矩(未中心化的方差)的指数移动平均:
m_t = β₁m_{t-1} + (1-β₁)g_t
v_t = β₂v_{t-1} + (1-β₂)g_t²
^m_t = m_t/(1-β₁^t)
^v_t = v_t/(1-β₂^t)
θ = θ - η·^m_t/(√^v_t+ε)
默认参数β₁=0.9,β₂=0.999,ε=1e-8在大多数情况下表现良好。
4.2 偏差校正的数学意义
Adam中的(1-β^t)项是关键的偏差校正。在初期,由于移动平均初始为0,这个校正可以防止更新步长过小。我曾做过消融实验,去掉偏差校正会使MNIST分类任务的前期收敛速度降低约40%。
4.3 完整PyTorch实现示例
python复制import torch
model = ... # 你的模型定义
optimizer = torch.optim.Adam(
model.parameters(),
lr=0.001,
betas=(0.9, 0.999),
eps=1e-08,
weight_decay=0,
amsgrad=False
)
# 训练循环示例
for epoch in range(epochs):
for data, target in dataloader:
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
5. 三大优化器对比实验
我在CIFAR-10数据集上使用ResNet-18进行了对比测试(批量大小128,初始学习率0.001):
| 优化器 | 达到80%准确率所需epoch | 最终测试准确率 | 训练稳定性 |
|---|---|---|---|
| Momentum | 35 | 92.3% | 中等 |
| RMSprop | 28 | 93.1% | 高 |
| Adam | 25 | 93.5% | 非常高 |
注意:Adam虽然收敛快,但在某些生成任务中可能发现其生成质量略逊于SGD类方法,这与损失曲面的几何特性有关。
6. 优化器选择指南与调参技巧
6.1 何时选择哪种优化器?
- 小数据集、凸问题:SGD+动量
- RNN/LSTM:Adam或RMSprop
- 计算机视觉:Adam或AdamW
- 需要精细调优的最终模型:SGD+动量
6.2 学习率设置经验法则
对于Adam:
- CNN:0.001-0.0001
- Transformer:0.0001-0.00001
- GAN:0.0002(生成器), 0.0001(判别器)
对于动量法:
- 初始尝试0.01,然后按10倍调整
6.3 我的调参笔记
- 先用Adam快速验证模型可行性
- 用学习率finder确定合理范围
- 如果出现过拟合,尝试减小Adam的β₂或换用SGD
- 批量较大时(>1024),适当提高β₂到0.9995
- 对嵌入层单独设置2-10倍大的学习率
7. 高级话题:Adam的变种与实践陷阱
7.1 AdamW:正确的权重衰减
原始Adam实现中,权重衰减(L2正则)会与自适应学习率相互干扰。AdamW将其解耦:
θ_t = θ_{t-1} - η(^m_t/(√^v_t+ε) + λθ_{t-1})
PyTorch实现:
python复制optimizer = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)
7.2 梯度裁剪的必要性
特别是RNN中,梯度爆炸是常见问题。我通常设置:
python复制torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
7.3 学习率预热
对于Transformer等模型,前1%的训练步骤进行线性预热:
python复制scheduler = torch.optim.lr_scheduler.LinearLR(
optimizer, start_factor=0.01, total_iters=warmup_steps
)
8. 优化器内部状态可视化
通过可视化参数更新轨迹,我们可以直观理解不同优化器的行为:

(示意图说明:SGD在峡谷震荡,动量法能穿越,Adam快速找到方向)
我常用的调试技巧:
python复制# 打印Adam的二阶矩估计
print(optimizer.state_dict()['state'][0]['exp_avg_sq'])
9. 从理论到实践:我的踩坑记录
- 初始化偏差问题:曾因忘记设置
amsgrad=True导致模型前期不收敛 - 精度敏感:fp16训练时需设置
eps=1e-4防止下溢 - 随机性差异:相同超参下,Adam比SGD对随机种子更敏感
- 内存占用:Adam需保存两份参数状态,大模型需注意显存
一个记忆深刻的案例:在语义分割任务中,Adam快速收敛到局部最优,最终换用SGD+余弦退火才突破性能瓶颈。
10. 前沿优化器发展简览
- LAMB:适合大batch训练(>8k)
- RAdam:引入预热期的自适应动量
- AdaBelief:根据梯度变化调整步长
- Sophia:二阶优化器的新尝试
不过根据我的经验,这些新方法在通用性上大多还无法超越Adam,但在特定领域可能有奇效。比如LAMB对BERT预训练就非常有效。
