语言选择: 简体中文简体中文 line EnglishEnglish

公司动态

优化算法SGD与Adam

说来惭愧,一直在用的两个经典的优化器,几行代码就完成了参数更新,但是却一直没有深入的了解其中的原理。

improt torch
...
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
...
optimizer.zero_grad()
...
optimizer.step()

首先贴一下pytorch的官方文档

torch.optim — PyTorch 1.8.0 documentation

不同的优化器都采用相同的参数更新步骤,即

1.计算损失函数对于当前参数的梯度: g_t=\
abla loss(w_t)

2.根据历史梯度计算一阶和二阶动量

m_t=\\phi(g_1, g_2, \\cdots, g_t); V_t=\\psi(g_1, g_2, \\cdots, g_t)

3.根据当前时刻的梯度,一阶和二阶动量,学习率计算当前时刻的下降梯度:

\\eta_t=\\alpha \\cdot m_t / \\sqrt{V_t}

4.根据当前时刻的下降梯度更新参数:w_{t+1}=w_t - \\eta_t

其中: w_t 为待优化参数,  loss(x) 为损失函数, \\alpha 为学习率。

那么我们如何计算一阶和二阶动量呢?

对于最经典的SGD算法, m_t=g_t , V_t=1 。也就是没有用到动量的概念,朴实无华。

对于带动量的SGD,m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1)\\cdot g_tV_t=1 。也就是一阶动量不仅要考虑当前时刻的梯度,还考虑到了上一时刻的动量。就像是一辆车,上一时刻在全速向前,这一时刻要刹车,但是不可能立刻停下来。

Adam算法引入了二阶动量的概念,二阶动量是什么呢,是历史动量的平方和,可以理解为这个参数他更新的频率大小,二阶动量大就说明这个参数经常更新,我们就希望这个经常更新的参数可以慢一点更新,很少更新的参数每次更新的步子就大一点,但是如果选取历史动量的平方和,随着时间的累计这个值会特别大,导致下降梯度近似于0,无法继续训练,于是我们定义如下,在加上之前SGD的一阶动量,就构成了Adam算法的核心。

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

 V_t=\\beta_2 * V_{t-1}+ (1-\\beta_2) g_t^2

AdamW修正了Adam算法和weight decay一起使用时存在的BUG,具体可以参考下边的博客。

fast.ai - AdamW and Super-convergence is now the fastest way to train neural nets

一个框架看懂优化算法之异同 SGD/AdaGrad/Adam - 知乎 (zhihu.com)

从动量和矩的角度探讨优化理论的内涵(以动量法、AdaGrad和Adam举例) - 知乎 (zhihu.com)

平台注册入口