优化器

统一记号:

  • 目标函数(loss):L(θ)L(\theta),参数向量:θ\theta
  • tt 次迭代(iteration)参数:θt\theta_t
  • 梯度:gt=θL(θt)g_t=\nabla_{\theta}L(\theta_t)
  • 学习率:η\eta
  • mini-batch:Bt\mathcal{B}_t,其梯度估计记为 gt=θLBt(θt)g_t=\nabla_{\theta}L_{\mathcal{B}_t}(\theta_t)
  • 动量法中的速度变量记为 utu_t;Adam 系列中的一阶矩、二阶矩分别记为 mt,vtm_t, v_t

优化器是在反向传播给出梯度之后,决定“参数往哪里走、走多远”的更新规则,一个最常见的形式是损失最小化:

minθ  L(θ)=1Ni=1N(f(xi;θ),yi)\min_{\theta}\; L(\theta)=\dfrac{1}{N}\sum_{i=1}^{N} \ell\big(f(x_i;\theta),y_i\big)

反向传播(backprop)负责高效地算出梯度 θL(θ)\nabla_{\theta}L(\theta);优化器负责把梯度变成一次次参数更新。

Vanilla GD(批量梯度下降,Batch Gradient Descent)

最朴素的梯度下降每次用全量数据计算精确梯度,然后沿负梯度更新:

gt=θL(θt),θt+1=θtηgtg_t=\nabla_{\theta}L(\theta_t),\qquad \theta_{t+1}=\theta_t-\eta\,g_t

优点:

  • 梯度精确、更新方向稳定;对凸问题有清晰理论。

缺点:

  • 每步都要扫全数据集,代价高;大模型/大数据下几乎不可用。
  • 对非凸深网也不具备“逃离坏点”的随机性。

SGD(随机梯度下降,Stochastic Gradient Descent)

SGD 每次只用一个样本(或极小 batch)来计算梯度:

gt=θ(f(xit;θt),yit),θt+1=θtηgtg_t=\nabla_{\theta}\ell\big(f(x_{i_t};\theta_t),y_{i_t}\big),\qquad \theta_{t+1}=\theta_t-\eta\,g_t

优点:

  • 单步开销小、更新频繁;在大数据下非常高效。
  • 梯度噪声有时反而有益:更容易“抖”出鞍点或糟糕的局部区域。

缺点(直觉上也最常见):

  • 更新噪声大,loss 曲线抖动明显。
  • 在狭长沟壑(某些维度很陡、某些维度很平)中容易来回震荡。
  • 在鞍点附近(一个方向上凸、另一个方向上凹)可能进展很慢。

图 a) 中,起点的位置会影响梯度下降的结果:在点 1,3 处,参数梯度下降到了全局最小值,而在 2 处,错误的起点使其下降到了局部最小值。在图 b) 中,SGD 的更新方向由于噪声的存在而变得不稳定,从而使得参数能够跳出鞍点区域,继续向下优化。

Mini-batch SGD

实践中最常见的是 mini-batch SGD:用一个小批量 Bt\mathcal{B}_t 在当前位置 θt\theta_t 估计梯度:

gt=θLBt(θt),θt+1=θtηgtg_t=\nabla_{\theta}L_{\mathcal{B}_t}(\theta_t),\qquad \theta_{t+1}=\theta_t-\eta\,g_t

它介于 GD 和 SGD 之间:

  • batch 变大 → 梯度方差更小、更新更稳,但单步更贵。
  • batch 变小 → 更新更“吵”,探索更强,但可能更难调、吞吐不一定更高。

工程里通常按硬件吞吐与收敛稳定性折中选 batch size;并配合学习率缩放(例如线性缩放)与 warmup。

SGD with Momentum(动量法)

动量法的核心是引入“速度”变量 utu_t,对梯度方向进行指数平滑,从而减少震荡并加速沿一致方向的前进:

ut+1=μutηgtu_{t+1}=\mu u_t-\eta\,g_t

θt+1=θt+ut+1\theta_{t+1}=\theta_t+u_{t+1}

其中 μ[0,1)\mu\in[0,1) 是动量系数,常见经验值是 0.9。

优点:

  • 在“长沟壑”场景明显加速;抑制来回震荡。
  • 比 vanilla SGD 更稳、更好调。

缺点:

  • 仍可能在某些非凸区域走得过头(overshoot)。
  • 额外超参数(μ\mu)与更复杂的调度策略。

