深度学习优化器详解:SGD、Momentum、RMSProp与Adam的选型与调参
2026/9/16 1:27:09 网站建设 项目流程

跑深度学习训练的时候,最让我头疼的往往不是模型结构设计,而是loss曲线像心电图一样上下乱跳,或者干脆卡在某个值上一动不动。遇到这种场景,很多人第一反应是调学习率,或者怀疑数据预处理出了问题,但真正的问题可能出在优化算法的选择上。SGD(随机梯度下降)并不是唯一的选择,Momentum、RMSProp、Adam这三个优化算法几乎覆盖了日常训练中90%以上的需求。这篇博客不打算堆砌公式,而是想从“为什么需要它们”“它们各自解决了什么问题”以及“实际工程里到底怎么选、怎么调”三个层面,把这三个算法彻底讲透。

1. 从SGD的“死穴”看优化器存在的意义

很多人入门深度学习时,接触的第一个优化算法就是SGD。它的逻辑简单得不能再简单:算出梯度,沿梯度的反方向走一步。可一旦放到真实的深度网络里,这个朴素版本几乎寸步难行,根本原因在于损失曲面远比我们想象中复杂。深刻理解SGD的局限,才能真正明白为什么Momentum、RMSProp、Adam这些“改良版”优化器会相继出现。

1.1 病态曲率下的原地踏步

想象一个等高线图呈狭长峡谷状的损失函数。在峡谷的某个维度上,坡度非常陡峭,损失值剧烈变化;在另一个维度上,坡度非常平缓,损失值几乎不怎么波动。这种情况下,SGD的更新方向会被陡峭维度主导,导致参数在峡谷两侧来回弹跳,而在平缓维度的推进却极其缓慢。直观感受就是loss曲线震荡得非常厉害,但整体下降趋势非常有限。

我最早训练一个浅层全连接网络时就踩过这样的坑。当时loss一直在2.3附近反复横跳,我把学习率从0.1一路降到0.01,震荡确实减轻了,但收敛速度也变得慢得难以接受。后来才意识到,问题不在于学习率的大小,而在于SGD在病态曲率面前只会“暴力”地沿梯度方向走,完全没有考虑到不同方向上的曲率差异。生活化的类比就是:你不知道脚下是一座窄而陡的山脊,只凭当前最陡方向迈步,结果每走一步都几乎横着滑回原位。

1.2 高维空间真正的主宰者是鞍点

低维空间里,函数极小值点很常见,但在深度网络的高维参数空间中,绝大多数梯度接近零的点并不是局部极小值,而是鞍点——在某些方向上是极小值,在另一些方向上是极大值。SGD遇到鞍点时,各个方向的梯度都近似为零,更新幅度极其微小,训练看起来就像“冻住”了一样。

这也是为什么很多初学者发现,模型训练到一半loss就不再下降,但无论怎么调学习率都没有本质改善。实际上,不是学习率的问题,而是SGD缺乏“冲过”鞍点的能力。它只看到了当前一步的梯度,没有任何历史信息的积累,所以在一个梯度几乎为零的平缓区域里,它没有任何动力走出去。Momentum的引入,本质上就是给SGD装上了一个“惯性系统”,让它靠着历史速度冲过这些平缓地带。

1.3 学习率的“一视同仁”困局

SGD还有一个固有问题:它对所有参数使用同一个学习率。但在深度网络中,不同参数层的梯度量级差异可能非常大。浅层卷积核的梯度可能很小,而最后分类层的梯度可能很大。使用同一个学习率,本质上是把“尺度信息”强行忽略了。

如果学习率设置得过大,梯度大的参数会震荡甚至发散;设置得过小,梯度小的参数收敛会慢到让人怀疑人生。这就是为什么SGD调参时学习率极其敏感,0.1能用,0.2可能就发散。后面要讲的RMSProp和Adam,核心突破之一就是打破了这种“所有参数共享一个学习率”的僵局,给每个参数独立配置自适应学习率。

2. 三个核心算法的机制拆解与直觉

