优化器家族里的“调参手感”很多时候就藏在理解上。我见过不少人把 Adam 当默认配置直接套loss 掉不下去就从网络结构找问题很少有人意识到优化器本身的更新规则可能才是收敛慢的根源。这篇博文梳理一下我实际训练模型时反复用到的一组优化技术——指数加权平均EWA、动量梯度下降Momentum、RMSprop 和 Adam从背后原理讲到可以照抄的超参配置希望能让后面的人避开我踩过的那些坑。我默认你已经知道神经网络反向传播在算梯度不了解优化器也不影响阅读。重点会放在为什么这些方法有效默认参数为什么是那几个数实际项目里到底怎么选1. 从SGD的困境说起为什么普通梯度下降容易卡在半山腰1.1 朴素SGD的更新逻辑随机梯度下降Stochastic Gradient Descent, SGD的更新规则很简洁每一步沿着负梯度方向挪一小步。θ θ - lr * gradientlr 是学习率通常取 0.01、0.001 这种量级。在损失曲面上这相当于“看着脚下哪边陡就往哪边迈”方向永远由当前点的梯度决定。这样做看似合理实际操作中却有两个常见困境振荡和缓慢的之字形路径。1.2 病态曲率让SGD走“之”字想象一个长长的山谷谷底在 x 方向平坦延伸y 方向却很陡峭。这在实际网络里非常常见——某个权重方向的梯度长期很大另一个方向却长期很小。学习率大一点y 方向来回振荡loss 忽高忽低学习率小一点x 方向前进得极其缓慢。这就是数值优化里的“病态曲率”问题ill-conditioning。换成更生活化的说法不同参数对学习率的要求不一样但SGD只能给所有参数同一个学习率。这是一个结构性的矛盾不换算法很难解决。1.3 mini-batch噪声加剧了振荡现代深度学习几乎都是 mini-batch 训练每次用一小批数据算梯度。批次一换梯度方向天然带噪声SGD 的每一步都“抖”。如果损失曲面本身又狭长抖动就变成横跨山谷的侧向摆动。早期训练中我经常看到 loss 曲线是锯齿状下降的乍看很正常其实每一步里有一半精力浪费在无效的左右横跳上。动量、RMSprop、Adam 这些方法的核心动机很多就是针对“怎么把这种抖动消掉”以及“怎么给不同参数配不同步长”这两件事。2. 指数加权平均EWA动量优化最核心的“记忆机制”2.1 EWA在做什么用历史信息给梯度“做平滑”指数加权平均Exponential Weighted Average, EWA也叫指数滑动平均。它的公式很多人见过v_t β * v_{t-1} (1 - β) * θ_t这里 θ_t 可以是梯度、可以是参数、可以是任何时序值β 是衰减系数。展开看v_t 其实是历史所有 θ 的指数加权和v_t (1 - β) * (θ_t β * θ_{t-1} β² * θ_{t-2} ... β^(t-1) * θ_1)注意权重是指数级衰减的所以 v_t 对近期数据敏感对很久以前的数据“有记忆但很模糊”。这就像你記一个城市的“夏季体感温度”不是只用今天的气温而是把最近几周的温度揉在一起看。天气单日会剧烈波动看一个月均值就平滑多了。EWA本质上就是一个带遗忘因子的滑动平均滤波器。2.2 β值到底代表什么有效窗口期的直觉算法β 越接近 1记忆越长。有一个简单的换算可以把有效窗口近似看成N ≈ 1 / (1 - β)β 0.9 → 约 10 步的平均β 0.99 → 约 100 步的平均β 0.999 → 约 1000 步的平均这个近似在工程上很有用。调整 β 就相当于在“平滑程度”和“响应速度”之间做权衡β 太大曲线平滑但对新变化反应慢β 太小跟手但噪声也留下来了。2.3 你绝不能忽略的偏差修正EWA 有个新手很容易踩的坑初始化 v_0 0。前几步的 v_t 会被严重压低因为早期累加的历史不够多。举个例子β 0.9t 1 时v_1 0.9 * 0 0.1 * θ_1 0.1 * θ_1真实平均值应该接近 θ_1但你算出来只有十分之一。前几十步这种低估都会明显拖慢收敛尤其是训练初期。解决办法是偏差修正bias correction把分母除一下v_hat v_t / (1 - β^t)t 越大β^t 趋近 0修正项趋近 1t 较小时修正项明显大于 1把前期低估拉回来。我一开始总把这个修正当成“锦上添花”后来做梯度统计可视化才发现不修正的话前 50 步的梯度方差偏高训练曲线明显僵硬。规范做法是只要用 EWA 式更新就把偏差修正加上成本几乎为零。3. 动量梯度下降Momentum带着惯性越过振荡区3.1 从一个球的比喻开始动量梯度下降Momentum的更新逻辑是速度向量不再直接等于当前梯度而是梯度与历史速度的合成。v_t β * v_{t-1} gradient θ θ - lr * v_t可以想象你从山顶推一个球下去球不会每一步都完全顺着当前山坡的切线走而是带着之前的动能。遇到局部小坑或者侧向陡坡球不会立刻被拽离主方向——因为惯性让它更倾向于保持原来的大方向。对应到训练里就是横跨山谷的侧向振荡互相抵消而沿着山谷平坦方向的速度持续累积。同样的学习率动量的实际推进速度常常是SGD的好几倍。3.2 怎么理解 β 对动量的调控动量里的 β 是速度衰减系数。常用值β 0.9标准配置相当于平均过去约 10 步的梯度β 0.99更强的惯性适合损失曲面特别崎岖时β 0.8弱化历史适合任务本身波动较小、需要快速跟随梯度转向时。我在实践里有个体会β 调的幅度不需要精细到小数点后两位0.9 到 0.95 之间是一个安全区间。重要的是动量的 β 不需要和 EWA 的 β 分开记忆两者的原理是同一个。3.3 为什么 mini-batch 越大动量越显得有价值动量在 mini-batch 训练中的收益尤其明显。小批量梯度噪声通常可以初步看成零均值扰动动量对梯度做时间平均天然把噪声滤掉了一部分。全批量梯度下降动量更多体现在“越过局部极小值”mini-batch 梯度下降动量则同时带来“平滑梯度方向”的作用。我做过一个不太严谨但很直观的对比ResNet 类模型batch size 从 64 翻到 256SGD 的收敛曲线明显变抖换 Momentum 之后两者曲线差异大幅缩小。这说明动量的平滑效果在 batch size 变大带来的噪声降低之外还能额外补偿方向不一致问题。3.4 Momentum 的 PyTorch 实现小抄PyTorch 里用SGD加上momentum参数即可import torch.optim as optim optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)注意 PyTorch 的 SGD 实现momentum0时是纯SGDmomentum0时直接采用动量速度累积不需要额外封装。4. RMSprop用二阶矩估计给每个参数配专属步长4.1 AdaGrad 的教训累积平方导致学习率过早死亡RMSprop 不是凭空出现的。它前面有一个 AdaGrad思路是梯度大的方向步长应该小一点梯度小的方向步长应该大一点。AdaGrad 维护一个历史梯度平方和G G gradient² θ θ - lr * gradient / sqrt(G ε)问题是 G 单调递增。训练后期 G 巨大sqrt(G) 也跟着巨大所有参数的学习率被压到趋近于 0。结果就是模型还没收敛学习率就先“死亡”了。我在较早的训练框架里调过 AdaGrad几十个 epoch 后 loss 基本纹丝不动就是这个原因。4.2 RMSprop 的改进用 EWA 代替累加RMSprop 的提出很朴素既然 G 不能无限累积那就用指数加权平均把历史平方梯度缩放到一个稳定区间。s_t β * s_{t-1} (1 - β) * gradient² θ θ - lr * gradient / sqrt(s_t ε)这里的 s_t 是梯度的二阶矩估计也就是均方根。所以 RMSprop 更新的步子大小等于有效步长 lr / sqrt(s_t ε)分母本质上是对梯度幅度的动态归一化。梯度长期偏大的方向s_t 偏大步长被压缩梯度长期偏小的方向s_t 偏小有效步长放大。这样每个参数就有了自己独立的学习率不再被全局同一个学习率困死。4.3 为什么要加 ε以及 ε 的量级s_t 可能接近 0直接做除法会造成数值不稳定。ε 是一个极小的平滑项常用默认值ε 1e-8在极端量化训练、混合精度训练里可能需要 ε 1e-6 或 1e-4我遇到过梯度裁剪后训练曲线在个别 batch 突然冲高排查到最后是混合精度下 s_t 下溢导致除零异常。把 ε 从 1e-8 提到 1e-6 就稳了。4.4 RMSprop 的适用场景RMSprop 在循环神经网络、语音模型这类梯度尺度差异非常大的模型中表现很稳。CNN 上它的收敛速度通常比纯SGD快但最终精度和超参敏感度不如 Adam 好调。它更像一个“过渡方案”——理解了 RMSprop 再看 Adam会顺很多。值得强调的是RMSprop 里 β 的默认建议值是 0.999而不是动量的 0.9。因为平方梯度经常出现极端大值需要更长的平滑窗口来稳定估计如果用 0.9二阶矩波动太大步长忽大忽小。5. Adam一阶矩与二阶矩的协同工作逻辑5.1 动量自适应步长Adam就是把两件事缝合AdamAdaptive Moment Estimation几乎是把前面两个思想直接拼接一阶矩估计 m_t用 EWA 平滑梯度等于动量的核心思想二阶矩估计 v_t用 EWA 平滑梯度平方等于 RMSprop 的核心思想。两个矩估计都带偏差修正m_hat m_t / (1 - β1^t) v_hat v_t / (1 - β2^t)最后更新θ θ - lr * m_hat / (sqrt(v_hat) ε)用一句话概括Adam 的方向由动量的历史梯度决定步长由 RMSprop 的自适应缩放决定。5.2 四个超参分别管什么Adam 的默认超参大家在文档里都能看到但很少人真正逐项理解过参数默认值实际作用lr0.001全局步长基准依然最重要β10.9一阶矩衰减系数控制动量窗口β20.999二阶矩衰减系数控制自适应缩放窗口ε1e-8数值稳定项避免除零β1 0.9表示速度大约平均最近 10 步的梯度β2 0.999表示二阶矩大约平均最近 1000 步的平方梯度。二阶矩窗口要比一阶矩长很多否则自适应缩放会因为单个大梯度剧烈抖动。这两个值不是随便定的默认值在中大规模数据上通常表现稳健但小数据集上训练初期可以考虑调大 β2 的修正效果或者用 Adam 的 bias-correction 版本直接省心。5.3 Adam 的偏差修正在实际训练中的意义PyTorch 的Adam默认就带 bias correction。动手实现时需要自己加。我早期手写过简化版 Adam漏了偏差修正结果前几百步 loss 下降异常缓慢走的路和我用现成优化器跑出来的完全对不上。当时花了一整天排查最后发现是前几步的 m_hat 和 v_hat 都偏小步长被严重低估。经验总结自己实现优化器时一定先和框架自带版本对齐指标再谈修改和创新。5.4 Adam 的缺点泛化和权重衰减的隐藏陷阱Adam 不是银弹。实践上有两个公认需要注意的地方泛化性略弱于调好的 SGDMomentum。不少图像分类任务里SGDMomentumlr 调得很仔细最终测试精度会比 Adam 高零点几个百分点。原因有一种解释是自适应学习率让参数更新更“急”训练后期在极小值附近退火不够充分。L2 正则和 Adam 结合要小心。Adam 会把权重衰减也“自适应缩放”一遍效果和初衷可能不一致。所以后来有了 AdamW——把权重衰减从梯度里拆出来单独处理。我现在处理带强权重衰减的表征学习任务时优先试 AdamW如果只是快速验证网络结构能不能收敛直接用 Adam 默认参数省心。5.5 以 Pdf 比较四种优化器行为优化器平滑梯度自适应步长偏差修正典型场景SGD无无无简单基线、精细调参Momentum有无可省图像分类、CV 任务RMSprop无(仅平方)有一般有RNN、序列建模Adam有有有快速验证、NLP、多模态6. 实战选型、调参与我踩过的坑6.1 选优化器的实用决策链路现在让我帮你把选型逻辑收敛成一套可执行的规则如果你要快速验证一个新模型结构是否有效直接用 Adamlr1e-3跑几十个 epoch 看 loss 有没有下降趋势如果结构没问题、想刷最终精度换 SGDMomentumlr 从 0.1 起按批次大小做线性缩放配合 cosine 退火如果任务是 Transformer 类用 AdamW且通常配 warmup 余弦退火lr 从 1e-4 到 5e-4 之间搜索如果 loss 在训练中频繁尖刺先别急着换优化器检查一下数据批次的 label 噪声和梯度裁剪阈值再考虑调 ε。6.2 lr 与 batch size 的联动一个经常被忽略的参数很多优化器问题最后都归结到学习率。我有一次在某个图像分类任务上调 Adam固定 batch size 128lr 从 1e-3 提到 3e-3loss 直接发散降到 3e-4收敛又太慢。后来把 batch size 调到 256用 1e-3 就稳稳收敛。原因在于batch size 增大时梯度噪声更小可以用更大的学习率反之则要降 lr。Adam 虽然对 lr 有一定鲁棒性但这种联动同样有效。线性缩放规则lr ∝ batch size在 SGD 上更严格在 Adam 上可以松一点。6.3 热启动和 warmup 的价值训练初期梯度统计还没稳定尤其 Adam 的二阶矩从零开始累积如果直接用较大的 lr前几步可能迈得过大后面很难拉回来。一个稳定的做法是 warmup前 5~10 个 epochlr 从 0 线性增长到目标值然后按余弦退火cosine annealing衰减到 0。我在 Transformer 类模型上调参时没有 warmup 的组合几乎都会出现前 200 步 loss 冲高加了 10 epoch warmup 之后曲线平顺很多。6.4 用梯度统计诊断优化器状态迭代很多次之后我发现一个很实用的调试技巧定期打印梯度和更新量的均值/方差而不是只看 loss。如果梯度范数一直很大说明 lr 可能太大或者梯度裁剪需要更紧如果更新量的方差长期比梯度方差小一个量级说明被二阶矩分母压得太狠可以试着调小 β2 或调大 lr如果动量的速度向量方向与梯度方向长期夹角超过 90 度说明 β 过大历史惯性在“拖后腿”。这个诊断在 TensorBoard 里配几个add_scalar就行。肉眼观察 loss 曲线的滞后性太强等看到问题的时候往往是几十个 epoch 之后了。6.5 我最后悔的一次“优化器迷信”曾经有一个推荐系统模型我的第一反应是直接上 Adam默认 lr调了两周效果平平AUC 一直卡在 0.73。后来某天我把优化器切成 SGDMomentumlr0.01加了一个简单的阶梯衰减两轮就跑到了 0.78。不是什么魔法而是这个任务的损失曲面相对平滑Adam 的“自适应”优势根本用不上反而因为它的隐式正则和 SGD 不同陷入了一个更平的高偏差区域。这件事给我的教训是优化器选型不要凭习惯先看问题本身需要的是平滑梯度还是自适应步长最稳妥的做法是两种都跑个基础实验再决定。6.6 一些可以直接用的超参起点为了让你少折腾我把验证过的小抄放在这里图像分类任务CNNSGDlr0.05batch size 128 基准momentum0.9weight_decay1e-4cosine退火NLP 文本分类BERT 微调AdamWlr2e-5weight_decay0.01warmup ratio0.1快速验证新结构Adamlr1e-3β10.9β20.999ε1e-8强化学习策略网络RMSprop 或 Adamlr3e-4gradient clip 1.0β20.99大模型预训练AdamWlr 峰值 1e-4 到 3e-4warmup 2000 步β10.9β20.95权重衰减 0.1。这些起点不能保证最优但保证不会短时间内跑飞。之后微调优先动 lr其次动 weight_decay最后才动 β 和 ε。7. 总结与经验这些方法如何串起来用EWA、Momentum、RMSprop、Adam 不是四套互相孤立的技术而是一条从“让梯度平滑”到“让每一步合理缩放”的演进线。EWA 提供了基础的记忆机制Momentum 把记忆用在方向上RMSprop 把记忆用在步长上Adam 则用两套记忆同时控制方向和步长。我自己现在训练模型时的默认心态是Adam 解决 80% 的“能不能收敛”问题但“收敛得好不好”往往要回到 SGDMomentum 和仔细调 lr 上。优化器是一项系统工程不要迷信任何一个默认值。最后再分享一个小技巧如果你想深入理解这些优化器别光看公式——亲手把 Adam 实现一遍打印一下 m_hat 和 v_hat 在前 50 步的低估程度体会会深很多。我当年就是靠这个方式把动量、偏差修正这些概念彻底吃透的后面再遇到任何新的优化器变体都只是在这些基本组件上做组合而已。
阅读完成 · 觉得有帮助?