直观上看无动量的随机梯度下降以非直接的路径接近最小值,而有动量的随机梯度下降则由前一段优化方向和当前 batch 的梯度加权组合产生,可以平滑轨迹加快收敛。

Nesterov Momentum(NAG,Nesterov 加速梯度)

Nesterov 的直觉是“先按动量往前看一步,再在那个位置上评估梯度”,因此更像是在用更前瞻的梯度修正动量方向。

一种常见写法是:

gt=θL(θt+μut)g_t = \nabla_{\theta}L(\theta_t+\mu u_t)

ut+1=μutηgt,θt+1=θt+ut+1u_{t+1}=\mu u_t-\eta\,g_t,\qquad \theta_{t+1}=\theta_t+u_{t+1}

优点:

  • 在很多任务上比普通 Momentum 稍稳,收敛更“聪明”。

缺点:

  • 仍需调学习率与动量。

Second-order methods(二阶方法)

深度学习优化里还有一类很常见的思路是基于 Newton’s method(二阶方法),其核心迭代为:

gt=θL(θt),θt+1=θt[HL(θt)]1gtg_t=\nabla_{\theta}L(\theta_t),\qquad \theta_{t+1}=\theta_t-[H_L(\theta_t)]^{-1}g_t

其中 HL(θt)H_L(\theta_t) 是损失函数 LL 关于参数 θt\theta_t 的 Hessian 矩阵(由二阶偏导数组成的方阵),gt=θL(θt)g_t=\nabla_{\theta}L(\theta_t) 是梯度向量。直觉上,Hessian 描述了损失函数的局部曲率:乘上 Hessian 的逆,会让优化在“曲率较小(更平坦)”的方向迈更大步,在“曲率较大(更陡)”的方向迈更小步。注意这个更新式里没有显式学习率超参数,这也是二阶方法常被拿来对比一阶方法的一个优点。

但在深度学习里直接计算并求逆 Hessian 往往不可行:时间和空间开销都极其昂贵。比如一个有 100 万参数的网络,Hessian 是 [1,000,000×1,000,000][1{,}000{,}000\times 1{,}000{,}000] 的矩阵,显式存储就需要约 3725 GB 内存。

因此出现了大量 quasi-Newton(拟牛顿)方法来近似 [HL(θt)]1[H_L(\theta_t)]^{-1}。其中最常见的是 L-BFGS:它利用一段时间内的梯度信息来隐式构造近似(不显式存整张矩阵)。

但即使缓解了内存问题,L-BFGS 的一个朴素应用仍然通常需要在全量训练集上工作;而把它可靠地用在 mini-batch 上更困难,至今仍是研究/工程上的活跃方向。

所以在大规模深度学习与 CNN 训练中,目前更常见的仍是基于(Nesterov)Momentum 的 SGD 变体:实现更简单、扩展性更好。

AdaGrad(自适应学习率:累积平方梯度)

AdaGrad 的动机是:不同参数维度的更新频率差异很大(例如稀疏特征/embedding),希望“常更新的参数走慢点,少更新的参数走快点”。

做法:对每个参数维护平方梯度累积 ss

st+1=st+gt2s_{t+1}=s_t+g_t^2

θt+1=θtηgtst+1+ϵ\theta_{t+1}=\theta_t-\eta\,\dfrac{g_t}{\sqrt{s_{t+1}}+\epsilon}

其中 gt2g_t^2\sqrt{\cdot}、除法都是逐元素(element-wise),ϵ\epsilon 防止除 0。

优点:

  • 在稀疏特征场景非常强;对每个参数都有自适应步长。

缺点:

  • sts_t 单调增大,导致有效学习率单调衰减,训练后期可能“走不动”。

RMSProp(自适应学习率:滑动平均平方梯度)

RMSProp 可以看作对 AdaGrad 的“降温”:不再把所有历史平方梯度都累积进去,而是用指数滑动平均跟踪一个有限时间尺度内的平方梯度。远离当前时间点的梯度信息会被逐渐丢弃,从而避免学习率过快衰减的问题。

st+1=ρst+(1ρ)gt2s_{t+1}=\rho s_t+(1-\rho)g_t^2

θt+1=θtηgtst+1+ϵ\theta_{t+1}=\theta_t-\eta\,\dfrac{g_t}{\sqrt{s_{t+1}}+\epsilon}

其中 ρ\rho 常取 0.9 或 0.99。

优点:

  • 避免 AdaGrad 学习率过快衰减;在很多深网任务上表现稳定。