理解了SGD的三个死穴之后,再来看优化算法的发展脉络,就非常清楚了:Momentum解决“震荡与冲出鞍点”的问题,RMSProp解决“不同参数学习率统一”的问题,Adam把前面两个思路融合在一起,形成了目前最通用的优化器形态。

2.1 Momentum:给梯度更新装上“惯性”

Momentum的物理直觉非常朴素:一个带质量的小球从山坡滚下来,如果小球有速度、有惯性,那么在下坡方向会越滚越快,在峡谷两侧则因为反向速度的抵消而不会反复震荡。

它的迭代公式比SGD多了一个动量项:

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

这里的β称为动量系数,最常用的取值为0.9。v是历史梯度的指数加权移动平均。注意,Momentum不是简单地把历史梯度相加,而是按指数衰减的方式加权平均——越久远的梯度对当前速度的影响越小。这就相当于做了一次“平滑滤波”,把梯度中的高频噪声滤掉,保留低频的稳定趋势。

实际效果上有两点很关键。第一点是“震荡抑制”:如果某个维度的梯度方向频繁翻转,Momentum会让正反向梯度先叠加抵消,再由剩余的速度决定更新方向,弹跳幅度明显减小。第二点是“加速穿越鞍点”:进入梯度近似为零的区域后,SGD会因为梯度很小而几乎停步,但Momentum还保存着之前积累的速度,靠着惯性“冲”过平缓区域。

有个细节值得注意:实际工程里,多数框架实现的Momentum其实是“Nesterov Momentum”,它的差别在于先按当前动量走一步,然后再计算梯度。这种“向前看一步”的方式能让更新方向更准确,收敛速度通常略快一些。PyTorch里优化器参数momentum=0.9时,使用的就是Nesterov的变体(需开启nesterov=True)。

2.2 RMSProp:每个参数独立分配学习率

RMSProp的出发点是解决学习率“一刀切”的问题。它的做法是:为每个参数维护一个梯度平方的指数移动平均,然后用这个平均值去归一化当前梯度。

s = β * s + (1 - β) * g² θ = θ - (η / sqrt(s + ε)) * g

其中g是当前梯度,β通常取0.9,ε是一个极小的常数(如1e-8),防止除零。这个公式的直觉是:如果某个参数的梯度一直很大,那么它的s值就大,有效学习率η/sqrt(s)就会变小;如果某个参数的梯度一直很小,那么它的s值就小,有效学习率就会变大。这是真正的“按需分配”。

组合起来的更新效果有一种“指数三角优化”的味道:不同参数在损失曲面中处于完全不同的“地形”,有的在陡坡上,有的在平原上,RMSProp给它们各自配了对应的“步长”——陡坡上的参数步子小一点避免跑飞,平原上的参数步子大一点加快移动。

RMSProp的缺点是应对稀疏梯度时不够稳定。如果一个参数很长时间不更新,它的s会衰减到很小,突然来一个大梯度时,归一化后的更新幅度可能变得非常大,导致参数剧烈跳变。这种情况在NLP任务里并不少见,后面提到的Adam通过二阶矩的偏差校正规避了一部分风险。

2.3 Adam:双引擎驱动与偏差校正

Adam全称是Adaptive Moment Estimation,本质上是Momentum和RMSProp的组合:既用一阶动量(历史梯度的加权平均)来获得速度,又用二阶动量(历史梯度平方的加权平均)来归一化学习率。

m = β1 * m + (1 - β1) * g v = β2 * v + (1 - β2) * g² m_hat = m / (1 - β1^t) v_hat = v / (1 - β2^t) θ = θ - (η / sqrt(v_hat) + ε) * m_hat

这里的β1通常取0.9,β2取0.999。注意公式里的m_hatv_hat,这是Adam的偏差校正项,也是它和朴素RMSProp最核心的区别。训练初期,m和v都被初始化为零,特别是β2取0.999时,v的累积速度非常慢,如果不做校正,前几千步的有效学习率会被严重低估。偏差校正的直觉是:既然早期历史积累不够,那就先按已发生的步数把“折扣”补回来。

