
跑深度学习训练的时候最让我头疼的往往不是模型结构设计而是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里优化器参数momentum0.9时使用的就是Nesterov的变体需开启nesterovTrue。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_hat和v_hat这是Adam的偏差校正项也是它和朴素RMSProp最核心的区别。训练初期m和v都被初始化为零特别是β2取0.999时v的累积速度非常慢如果不做校正前几千步的有效学习率会被严重低估。偏差校正的直觉是既然早期历史积累不够那就先按已发生的步数把“折扣”补回来。实际使用中Adam对学习率的敏感度远低于SGD。同样一个模型SGD可能需要在{0.1, 0.01, 0.001}之间反复试Adam则通常用默认的0.001就能得到不错的结果。这就是为什么它成了Transformer、扩散模型、GAN等几乎所有现代模型的首选优化器。但Adam也并非万能最典型的问题是在某些任务上收敛到的最终精度不如精心调参的SGDMomentum泛化性能有时会略差。3. 实战选型、超参数与隐藏坑前面讲了机制这一节重点讲工程里怎么选、怎么调。很多初学者会问“是不是无脑用Adam就行了”答案是不一定。选型取决于你对最终指标的要求、训练资源、模型类型等很多因素。对比维度SGD MomentumRMSPropAdam / AdamW收敛速度慢中快对学习率敏感度高中低泛化性能通常较好中等良好AdamW可进一步改善内存开销额外存动量额外存二阶矩额外存一阶和二阶矩适用场景CNN、经典图像模型RNN/时序模型Transformer、NLP、GAN、RL3.1 什么场景优先SGDMomentum如果你的任务属于图像分类、目标检测这类传统CV任务训练数据规模充足且你有时间做细致的超参数搜索SGDMomentum往往能给出更低的泛化误差。一个重要的机制解释是SGDMomentum的更新路径更“简洁”它不会像自适应学习率方法那样频繁放大或缩小更新幅度这种约束在某种程度上起了正则化的作用。具体配置上一个行之有效的基线是初始学习率0.1动量0.9配合批次大小256。训练过程中用余弦退火或阶梯式衰减把学习率逐步降到0。很多经典CNN模型ResNet系列都是这么训练的。如果你复现论文发现精度差一截检查一下是不是用了Adam而论文里是SGDMomentum——这是复现实验最常见的差距来源之一。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-4或5e-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_norm1.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 我的调试顺序优化器工具化的“三板斧”踩过的坑多了之后我总结了一个“三板斧”式的调试顺序确认数据与loss计算无误先排除非优化器因素尝试不同的优化器组合SGDMomentum→Adam→AdamW用同样的学习率跑一圈找到能稳定下降的方向后再统一调整学习率和batch size很多情况下第一步排除数据问题后只要从SGD换到Adamloss就能明显改善。这时候就需要回溯之前学过的知识到底是因为Adam的自适应学习率解决了梯度尺度不一致问题还是因为Momentum冲过了鞍点只有理解了机制才能在下一步做合理的调参决策而不是盲目地试各个超参数组合。5. 个人经验优化器是手段不是目的我见过不少同学把优化器当成“神秘配方”以为用了Adam就万事大吉loss不降就疯狂换优化器。这种思路其实偏离了本质。优化器只是在给定损失函数和梯度信息后决定参数如何更新的规则。它能加速收敛、帮助逃出局部不利区域但永远弥补不了数据质量差、模型容量不够、目标函数设计有误等根本性问题。根据我的实际经验一个健康的调试流程应该是先确保模型能过拟合一个小样本比如几十条数据排除代码bug再把优化器作为一个关键变量来调试而不是一开始就在各种优化器之间反复横跳。深度学习项目里loss不能下降的原因可能藏在数据处理、模型结构、batch size、学习率调度等任何一个环节优化器只是其中一个“杠杆”。最后再分享一个经验每逢模型调参遇到瓶颈我最喜欢干的一步是把优化器换回SGDMomentum用较小的学习率加上余弦退火跑一遍。这个过程往往没有随之而来的惊喜但它会迫使我把模型的每一条数据流、每一层权重的梯度分布重新捋一遍。很多“Adam调不动”的问题最后其实是数据处理里一个不该存在的除零操作导致的。务实、有耐心地对待优化器这个工具能少走很多弯路。