
搞深度学习的人迟早会撞上一组名字SGD、Momentum、AdaGrad、Adam。刚开始跑模型的时候我跟很多同学一样把这些优化器当“黑盒”用——反正框架里设一行optimizer torch.optim.Adam(...)就能跑收敛了就是成功不收敛就换个学习率。但真到项目不好调的时候才意识到一个问题优化器不是“选一个就行”的开关它本质上是在决定训练过程走什么样的“下山路线”不同路线对应完全不同的收敛行为、泛化能力和调试手感。这篇文章我打算把这四个优化器从头到尾聊透。会用大白话和物理直觉解释它们的原理也会把每个算法的更新公式、超参含义、适用场景和坑位都摊开来说。不管你是刚入门深度学习、在“炼模型”过程中头疼过 loss 震荡的新手还是想搞清楚“Adam 和 SGD 到底该选谁”的从业者这篇都能给你一个可以照着用的参考。1. 优化器到底在解决什么问题——不是玄学是“下山”先忘掉公式想一个场景。你站在一座山的某个位置目标是走到山谷最低点。你看不清全貌只能靠脚底踩到的坡度来判断方向坡度往下就往那边走一步。这个“靠坡度找方向、一步步挪到低点”的过程就是梯度下降。模型训练里的“山”是损失函数构成的曲面脚下踩到的“坡度”是每个参数对损失的梯度走的“步子”就是学习率。方向知道了但怎么走大有讲究。步子迈太大可能会直接跨过谷底、冲到对面山坡上然后来回震荡步子迈太小训练慢到天亮都到不了终点。更麻烦的是这座山不是标准的“大碗”——有的方向很陡、有的方向很缓干脆是一片高原中间还可能有个假洼地局部最小或者马鞍形区域鞍点。标准梯度下降在这种地形里非常吃亏。SGD、Momentum、AdaGrad、Adam本质上就是针对这些“下山难题”一层层打补丁。SGD 提供了最朴素的走法Momentum 发现老是震荡就给脚步加“惯性”AdaGrad 发现不同方向难度不一样就给每个方向配一个“自动调节的步长”Adam 干脆把前两家的经验合并既有惯性又给每个参数动态配步长成了这几年最稳的默认选择。这套演进逻辑非常重要。很多人记不住公式是因为不知道每个公式在解决什么具体问题。一旦你理解了“补丁在补什么洞”你不需要背公式也能做出正确的调参决策。2. 四个选手逐个拆解原理、公式与直觉2.1 SGD最朴素的梯度下降但别小看它一般框架里写的SGD严格说其实是“小批量随机梯度下降”Mini-batch Gradient Descent。它不是每看一个样本就更新一次而是随机抽一小批样本算出这批样本的平均梯度然后用这个梯度去更新参数θ θ - α * g_t其中 θ 是参数α 是学习率g_t 是当前小批量算出来的梯度。SGD 最大的优点是简单、稳定、在下坡方向清晰时非常靠谱。它最大的问题也摆在明面上第一个是震荡。在峡谷类地形里梯度在陡峭方向上来回摆但平缓方向却推进很慢loss 曲线看起来就是一条“波浪式下降”的线收敛很磨人。第二个是弯曲地形适应差。换到那种“弯弯绕绕”的沟壑地形它每一步都走得很机械效率低。第三个是学习率难调。α 太小训练慢α 太大直接在最优解附近转圈loss 下不去。但有一个现象很反直觉SGD 在很多视觉任务里泛化效果反而比 Adam 好。我个人的理解是SGD 的“机械和简单”让它落到的解往往在更平坦的区域而 flat minima 在测试集上通常表现更稳。所以如果你在做一个标准的图像分类任务不是特别在意收敛速度的话用带动量的 SGD 仍然是非常好的选择。2.2 Momentum给梯度加“惯性”SGD 在峡谷地形里震荡太严重解决思路非常直观加惯性。想象一个保龄球从山坡滚下来它不会因为某个瞬时坡度变平就立刻停下来而是会靠着之前的动能继续往前冲一段。动量法Momentum就是给参数更新引入一个“速度项” vv_t μ * v_{t-1} α * g_t θ_t θ_{t-1} - v_tμ 是动量系数一般取 0.9。仔细看这个公式。梯度方向如果连续一致比如始终向下坡走v 会越累越大相当于越走越快梯度方向来回反弹时历史的 v 和当前梯度方向相反会相互抵消一部分震荡就被削平了。实践里我最直观的感受是加了动量之后训练前期 loss 下降更快中后期曲线也更“顺滑”不会再像裸 SGD 那样锯齿状乱跳。这里还有一个变体叫 NAGNesterov Accelerated Gradient它的做法是先顺着当前的动量方向“超前看一步”再算那个位置上的梯度。效果上比普通 Momentum 收敛更稳很多框架里的torch.optim.SGD(..., momentum0.9, nesterovTrue)就是这种设置。配合 0.9 的动量基本是视觉任务里最经典的配置组合之一值得直接抄作业。2.3 AdaGrad给每个参数单独配“步长”SGD 和 Momentum 都还是“全局一个学习率”但真实问题里不同参数的处境差太多了。拿推荐系统举例有的特征比如热门商品频繁出现对应参数的梯度几乎每次都在更新有的特征比如长尾商品一整个 epoch 才遇到几次参数很少得到有效更新。如果全用同一个学习率频繁更新的参数容易震荡稀疏的参数又学不动。AdaGrad 的思路是针对每个参数维护一个“历史梯度平方和” s更新时用学习率除以这个和的平方根s_t s_{t-1} g_t^2 θ_t θ_{t-1} - (α / (√s_t ε)) * g_t直觉是某个参数历史梯度大、更新频繁s 就大等效学习率被压小某个参数历史梯度小、更新稀少s 就小等效学习率被放大。于是稀疏特征也能得到足够的更新幅度而频繁更新的参数不容易震荡。但 AdaGrad 有一个致命缺陷s 只增不减。随着训练进行s 越积越大等效学习率会持续衰减到最后所有参数的学习率都趋近于 0训练基本就“卡死”了。所以它在训练周期短、特征稀疏的模型里表现不错但在深度网络的长周期训练里基本没人用。它更大的价值在于提出了“自适应学习率”这个概念后面 RMSProp 和 Adam 都根植于此。RMSProp 是 AdaGrad 的直接改良——把“历史梯度平方和”改成“历史梯度平方的滑动平均”用 e.g. 0.9 的衰减系数让 s 只保留最近一段时间的梯度平方信息不再单调增长解决了学习率衰减到零的问题。不过 RMSProp 没有动量机制单独用的人也不多它更像是通往 Adam 的中间站。2.4 Adam把动量和自适应步长融为一体AdamAdaptive Moment Estimation是 Momentum 和 RMSProp 的合体由两套滑动平均组成一阶动量 m 保存梯度的均值方向相当于带惯性的梯度二阶动量 v 保存梯度平方的均值相当于自适应放缩的学习率m_t β1 * m_{t-1} (1 - β1) * g_t v_t β2 * v_{t-1} (1 - β2) * g_t^2 m_hat m_t / (1 - β1^t) v_hat v_t / (1 - β2^t) θ_t θ_{t-1} - α * m_hat / (√v_hat ε)默认设置是 β1 0.9、β2 0.999、ε 1e-8。很多框架内部已经把这些超参配好了所以 Adam 确实开箱即用。为什么实践里 Adam 几乎成为“默认王”因为它一次性解决了前三个算法的痛点不同参数自适应学习率继承 AdaGrad/RMSProp、方向平滑不震荡继承 Momentum、对初始学习率不那么敏感——即使学习率设得稍微高一点二阶动量会自动对梯度过大的方向做抑制不容易直接发散去。我自己做生成模型、Transformer 类任务、多模态对齐项目时基本首选 Adam因为它的收敛速度足够快、曲线稳定省掉了大量为“分方向调步长”操心的时间。Adam 也有被反复吐槽的缺点在某些任务上泛化能力不如带动量的 SGD对学习率依然敏感只是容差比 SGD 大。后者通常通过 AdamW把权重衰减从 L2 正则里拆出来单独处理缓解。坦白讲在一个新项目起步阶段我会先用 Adam 把模型跑通、找到合理的结构和数据方式再回头尝试 SGD 调参去刷更好的泛化精度。下面是这四种优化器的速览对比表优化器核心机制收敛速度稳定性典型使用场景SGD直接按梯度更新慢中等容易震荡视觉任务、追求泛化能力时MomentumSGD 惯性中等偏快明显改善震荡视觉任务的标配常配合 NAGAdaGrad自适应学习率受限于学习率衰减早期稳定后期易停线性模型、稀疏特征训练Adam动量 自适应学习率快非常稳定通用默认Transformer、GAN、多模态等3. 实战中的选择思路与超参调节经验3.1 优化器怎么选才合理很多朋友喜欢固定用 Adam 跑一切任务不能说错但要分场景。我现在的选择逻辑大概是这样的如果任务比如图像分类、语义分割、检测结构成熟数据相对规范我会优先试带动量的 SGDmomentum 0.9学习率从 0.01 起因为它泛化更好最终指标往往能刷得更高如果任务涉及 Transformer、大规模预训练微调、生成模型或数据分布异常复杂直接上 AdamW 更省心它自适应方向和步长后不用去猜“哪个方向的梯度尺度差太多”这种细节。还有一个选型思路依赖你的调试预算如果项目上线时间紧优先 Adam它的默认参数基本能跑顶多调学习率如果项目是长期打磨型比如想刷榜单或者做高精度业务模型SGD 系更值得花时间调。再补充一点小经验模型非常深超过 100 层时SGD 系的学习率需要配合更精细的初始化才稳定Adam 系对深层的尺度变化容忍度更高所以大模型预训练阶段基本是现代 Adam 家族的天下。3.2 学习率设置与退火策略学习率是比优化器本身更关键的开关。我用过的比较靠谱的经验值SGD/带动量 SGD 初始学习率常见 0.01 到 0.1视觉分类任务常从 0.1 开始找Adam 常见 0.001 到 0.0003Transformer 常用 2e-4 或 1e-4 甚至更低大规模预训练模型通常 1e-5 到 5e-5稍大就会 loss 发飘。学习率不能一尘不变。训练初期需要大一点让参数快速到达“靠谱区域”训练后期需要小一点让参数在最优解附近精细搜索。常见做法有指数衰减每 N 个 epoch 学习率乘个 0.9 之类的系数、余弦退火Cosine Annealing学习中后期平滑降到很小配合 Warm Restart 还能跳出局部小洼地。我最常用的组合是“Warmup 余弦衰减”前 5% 的步数从很小的学习率线性爬升到目标值防止刚开始大步子踩空之后按余弦曲线逐渐降低收敛曲线的尾部非常干净漂亮。关于 Warmup 的原理解释一下训练开始时参数还是随机的直接上一大批梯度很容易让早期更新过于激进而且 Adam 的二阶动量估计还没积累起来前几步的“自适应”是不可靠的。Warmup 相当于给优化器一个适应期。3.3 Adam 与 SGD 的切换心得这是我把参数调到一定程度之后非常喜欢的“绝招”先用 Adam 快速跑一个大体可靠的模型把学习率、网络结构、数据增强这些大头确定下来然后切换到带动量 SGD或 NAG从头或从当前权重继续训练往往能把泛化误差再压一截。原因前面说了Adam 在很多任务上偏向于收敛到尖锐的最小值SGD 则偏向于平坦的最小值后者测试误差通常更小。切换时有两个坑。第一个坑直接换优化器会导致 loss 短暂飙升因为两种优化器的参数更新尺度完全不同好在一般几个 epoch 内能重新稳定不用被吓到。第二个坑切换后要重新搜索学习率不能接着用 Adam 的旧值。经验上 SGD 的合适学习率往往比 Adam 高一个数量级建议从 0.01 左右重新试用三五个短训练确认合适的量级再跑长训。4. 训练调优中的常见问题与避坑技巧4.1 Loss 震荡不收敛怎么排查最典型的症状是 loss 曲线像锯条一样上下抖。第一优先检查学习率是不是太大试着把学习率缩小 10 倍如果曲线明显变平稳就说明先前步子迈太大。第二个常见原因是 batch size 太小小批量梯度噪声大sample 出来的方向不稳定可以适当增大 batch size。第三个容易被忽略的是数据本身标签噪声大、样本分布异常、脏数据比例高都会让 loss 无法稳定下降此时光调优化器没用需要清理数据或检查 loss 权重的设置。如果是 Adam 训练过程中 loss 直接跳动到 NaN多半是梯度爆炸。可以先用torch.nn.utils.clip_grad_norm_设置梯度裁剪clipping把梯度范数限制在一个安全范围内比如 1.0 或 0.5。这个操作对长序列 Transformer 尤其重要。顺便一提如果用了混合精度训练NaN 也可能来自 fp16 下的数值溢出要配合动态缩放Grad Scaler处理。4.2 AdaGrad 和 Adam 的高阶注意事项AdaGrad 在长时间训练中的“学习率归零”问题我前面提过在实际项目中我建议不要直接用原始 AdaGrad 去训练深度模型否则你会看到训练到一半 loss 基本不动了不熟悉的人会以为模型“死了”其实是分母里的累计平方和把步长压没了。如果一定要用自适应类优化器做稀疏数据优先考虑 Adam 或支持稀疏梯度的 AdamW。Adam 也有一个比较隐蔽的坑权重衰减weight decay的处理。早年不少实现直接把 weight decay 加进 L2 正则里但 Adam 的自适应机制会破坏这个正则的语义导致实际正则效果和预期不符所以 AdamW 把权重衰减从梯度里拆出来在参数更新时单独做一次衰减。经验上是用 Adam 做预训练或大规模模型时不要简单开启 weight_decay0.01 了事建议明确使用 AdamW并把权重衰减的目标集中在合适的层比如只对权重、不对偏置和 LayerNorm 的 γ/β 做衰减能省掉不少泛化下降的烦恼。4.3 从“练不出来”到“练得动”的排查顺序训练效果差很多人第一反应是去调优化器参数但优化器往往不是罪魁祸首。我的排查顺序是先看 loss 是否在原子层面正常下降排除实现 bug、数据 pipeline 问题再确认初始化方式是否合理比如残差网络、Transformer 的初始化往往要特殊设计然后查学习率和 warmup 的设置最后才考虑优化器类型和超参。这个顺序能帮你省大量时间。另一个很多人忽视的现象模型在训练集上 loss 下降正常测试集表现差你调 Adam 的 β2 或者 momentum 不会有本质影响。此时应该从正则化、数据增强、dropout、标签平滑、早停策略这几个方向下手。优化器能帮你“到得了低点”但能不能“低得有价值”更多取决于正则化与数据策略。把这层关系理清楚就不会在 optimizer 的超参上白费力气。我自己这几年跑实验最大的体会是优化器从来不是一个孤立的选择它跟学习率、batch size、数据分布、权重衰减和任务类型纠缠在一起。与其花大力气追求“最好的优化器”不如先把这四类算法的“脾气”摸清楚再根据项目当前的瓶颈去选。新手期先用 Adam 跑通万事进阶之后再尝试带动量 SGD 刷精度是一条非常平滑的成长路线。最后再补一句话如果你真的只想记一个配置那就记 Adam 的默认参数加个 cosine 退火学习率绝大多数情况都不会让你失望。