实际使用中,Adam对学习率的敏感度远低于SGD。同样一个模型,SGD可能需要在{0.1, 0.01, 0.001}之间反复试,Adam则通常用默认的0.001就能得到不错的结果。这就是为什么它成了Transformer、扩散模型、GAN等几乎所有现代模型的首选优化器。但Adam也并非万能,最典型的问题是在某些任务上收敛到的最终精度不如精心调参的SGD+Momentum,泛化性能有时会略差。

3. 实战选型、超参数与隐藏坑

前面讲了机制,这一节重点讲工程里怎么选、怎么调。很多初学者会问:“是不是无脑用Adam就行了?”答案是不一定。选型取决于你对最终指标的要求、训练资源、模型类型等很多因素。

对比维度SGD + MomentumRMSPropAdam / AdamW
收敛速度
对学习率敏感度
泛化性能通常较好中等良好(AdamW可进一步改善)
内存开销额外存动量额外存二阶矩额外存一阶和二阶矩
适用场景CNN、经典图像模型RNN/时序模型Transformer、NLP、GAN、RL

3.1 什么场景优先SGD+Momentum

如果你的任务属于图像分类、目标检测这类传统CV任务,训练数据规模充足,且你有时间做细致的超参数搜索,SGD+Momentum往往能给出更低的泛化误差。一个重要的机制解释是:SGD+Momentum的更新路径更“简洁”,它不会像自适应学习率方法那样频繁放大或缩小更新幅度,这种约束在某种程度上起了正则化的作用。

具体配置上,一个行之有效的基线是:初始学习率0.1,动量0.9,配合批次大小256。训练过程中用余弦退火或阶梯式衰减把学习率逐步降到0。很多经典CNN模型(ResNet系列)都是这么训练的。如果你复现论文发现精度差一截,检查一下是不是用了Adam而论文里是SGD+Momentum——这是复现实验最常见的差距来源之一。

3.2 什么场景应该直接上Adam

毕竟不是每个任务都有充足的数据和算力让你慢慢调SGD。以下是直接用Adam(或AdamW)优先级更高的场景:

  • 训练Transformer、BERT、GPT等注意力结构模型。这类模型的损失曲面异常复杂,Adam是诸多预训练框架的默认配置。
  • 训练GAN、扩散模型这类训练本身就不稳定的模型。Adam在生成对抗场景里几乎成了标配,因为它能在不剧烈震荡的前提下快速下降loss。
  • 做强化学习时。RL的奖励信号方差极大,梯度分布飘忽不定,Adam能有效平滑更新方向。
  • 新手刚刚搭建完一个新的模型架构,想要快速验证“模型能不能学会某个函数”。用Adam省去调学习率的成本,先把正确性验证清楚再说。

3.3 AdamW为什么能取代Adam

热搜词里出现了“adam和adamw的区别”,这个问题确实值得单独展开。传统Adam配合L2正则化(权重衰减)时,L2正则项的梯度会参与Adam的动量累积和自适应学习率归一化,导致“衰减被放大或缩小”,和纯粹的权重衰减并不等价。AdamW则是把权重衰减从损失计算中拿出来,直接在参数更新时单独执行:不管Adam怎么调整学习率,权重衰减这一步始终按固定比例执行,行为更干净。

实际操作中,HuggingFace的Transformers、Timm、PyTorch Lightning等主流库的默认优化器几乎都换成了AdamW。如果你的模型用Adam训练出现过拟合明显、泛化不佳的情况,换成AdamW会有可感知的改善。

3.4 学习率、batch size与退火策略的联动

所有优化器都逃不开学习率这个关键超参数。一个容易忽视的事实是:学习率的最优值和batch size强相关。当batch size增大时,梯度噪声变小,可以相应地调大学习率。一个粗略的经验法则是:batch size翻倍,学习率可以考虑乘以sqrt(2)或直接乘以2。

对于Adam而言,Transformer类模型最常见的学习率起点是3e-45e-5,如果loss发散,先看是不是学习率太大,而不是急着去改β1、β2这些二阶超参数。另一个工程技巧是热身(warmup):训练前先用较小的学习率运行一段步数,再把学习率升到目标值。Adam虽然自带偏差校正,但很多大规模预训练任务仍然用warmup,原因是在训练初期,模型参数变化剧烈,一阶和二阶动量的估计很不稳定,先用小学习率“热热身”能显著减少初期震荡。

