多种梯度更新方法——都是对Gradient Descent的优化

传统GD

x = x - lr * grad_x

AdaGrad——不同方向的梯度应该可以不同

pytorch使用adamw优化器_伪代码

  • 为了解决不同方向上梯度涨落速度不一致的情况,所以相当于给每个方向不同的learning_rate。
  • 具体每个方向的lr大小要怎么拟定?——之前该方向上grad大的,就给小lr——即梯度变化幅度缓慢,那么就拉开步子大胆走。如上图的公式,历史grad总量和lr大小成反比,即该方向的总grad越小,则lr就越大。
  • 但该方法解决不了,在该方向上梯度先大再小的情况(如 RMSProp 图一)。

RMSProp——自适应地调节每个方向的梯度大小

pytorch使用adamw优化器_pytorch使用adamw优化器_02

pytorch使用adamw优化器_pytorch使用adamw优化器_03

  • 如图一, RMSProp 可解决这种grad在某一方向一会儿变化缓慢一会儿变化剧烈的情况
    具体公式如图2.注意公式中的 alpha ,可将之看作“decay_rate”,它相乘的 sigma ,保存了全部的历史的grad,即 g^t 。通过调整 alpha 的大小,可权衡新的lr是偏向新grad还是历史grad。
  • 实际上的RMSProp公式和这里的稍有出入,见下图3。RMSProp共有2个超参数,对下图而言,就是 epsbeta ,这里 beta 对应李宏毅教程就是alpha,我们还是称之为 decay_rate

pytorch使用adamw优化器_正则_04

v = decay_rate * v + (1 - decay_rate) * grad_x**2
x = x - lr * grad_x / (np.sqrt(v) + eps)

超参数: decay_rate , eps

Momentum——转向别太猛,加点惯性

pytorch使用adamw优化器_正则_05

  • 思路:梯度的更新,应考虑到此前梯度的变化情况——这次是在方向上的考虑,即加上“惯性”。其中,原梯度越大,对下一步梯度的“转向影响程度”越大。
  • 该方法有一个超参数lambda,每一步真实的梯度 v ,从原求导结果grad 变为了grad + lambda * v_t-1
  • 需要注意的是,momentum和以上两个方法不太一样。以上两个还可以被称为对学习率的调整,但momentum和学习率无关,它的改动项直接加在学习率和原梯度的乘积上。
v = lambda * v + lr * grad
x = x - v

超参数:lambda

Adam——RMSProp和Momentum的集大成者

pytorch使用adamw优化器_正则_06

如图所示,是Adam优化器的伪代码。我们详细来看

  • 首先,看数字1标识处:对参数进行初始化。其中m是Momentum,指的是动量,即使用历史梯度平滑过的梯度; v 是RMSProp式中的sigma (见李宏毅RMSProp部分的slide截图),即记录了全部历史grad,并用此进行梯度的指数加权平均。
  • 绿色五角星:循环条件——被优化的参数没有收敛。这个没啥可说的~
  • 数字2标识处:首先,当然是通过当前batch计算这一步的梯度
  • 数字3标识处:先计算m即动量momentum,并涉及第一个超参数 beta_1。其实这里算法和momentum是几乎一样的,所以beta_1 这里的作用也是对历史梯度和当前梯度的衡量;
  • 数字4标识处:RMSProp的因数计算,和RMSProp里 sigma 计算步骤完全一样,略
  • 5.1、5.2:bias-纠正计算,没仔细了解就不妄下结论了
  • 数字6:将m v 结果整合到一起,更新参数的梯度
m = beta_1 * m + (1 - beta_1) * grad_x
v = beta_2 * v + (1 - beta_2) * grad_x**2
x = x - lr * m / (np.sqrt(v) + eps)

AdamW——Adam + L2正则

简单来说,AdamW就是Adam优化器加上L2正则,来限制参数值不可太大,这一点属于机器学习入门知识了。以往的L2正则是直接加在损失函数上,比如这样子:

pytorch使用adamw优化器_伪代码_07

但AdamW稍有不同,如下图所示:

pytorch使用adamw优化器_后序_08


粉色部分,为传统L2正则施加的位置;而AdamW,则将正则加在了绿色位置。至于为何这么做?直接摘录BERT里面的原话看看——

# Just adding the square of the weights to the loss function is *not* # the correct way of using L2 regularization/weight decay with Adam, # since that will interact with the m and v parameters in strange ways. # # Instead we want to decay the weights in a manner that doesn't interact # with the m/v parameters. This is equivalent to adding the square # of the weights to the loss with plain (non-momentum) SGD. # Add weight decay at the end (fixed version)

总之就是说,如果直接将L2正则加到loss上去,由于Adam优化器的后序操作,该正则项将会与mv产生奇怪的作用。具体怎么交互的就不求甚解了,求导计算一遍应该即可得知。
因而,AdamW选择将L2正则项加在了Adam的mv等参数被计算完之后、在与学习率lr相乘之前,所以这也表明了weight_decay和L2正则虽目的一致、公式一致,但用法还是不同,二者有着明显的差别。以BERT中的AdamW代码为例,具体是怎么做的一望便知:

step_size = group['lr']
       if group['correct_bias']:  # No bias correction for Bert
           bias_correction1 = 1.0 - beta1 ** state['step']
           bias_correction2 = 1.0 - beta2 ** state['step']
           step_size = step_size * math.sqrt(bias_correction2) / bias_correction1
           
       p.data.addcdiv_(-step_size, exp_avg, denom)
       
       if group['weight_decay'] > 0.0:
           p.data.add_(-group['lr'] * group['weight_decay'], p.data)

如code,注意BERT这里的step_size 就是当前的learning_rate。而最后两行就涉及weight_decy的计算。如果我们将AdamW伪代码第12行的公式稍加化简,会发现实际上这一行大概是这样的:
pytorch使用adamw优化器_正则_09
此处pytorch使用adamw优化器_后序_10就是weight_decay。再将上述公式最后一项和code最后一行对比,是不是一模一样呢。