公司新闻

从 SGD 到 AdamW —— 优化算法的演化

优化算法大致可分为两大阵营:第一大阵营基于梯度下降,在深度学习领域很吃得开;第二大阵营则是基于牛顿法。

牛顿法的基本思想是利用迭代点处的一阶导数(梯度)和二阶导数(海森矩阵)对目标函数进行二次函数近似,然后把二次函数的极小点作为新的迭代点,并重复这一过程。而梯度下降只需要用到一阶导数

这篇文章我想从深度学习中的梯度下降算法入手,梳理一下它的演化历程。

注:梯度下降有三种模式:single, batched, full,表征每轮计算梯度时用到的样本数目。最常用的是 batched 模式,它兼顾了训练稳定性与训练速度。下面讨论的并不是每轮更新样本数目的影响,重点是拿到梯度后如何更新参数。所以默认每个时间步得到的梯度是一个小批量的平均梯度就好了。

梯度下降的目标是找到合适的参数 w ,使得它能最小化目标函数 f(w) : \	ext{argmin}_{w}f(w)

计算可以分为以下几个步骤:

  1. 在时间步 t ,计算梯度 g_t=\
abla f(w_t)
  2. 根据历史梯度,计算一阶动量 m_t=\\phi(g_1, g_2, ..., g_t) 以及二阶动量 v_t=\\psi(g_1, g_2,...,g_t)
  3. 计算当前应该“走”的方向 \\eta_t=\\alpha_t \\cdot \\frac{m_t}{\\sqrt{v_t}+\\epsilon} ,其中 \\epsilon 是平滑项,一般取 1e-8
  4. 更新参数: w_{t+1}=w_t - \\eta_t

重复以上 1-4 步,直到收敛或者训练结束。

平时说的优化器状态,就是指这里的一阶和二阶动量。进行断点续训的时候,保存优化器状态是很重要的。

vanilla SGD 不考虑二阶动量,且一阶动量等于当前时间步的梯度

w_{t+1}=w_t - \\alpha_t \\cdot g_t

善于利用前人的经验

对原始的随机梯度下降做了些改进:一阶动量等于梯度的指数移动平均。即, t 时刻的下降方向,由此前累积的下降方向与当前时刻的梯度方向共同决定

m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1) \\cdot g_t

w_{t+1}=w_t - \\alpha_t \\cdot m_t

这可以让下降曲线更加平滑。一般 \\beta_1=0.9 ,这样一阶动量约为最近 \\frac{1}{1-\\beta_1}=10 个时刻梯度的平均值

为了防止在局部最优的沟壑里震荡,往往需要登高望远

它是对 SGD-M 的进一步改进。SGD-M 中,主要下降方向由累积梯度决定,当前时刻的梯度占的分量不大。既然如此,不如看看如果跟着累积梯度走一步,那个时候再怎么走

g_t=\
abla f(w_t - \\alpha_{t-1}\\cdot m_{t-1})

累积梯度: m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1) \\cdot g_t

更新参数: w_{t+1}=w_t - \\alpha_t \\cdot m_t


对待参数也要因材施教

"Adaptive" 自适应,指的是学习率的自适应。对于不同的参数,采用不同的学习率。

这里用二阶动量去衡量历史的“更新频率”。更新频率越高,学习率越小。具体来说,假设有 d 个参数,二阶动量矩阵就是一个 d \	imes d 的对角矩阵。对角线上的元素代表对应参数的累积梯度平方和。

v_t=\	ext{diag}(\\sum_{i=1}^t g_{i,1}^2, \\sum_{i=1}^t g_{i,2}^2, ..., \\sum_{i=1}^t g_{i,d}^2)

更新参数: w_{t+1}=w_t - \\alpha_t \\cdot \\frac{g_t }{\\sqrt{v_t}+ \\epsilon}

可以看到,对于第 j 个参数,学习率变成了 \\frac{\\alpha_t}{\\sqrt{v_{t,i}}+ \\epsilon}=\\frac{\\alpha_t}{\\sqrt{ \\sum_{i=1}^t g_{i,j}^2}+ \\epsilon}