4. 训练曲线不对劲时,怎么用优化器排障

训练神经网络遇到loss不下降、震荡、或者直接变成NaN,是每个人都会经历的过程。结合优化器做排障,有先后的排查顺序,下面的思路能让你省下大量时间。

4.1 loss震荡不收敛,先优化器后网络

很多人一看到loss震荡就怀疑网络结构写错了,于是开始改模型,改来改去问题还是存在。我的习惯是:先检查优化器配置。Adabelief有一个不错的延展概念,但实际中,如果使用了Adam类优化器而loss仍然剧烈震荡,第一个该看的指标是学习率。把它从0.001降到0.0001,如果震荡明显减轻,说明初始学习率偏大。

如果学习率已经很小但loss还是震荡,再检查梯度裁剪。对于Transformer、NLP任务,梯度裁剪几乎是必备操作。PyTorch里的做法是:

torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

注意裁剪的时机是在loss.backward()之后、optimizer.step()之前。裁剪阈值从1.0开始试起,如果梯度范数普遍偏大可以适当降低阈值。这一招对Adam和SGD都有效,在训练初期能避免偶发的梯度尖峰把参数推出正常区域。

4.2 gradient norm异常时的判断方法

排障时我会在训练循环里定期打印梯度范数:

total_norm = 0.0 for p in model.parameters(): if p.grad is not None: param_norm = p.grad.data.norm(2) total_norm += param_norm.item() ** 2 total_norm = total_norm ** 0.5

如果gradient norm在正常训练过程中突然跳高几个数量级,大概率是数据里有异常样本或者loss计算有漏洞(比如除以了接近零的数),优化器本身没问题。如果gradient norm一开始就极大,且随着训练持续攀升,则可能是初始化或学习率设置的问题。

还有一种情况是gradient norm极小,例如长期在1e-6附近徘徊,这通常是前面说的鞍点问题。这时候把Momentum的β调大到0.99,或者换成Adam,往往能打破僵局。

4.3 我的调试顺序:优化器工具化的“三板斧”

踩过的坑多了之后,我总结了一个“三板斧”式的调试顺序:

  1. 确认数据与loss计算无误(先排除非优化器因素)
  2. 尝试不同的优化器组合:SGD+Momentum→Adam→AdamW,用同样的学习率跑一圈
  3. 找到能稳定下降的方向后,再统一调整学习率和batch size

很多情况下,第一步排除数据问题后,只要从SGD换到Adam,loss就能明显改善。这时候就需要回溯之前学过的知识:到底是因为Adam的自适应学习率解决了梯度尺度不一致问题,还是因为Momentum冲过了鞍点?只有理解了机制,才能在下一步做合理的调参决策,而不是盲目地试各个超参数组合。

5. 个人经验:优化器是手段,不是目的

我见过不少同学把优化器当成“神秘配方”,以为用了Adam就万事大吉,loss不降就疯狂换优化器。这种思路其实偏离了本质。优化器只是在给定损失函数和梯度信息后,决定参数如何更新的规则。它能加速收敛、帮助逃出局部不利区域,但永远弥补不了数据质量差、模型容量不够、目标函数设计有误等根本性问题。

根据我的实际经验,一个健康的调试流程应该是:先确保模型能过拟合一个小样本(比如几十条数据),排除代码bug;再把优化器作为一个关键变量来调试,而不是一开始就在各种优化器之间反复横跳。深度学习项目里,loss不能下降的原因可能藏在数据处理、模型结构、batch size、学习率调度等任何一个环节,优化器只是其中一个“杠杆”。

最后再分享一个经验:每逢模型调参遇到瓶颈,我最喜欢干的一步是把优化器换回SGD+Momentum,用较小的学习率加上余弦退火跑一遍。这个过程往往没有随之而来的惊喜,但它会迫使我把模型的每一条数据流、每一层权重的梯度分布重新捋一遍。很多“Adam调不动”的问题,最后其实是数据处理里一个不该存在的除零操作导致的。务实、有耐心地对待优化器这个工具,能少走很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询