缺点:

  • 仍需要调学习率;不同任务对 ρ\rho 也可能敏感。

AdaDelta(自适应学习率:进一步减少对初始学习率依赖)

AdaDelta 也是“滑动窗口”的思想:它在 RMSProp 的基础上,进一步把更新步长做得更自洽,尽量减少对手工设定初始学习率的依赖。

一种常见的表达是同时跟踪:平方梯度的滑动平均 sts_t 与平方更新量的滑动平均 dtd_t

st+1=ρst+(1ρ)gt2s_{t+1}=\rho s_t+(1-\rho)g_t^2

Δθt=dt+ϵst+1+ϵgt\Delta\theta_t=-\dfrac{\sqrt{d_t+\epsilon}}{\sqrt{s_{t+1}+\epsilon}}\,g_t

dt+1=ρdt+(1ρ)(Δθt)2d_{t+1}=\rho d_t+(1-\rho)(\Delta\theta_t)^2

θt+1=θt+Δθt\theta_{t+1}=\theta_t+\Delta\theta_t

优点:

  • 经验上更不容易出现“有效学习率衰减到 0”的尴尬。

缺点:

  • 现代深度学习中常被 Adam/AdamW 取代;生态与默认配置更少。

Adam(Adaptive Moment Estimation)

Adam 可以理解为:

  • Momentum:对梯度做一阶动量(均值)的滑动平均
  • RMSProp:对平方梯度做二阶动量(方差规模)的滑动平均

更新(逐元素)为:

mt+1=β1mt+(1β1)gtm_{t+1}=\beta_1 m_t+(1-\beta_1)g_t

vt+1=β2vt+(1β2)gt2v_{t+1}=\beta_2 v_t+(1-\beta_2)g_t^2

由于 m0=v0=0m_0=v_0=0 会导致早期偏置(bias),使得初始动量估计偏小,Adam 通常使用偏置修正:

m^t+1=mt+11β1t+1,v^t+1=vt+11β2t+1\hat{m}_{t+1}=\dfrac{m_{t+1}}{1-\beta_1^{t+1}},\qquad \hat{v}_{t+1}=\dfrac{v_{t+1}}{1-\beta_2^{t+1}}

初始时将 m^t+1\hat{m}_{t+1}v^t+1\hat{v}_{t+1} 放大,抵消 mt+1m_{t+1}vt+1v_{t+1} 靠近 0 的影响。随着时间推移,修正项趋近于 1,对原始动量的影响减小。

最终更新:

θt+1=θtηm^t+1v^t+1+ϵ\theta_{t+1}=\theta_t-\eta\,\dfrac{\hat{m}_{t+1}}{\sqrt{\hat{v}_{t+1}}+\epsilon}

经验默认值常见为:β1=0.9,β2=0.999,ϵ=108\beta_1=0.9,\beta_2=0.999,\epsilon=10^{-8}

优点:

  • 收敛快、对学习率不那么敏感;新任务上很适合快速跑通 baseline。

潜在缺点(常见讨论点):

  • 在部分设置下可能出现训练后期震荡或泛化不如精调的 SGD 系列。

还有 note 上的动图:

Nadam(Nesterov + Adam)

Nadam 把 Nesterov 的“lookahead”思想融入 Adam 的一阶动量中,本质上是用更前瞻的方式来计算动量项,从而在一些任务上获得更快或更稳的收敛。

一个常用、也比较容易和代码对齐的 Nadam(带 bias correction)可以写成:

先算 Adam 的动量与偏置修正:

mt+1=β1mt+(1β1)gtm_{t+1}=\beta_1 m_t+(1-\beta_1)g_t

vt+1=β2vt+(1β2)gt2v_{t+1}=\beta_2 v_t+(1-\beta_2)g_t^2

m^t+1=mt+11β1t+1,v^t+1=vt+11β2t+1\hat{m}_{t+1}=\dfrac{m_{t+1}}{1-\beta_1^{t+1}},\qquad \hat{v}_{t+1}=\dfrac{v_{t+1}}{1-\beta_2^{t+1}}

然后把 Nesterov 的“lookahead”体现在一阶动量上(把它看作一个更前瞻的一阶动量项):

mt+1(N)=β1m^t+1+(1β1)gt1β1t+1m_{t+1}^{(N)}=\beta_1\hat{m}_{t+1}+\dfrac{(1-\beta_1)g_t}{1-\beta_1^{t+1}}

最终参数更新:

θt+1=θtηmt+1(N)v^t+1+ϵ\theta_{t+1}=\theta_t-\eta\,\dfrac{m_{t+1}^{(N)}}{\sqrt{\hat{v}_{t+1}}+\epsilon}

实现上常见做法是对 mm 的使用方式做 Nesterov 风格的修正(不同实现细节略有差异),但核心思想是:Adam 负责“自适应步长 + 动量”,Nadam 负责“在动量上再更前瞻一点”。

优点:

  • 有时比 Adam 更快、更稳。

缺点:

  • 并非总是优于 Adam;生态不如 AdamW 常用。

AdamW(Decoupled Weight Decay)

在很多代码里你会看到把 L2 正则写进 loss:

L(θ)=L(θ)+λ2θ22L'(\theta)=L(\theta)+\dfrac{\lambda}{2}\lVert \theta\rVert_2^2

这会让梯度多出一项 λθ\lambda\theta

但对 Adam 这类自适应方法来说,“把 L2 正则并入梯度”与“做权重衰减(weight decay)”并不完全等价。

AdamW 的核心就是把权重衰减从梯度里解耦出来,直接在参数更新时做衰减:

θt+1=θtηm^t+1v^t+1+ϵηλθt\theta_{t+1}=\theta_t-\eta\,\dfrac{\hat{m}_{t+1}}{\sqrt{\hat{v}_{t+1}}+\epsilon}-\eta\,\lambda\theta_t

优点:

  • 在大量任务里比 Adam + L2 更稳,泛化更好;现代训练中非常常用。

学习率调度

很多时候,“用什么优化器”不如“学习率怎么随时间变化”重要。一个常用经验是:

  • 前期需要更大的学习率来探索与快速下降
  • 后期需要更小的学习率来稳定收敛到更好点

下面列几类常见调度方式(它们可与上面任何优化器组合):

可以把学习率理解成优化器每一步的“步长”。步长太大时,参数可能在最优区域附近来回震荡甚至发散;步长太小时,训练会非常慢,并且容易停在不够好的平坦区域。调度器的作用就是让训练前期敢走大步、后期逐渐收小步,从而兼顾探索速度和收敛稳定性。

Step decay(阶梯衰减)

每隔若干 epoch 把学习率乘一个系数 γ\gamma

ηt+1=γηt\eta_{t+1}=\gamma\eta_t

优点是简单、超参数好解释;缺点是学习率跳变可能带来波动。

这种方法常和验证集曲线一起使用:如果 loss 或 accuracy 在一段时间内进入平台期,就把学习率降低一档。它的缺点是衰减时刻需要人为设定,如果降得太早会影响探索,降得太晚则会浪费训练时间。

Exponential decay(指数衰减)

ηt=η0ekt\eta_t=\eta_0 e^{-kt}

更平滑,但需要合适的 kk

指数衰减每一步都在连续变小,不会像 Step decay 那样突然改变训练动态。它适合希望学习率平滑下降的场景,但 kk 过大时后期学习率会很快接近 0,模型可能过早停止有效更新。

1/t1/t decay

ηt=η01+kt\eta_t=\dfrac{\eta_0}{1+kt}

衰减更温和,常用但不一定总是最优。

与指数衰减相比,1/t1/t decay 后期下降更慢,因此还能保留一定的参数微调能力。它在理论分析中很常见,但现代深度学习实践里通常会结合具体任务选用阶梯、余弦或带 warmup 的策略。

Cosine decay(余弦退火)

在现代训练(尤其是视觉任务)中非常常见:

ηt=ηmin+12(ηmaxηmin)(1+cosπtT)\eta_t=\eta_{\min}+\dfrac{1}{2}(\eta_{\max}-\eta_{\min})\left(1+\cos\dfrac{\pi t}{T}\right)

余弦退火的特点是前期下降较慢,中期下降加速,后期又逐渐变平滑。直觉上,它让模型在训练初期保持较强探索能力,最后以较小学习率进行细致收敛;如果配合周期性重启,还可以让训练多次跳出当前区域重新探索。

Warmup

无论用哪种 decay,大 batch 或大模型训练常常会在前几百/几千 step 用 warmup:

  • 从很小的学习率线性/非线性升到目标学习率
  • 减少初期不稳定与梯度爆炸风险

Warmup 解决的是训练刚开始时参数、梯度统计和归一化层状态都还不稳定的问题。此时如果直接使用较大学习率,优化器可能在还没形成可靠方向前就走得太猛;先从小学习率缓慢升高,可以让网络先进入一个更可控的训练状态。