莫要太贪心,贪多嚼不烂

AdaGrad 的缺点很明显:随着梯度平方和不断累积,学习率可能会趋近于 0,导致训练提前结束。于是 AdaDelta 考虑不累计全部历史梯度的平方和,只关注过去一段时间的窗口 —— 即指数移动平均。

v_t=\\beta_2 \\cdot v_{t-1}+ (1-\\beta_2) \\cdot \	ext{diag}(g_t^2)

一般 \\beta_2=0.9 ,关注过去 10 个时间步的梯度平方和。

更新参数: w_{t+1}=w_t - \\alpha_t \\cdot \\frac{g_t }{\\sqrt{v_t}+ \\epsilon}


新三年,旧三年,缝缝补补又三年

回顾一下:

SGD-M 在 SGD 的基础上考虑了指数移动平均的一阶动量;

AdaDelta 在 SGD 的基础上考虑了指数移动平均的二阶动量。

现在 Adam (Adaptive + Momentum) 说:小孩子才做选择,我全都要!

m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1) \\cdot g_t

v_t=\\beta_2 \\cdot v_{t-1}+ (1-\\beta_2) \\cdot \	ext{diag}(g_t^2)

在迭代初始阶段, m_t, v_t 会向初值偏移,需要进行校正:

\\hat m_t=\\frac{m_t}{1-\\beta_1^t}

\\hat v_t=\\frac{v_t}{1-\\beta_2^t}

更新参数: w_{t+1}=w_t - \\alpha_t \\cdot \\frac{\\hat m_t }{\\sqrt{\\hat v_t }+ \\epsilon}

关于为什么要进行校正,以及校正是否真的有必要,可以参考这个讨论:Why is it important to include a bias correction term for the Adam optimizer for Deep Learning?

常用的超参数: \\beta_1=0.9, \\ \\beta_2=0.999 。也就是说,一阶动量关注的时间窗口更小,大约有 10 个时间步;二阶动量的时间窗口则有 1000


既然要缝缝补补,怎么能少得了 Nesterov 呢?

NAdam=Nesterov + Adam

g_t=\
abla f(w_t - \\alpha_t \\cdot \\frac{\\hat m_{t-1}}{\\sqrt{\\hat v_{t-1}}+ \\epsilon})

其余和 Adam 一致。


对于 vanilla SGD 而言,权重衰减 与 L2 正则是等价的。

前者是 w_{t+1}=(1-\\lambda) w_t - \\alpha_t \\cdot \
abla f(w_t) ;后者是 w_{t+1}=w_t - \\alpha _t\\cdot \
abla (f(w_t) + \\frac{\\lambda'}{2}||w_t||^2_2)

\\lambda'=\\frac{\\lambda}{\\alpha_t} 时,二者是等价的。但是对于 Adam 这种引入了一阶、二阶动量的优化器,就没有等价关系了。

之前 Adam 添加 L2 正则项时,直接在梯度上添加粉色项。但由于 m_t, v_t 的存在,粉色项会与原损失函数梯度 \
abla f 发生耦合,产生奇怪的作用。

Adam with decoupled weight decay (AdamW) 提出了解耦合的权重衰减方式:添加绿色项而不是粉色项。

此时更新公式就是标准的权重衰减: w_{t+1}=(1-\\alpha_t \\lambda) w_t - \\alpha_t \\cdot \\frac{\\hat m_t }{\\sqrt{\\hat v_t}+ \\epsilon}

一般设 weight decay 参数 \\lambda=0.01


现在 Adam 和 AdamW 是 NLP 领域乃至整个 DL 领域常用的优化器。但为了训练时节省显存,有时也会用 SGD-M

另外,参数更新公式中学习率取决于时间步。常见做法是先 warmup —— 线性增长到最大值(一般在 5e-5 到 1e-4),然后线性或余弦衰减到最大值的 10%

关于其他的学习率规划器,可以参考:Pytorch 学习率规划器_云中君不见的博客

平台注册入口