从梯度下降到 AdamW:优化器全家族
手算每一步更新,看清 SGD、动量、自适应学习率到 AdamW 各自解决上一代的什么缺陷
- 能写梯度下降更新式并手算一步,判断学习率过大过小的后果
- 区分 BGD/SGD/mini-batch 并解释小批量噪声的利弊
- 说清动量、AdaGrad、RMSProp、Adam 逐级解决了什么问题
- 搞懂 Adam 的偏差校正、AdamW 的解耦权重衰减,能直接用来做工程选型
优化器的进化史,就是一部「不断给负梯度打补丁」的历史
所有深度学习优化器都从同一个最简公式出发:θ←θ−η·∇L,沿负梯度走一小步。但这个朴素方法在真实损失曲面上毛病不断——步长难调、峡谷震荡、鞍点停滞、不同参数需要不同步长。每一代优化器都精准修补上一代的一个具体缺陷:动量治「方向摇摆与停滞」,AdaGrad/RMSProp 治「所有参数共用一个步长」,Adam 把两者合并,AdamW 再修正它与权重衰减耦合的问题。这一节你要亲手算每一步,理解补丁打在哪。
4.1 梯度下降:更新式与一步手算
更新式 θt+1=θt−η·∇L(θt),η 是学习率,控制每步迈多大。以 f(x,y)=x²+y²(梯度 [2x,2y])为例,从 θ₀=[3,4]、η=0.1 出发:∇=[6,8],θ₁=[3−0.1×6, 4−0.1×8]=[2.4,3.2];再一步 ∇=[4.8,6.4],θ₂=[1.92,2.56]。每一步都离原点(最小值)更近,这就是收敛过程。
4.2 学习率:太大发散,太小太慢
学习率是最重要也最难调的超参数。对 x²,最优固定步长恰好是 η=0.5:x=3 时一步 x′=3−0.5×6=0,直达最小。η 再大就会出问题:η=1.0 时 x′=3−6=−3,下一步 −3−(−6)=3,在 ±3 间永久横跳;η>1 则每步离原点更远、直接发散。反过来 η 太小,每步只挪一丝,收敛慢到不可用。实践中用学习率调度(warmup 后余弦或阶梯衰减)让步长「先大胆探索、后小心收敛」。
对 f(x)=x² 用梯度下降,x=3、η=0.5,更新一次后 x 等于多少?训练损失不降反而周期性剧烈震荡,最可能的原因与对策是?
4.3 三种梯度:全量、随机与小批量
梯度下降按每次算梯度用多少样本,分三种。批量梯度下降 BGD 用全部样本算精确梯度,方向准,但每步极慢。随机梯度下降 SGD 每步只用 1 个样本,快,但梯度噪声大、路径抖动。mini-batch SGD 每次用 32~512 个样本,是速度与稳定的折中,也是现代深度学习的默认选择。噪声看着是缺点,实则能帮着跳出鞍点、提供正则化效果,但得配合学习率衰减,才能保证后期收敛。
4.4 动量:把历史梯度累积成「下坡惯性」
朴素 SGD 跑在狭长峡谷里会出问题:沿陡壁反复横跳,沿谷底走得却很慢。动量法加了个速度变量 v,更新规则是 v=β·v+∇L,θ←θ−η·v,β 一般取 0.9。本质是把过去的梯度做指数加权累积:方向一致的分量会不断叠起来,相当于沿谷底加速;来回反向的横跳分量会互相抵消,能抑制震荡。另外靠惯性,还能冲过浅鞍点和小的局部极小值。
手算两步(暂令 η=0.1、β=0.9,从 x=3、梯度 g₁=6 起):v₁=0.9×0+6=6,x₁=3−0.1×6=2.4;此处 g₂=4.8,v₂=0.9×6+4.8=5.4+4.8=10.2。注意第二步速度 10.2 已大于当前梯度 4.8——历史同向梯度让它「越冲越快」,这正是动量在一致方向上加速的体现。
动量有个很有名的改进叫 Nesterov 加速梯度(NAG):标准动量是在当前位置算梯度,再叠加历史速度;NAG 不一样,它先按历史动量「探一步」到 θ+βv,在那个前瞻位置算梯度,再回拉修正。 直觉上就是「先凭惯性冲出去,到落点再看坡该怎么调」,凸问题上有可证明更快的收敛率,实际用的时候后期震荡通常更小。 SGD 里把 nesterov 设为 True 就是开这个功能,Adam 的一阶矩设计也吸收了这种前瞻修正的思路。
4.5 自适应学习率:AdaGrad→RMSProp→Adam
不同参数得配不同步长:频繁更新、梯度一直大的参数用小步长,稀疏、偶尔才来大梯度的参数给大步长。AdaGrad 会累积每个参数的历史梯度平方和 G,更新步长改成 η/√(G+ε)·g——梯度大的参数分母涨得快,步长自动被压小。缺陷是 G 只增不减,步长最后会趋于零,训练提前停滞。
RMSProp 把「全历史累加」换成「指数移动平均」s=β₂·s+(1−β₂)·g²,让旧梯度逐渐遗忘,分母不会无限膨胀,解决了 AdaGrad 的早停。Adam 再把动量也并进来:一阶矩 m=β₁m+(1−β₁)g(动量方向)、二阶矩 s=β₂s+(1−β₂)g²(自适应步长),更新 θ−=η·m̂/(√ŝ+ε)。它兼顾加速与逐参数自适应,是绝大多数任务的稳健默认。
Adam 为什么要做偏差校正(手算 t=1)
m、s 都初始化为 0,且 β₁、β₂ 取 0.9 左右接近 1,训练最初几步的移动平均会被 0 初值「拖小」:第一步 m₁=(1−β₁)g=0.1g,明显小于真实梯度 g。偏差校正用 m̂=m/(1−β₁ᵗ)、ŝ=s/(1−β₂ᵗ) 补回这个偏差。手算 t=1、β₁=0.9:m=0.1g,m̂=0.1g/(1−0.9)=0.1g/0.1=g,恰好还原真实梯度;随 t 增大 β₁ᵗ→0,校正项趋近 1、自动失效。所以偏差校正只在早期热身阶段起关键作用,却能避免开局步长被严重压低。
来,咱们亲手把 Adam 一步步算一遍,就明白它到底在干嘛了。先给初始条件:某参数首步梯度 g₁=10,β₁=0.9、β₂=0.999、ε=1e-8,m₀=s₀=0、t=1。算一阶矩 m₁:0.1×10=1。算二阶矩 s₁:0.001×100=0.1。做偏差校正:m̂=1/(1−0.9)=10,ŝ=0.1/(1−0.999)=100。最后算有效更新方向:m̂/(√ŝ+ε)=10/10=1,这一步实际走的步长就是 η×1。 接着算第二步,这时候梯度变小了,g₂=1。m₂=0.9×1+0.1×1=1.0,校正后 m̂=1.0/(1−0.81)≈5.26。s₂=0.999×0.1+0.001×1=0.1009,校正后 ŝ=0.1009/(1−0.998001)≈50.5,√ŝ≈7.11。有效方向≈5.26/7.11≈0.74。 看出来了吧?Adam 用二阶矩把步长的幅度给归一化了,最终步长和梯度的绝对大小基本没什么关系,主要保留的是梯度的方向。这就是它对学习率没那么敏感的原因。 对比下朴素 SGD:g=10 和 g=1 时,步长直接差十倍,梯度一骤减,立刻就走得很慢。Adam 经过二阶矩归一后,两步的方向量大概是 1 和 0.74,波动小很多。所以在梯度尺度变化剧烈的训练早期,它更稳。但到了训练后期,它也可能过度「抹平」那些有用的大梯度——这就是它和精调过的 SGD 泛化能力有差异的来源之一。
4.6 AdamW:把权重衰减从「梯度」里解耦出来
原版 Adam 实现 L2 正则的方式,是把 λθ 直接加进梯度再送入一阶/二阶矩估计,这会让正则项也被自适应学习率缩放、跟逐参数步长纠缠,大模型上效果变差。AdamW(W 指 decoupled weight decay,解耦权重衰减)改在更新时单独、直接收缩参数:先按 Adam 算一步,再 θ←(1−η·wd)·θ。正则不再经过 m、s,强度稳定可控,是训练大语言模型几乎默认的优化器。
# AdamW 核心更新(伪代码,不在浏览器运行)
# m,s 初始为 0;beta1=0.9,beta2=0.95,eps=1e-8,wd=权重衰减
m = beta1*m + (1-beta1)*g
s = beta2*s + (1-beta2)*g*g
mhat = m/(1-beta1**t); shat = s/(1-beta2**t)
theta = theta - lr*mhat/(shat.sqrt()+eps) # Adam 步
theta = theta*(1 - lr*wd) # 解耦的权重衰减,单独收缩两笔常被忽略的工程账。其一是显存:Adam 要为每个参数额外存一阶矩 m 和二阶矩 s,等于多花约 2 倍参数量的优化器状态,混合精度下还要另存一份 FP32 主权重,大模型训练里优化器状态往往是显存最大头,这正是 ZeRO/FSDP 要分片优化器状态的原因(stage-14 展开)。其二是学习率调度与优化器的配合:Transformer 几乎必做 warmup(从零线性升到峰值,避免早期二阶矩未稳时步长失控),再用余弦衰减逐步收小以稳定落入低损失盆地。优化器决定「怎么迈步」,调度决定「每步迈多大」,两者要一起设计。
更新最简、泛化常很好,但需精细调学习率,在峡谷/鞍点处慢且抖,常配动量使用。
训练 Transformer 普遍用 AdamW:β₁=0.9、β₂=0.95(比默认 0.999 更稳,适配大模型梯度分布)、eps≈1e-8、权重衰减 0.01~0.1(对 LayerNorm/偏置通常不衰减)、梯度裁剪全局范数 1.0;学习率先线性 warmup 数百~数千步再余弦衰减。这些不是玄学,而是为稳住混合精度下的二阶矩与早期梯度尖峰。
自适应学习率收敛快,但部分任务上最终泛化比精调的 SGD+动量差一点——自适应可能过早把探索步长压小。Adam 对学习率依然敏感,不是不用调。追求极致泛化的视觉任务,一般会回到 SGD+动量;大模型预训练要兼顾稳定性和规模,几乎统一用 AdamW。
这节讲的全是一阶方法——只用到梯度。牛顿法靠 Hessian 逆矩阵自动确定步长和方向,收敛更快,但要计算、存储二阶矩阵,参数量上亿的时候根本用不了;L-BFGS 这类拟牛顿法用低秩近似,放到小批量场景又很难稳定。这就是深度学习退回来用一阶方法,靠动量和自适应来补短板的根本原因。
损失不降先按这个顺序排查:梯度是否真的回传(打印 grad 范数);学习率是否差了数量级(1e-3/1e-4/1e-5 各试);是否忘记 zero_grad 或梯度裁剪过狠;换 AdamW 是否立刻改善;最后才怀疑数据与模型。多数「模型不学习」是前两条。
AdamW 相对原版 Adam 最关键的改进是?
本节小结
一条进化链:梯度下降 θ←θ−η∇L(手算 [3,4]→[2.4,3.2]),学习率过大横跳发散、过小缓慢 → mini-batch 在速度与噪声间折中,噪声助逃鞍点 → 动量累积同向梯度、抵消横跳、冲过鞍点 → AdaGrad 逐参数压步长但会早停,RMSProp 改移动平均修复,Adam 合并动量+自适应并做偏差校正(t=1 时 m̂=m/(1−β₁)=g)→ AdamW 解耦权重衰减、直接收缩参数,成为大模型默认。选型:求稳与大模型用 AdamW,极致泛化可试精调 SGD+动量。
资深工程师加餐
底层原理 · 大厂视角 · 工程经验,点卡片展开
信息熵度量一个分布的不确定程度,越随机熵越大。交叉熵衡量「用模型预测的分布去编码真实分布」所需的平均代价:模型预测越接近真实标签,交叉熵越小。因此多分类任务用交叉熵做损失不是拍脑袋,而是有严格信息论依据的最大似然等价形式。