pytorch常用优化器总结(包括warmup介绍及代码实现)

梯度下降

梯度下降 GD

通过loss对%5Comega的一阶导数来找下降方向,并且以迭代的方式来更新参数。

W_%7Bt%2B1%7D%20%3D%20W_%7Bt%7D-%5Ceta%5Cnabla%20L%28W_%7Bt%7D%29,其中%5Ceta是学习率。

随机梯度下降 (SGD)

均匀的随机选择其中一个样本(X%5E%7B%28i%29%7D%2CY%5E%7B%28i%29%7D),用它代表整个样本,即把它的值乘以N,就相当于获得了梯度的无偏估计值。

公式:%5Cmathbf%7BW%7D_%7Bt%2B1%7D%3D%5Cmathbf%7BW%7D_%7Bt%7D-%5Ceta%20N%20%5Cnabla%20J%5Cleft%28%5Cmathbf%7BW%7D_%7Bt%7D%2C%20X%5E%7B%28i%29%7D%2C%20Y%5E%7B%28i%29%7D%5Cright%29

小批量梯度下降法(MBGD)

每次迭代使用m个样本来对参数进行更新,公式:

%5Cmathbf%7BW%7D_%7Bt%2B1%7D%3D%5Cmathbf%7BW%7D_%7Bt%7D-%5Ceta%20%5Cfrac%7B1%7D%7Bm%7D%20%5Csum_%7Bk%3Di%7D%5E%7Bi%2Bm-1%7D%20%5Cnabla%20J%5Cleft%28%5Cmathbf%7BW%7D_%7Bt%7D%2C%20X%5E%7B%28k%29%7D%2C%20Y%5E%7B%28k%29%7D%5Cright%29

梯度下降的优点:简单

缺点:训练速度慢,会进入局部极小点。随机选择梯度时,会引入噪声,使得权重更新的方向可能不正确。

动量优化

SGD+Momentum

添加动量,使当前训练数据的梯度受到先前训练数据的影响。

%5Cleft%5C%7B%5Cbegin%7Barray%7D%7Bl%7Dv_%7Bt%7D%3D%5Calpha%20v_%7Bt-1%7D%2B%5Ceta_%7Bt%7D%20%5Cnabla%20J%5Cleft%28W_%7Bt%7D%2C%20X%5E%7B%5Cleft%28i_%7Bs%7D%5Cright%29%7D%2C%20Y%5E%7B%5Cleft%28i_%7Bs%7D%5Cright%29%7D%5Cright%29%20%5C%5C%20W_%7Bt%2B1%7D%3DW_%7Bt%7D-v_%7Bt%7D%5Cend%7Barray%7D%5Cright.

加速收敛并具有摆脱局部最优的能力

但仍具有SGD一部分的缺点

实际尝试

NAG

牛顿加速梯度动量优化方法(NAG, Nesterov accelerated gradient):用上一步的速度先走一小步,再看当前的梯度然后再走一步,

%5Cleft%5C%7B%5Cbegin%7Barray%7D%7Bl%7Dv_%7Bt%7D%3D%5Calpha%20v_%7Bt-1%7D%2B%5Ceta_%7Bt%7D%20%5CDelta%20J%5Cleft%28W_%7Bt%7D-%5Calpha%20v_%7Bt-1%7D%5Cright%29%20%5C%5C%20W_%7Bt%2B1%7D%3DW_%7Bt%7D-v_%7Bt%7D%5Cend%7Barray%7D%5Cright.

NAG

https://zhuanlan.zhihu.com/p/62585696

可以理解为在标准动量上增加了一个修正因子。

理解:在momentum中小球会盲目的跟从下坡的梯度,容易发生错误,所以需要一个更聪明的小球,能提前知道它要去哪,还有知道走到坡地的时候速度慢下来,而不是又崇尚另一坡。

优点:梯度下降的方向更准确

缺点:对收敛速度影响不是很大

pytorch中SGD:

模型每次反向传播都会给可学习参数p计算出一个偏导数g_t,用于更新对应的参数p。通常g_t不会直接作用到对应的可学习参数p上,而是通过优化器做一下处理,得到新的值%5Chat%20g_t,即F%28g_t%29,然后和学习率lr一起用于更新参数

torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)

范围:

  • params(iterable): iterable of parameters to optimize or dicts defining parameter groups
  • lr(float): 学习率
  • momentum (float, optional) : 动量因子 默认 0。通过上一次的v和当前的偏导数,得到本次的v。即:v_t%20%3D%20v_%7Bt-1%7D%20%2A%20momentum%20%2B%20g_t, 这就是F。
    怎么理解:动量使得v具有惯性,这样可以缓和v的抖动,有时可以跳出局部极小。如:上次计算得到v=10,参数更新后本次的偏导是0,那么使用momentum=0.9后,最终用于更新可学习参数的v=9, 而不是0,这样参数仍会得到较大的更新,增加挑出局部极小值的可能性。
  • dampening: 是乘到偏导g上的一个数,即:v_t%20%3D%20v_%7Bt-1%7D%2Amomentum%20%2B%20g_t%20%2A%20%281-dampening%29,dampening在优化器第一次更新时不起作用。
  • weight_decay (float, optional): 权重衰减 (L2 penalty)默认为0,即:L2 正则化,选择合适的权重衰减很重要,需要根据具体的情况取尝试,初步尝试可以选择 1e-4 或者 1e-3。
    作用于当前可学习参数p的值,即:g_t%20%3D%20g_t%20%2B%20%28p%2Aweight_decay%29这里待更新的参数p的偏导就是g_t
  • nesterov (bool, optional): 当nesterov为True时,在上述v_t的基础上,最终得到v_t%20%3D%20g_t%20%2B%20v_t%20%2A%20momentum
    pytorch中SGD更新公式和其他框架略有不同。
    pytorch中是:v=m*v+g p=p-lr*v=p-lr*p*v-lr*g
    其他框架:v=m*v+lr*g p=p-v=p-m*v-lr*g
    动量小知识:
    其物理意义和摩擦系数一致,有效抑制了速度,降低了系统的动能。通过交叉验证,这个参数通常设置为[0.5,0.9,0.95,0.99]中的一个。
    和学习率随着时间退火类似,momentum随时间变化的设置有时能略微改善效果,其中动量在学习过程的后阶段会上升。
    一个典型的设置是,刚开始将动量设为0.5,而在后面的多个周期中慢慢升到0.99.

自适应学习率

AdaGrad

AdaGrad算法通过记录历史梯度,能够随着训练过程自动减小学习率

%5Ctheta_%7Bt%7D%3D%5Ctheta_%7Bt-1%7D-%5Cfrac%7B%5Ceta%7D%7B%5Csqrt%7B%5Csum_%7Bi%3D0%7D%5E%7Bt-1%7D%5Cleft%28g_%7Bi%7D%5Cright%29%5E%7B2%7D%7D%7D%20g_%7Bt-1%7D

以前的方法是对所有参数使用一个学习率,现在对不同的参数有不同的学习率。 (接收到较大梯度值的权重更新学习率会降低,而接收到较小梯度值的权重学习率会更大),适用于数据稀疏或分布不平衡的数据集。

torch.optim.Adagrad(params,lr=0.01,lr_decay=0,weight_decay=0,initial_accumulator_value=0)

范围:

  • params(iterable): 学习参数
  • lr(float, optional) 学习率,默认0.01
  • lr_decay(float, optional): 学习率衰减,默认0
  • weight_decay(float, optional): L2惩罚系数 默认0
  • initial_accumulator_value: 初始加速值,默认为0

优点:自动调整学习率

缺点:依赖一个人工设置的全局学习率,随着迭代次数增多,学习率会越来越小,最后趋于0(因为Adagrad累加之前所有梯度平方作为分母)

实践中不推荐

Adadelta

对Adagrad的改进,Adadelta分母中采用距离当前时间点比较近的累加项,这样可以避免训练后期,学习率过小。

torch.optim.Adadelta(params, lr=1.0,rho=0.9,eps=1e-6,weight_decay=0)

范围:

  • params: 待优化的参数
  • rho(float, optional) 用于计算平方梯度的运行平均值的系数 (默认0.9)
  • eps(float, optional) 防止分母为0
  • lr(float, optional) 学习率,默认为0
  • weight_decay(float, optional) L2 默认为0

优点:避免后期,学习率太小,早中期,加速效果好,训练速度快

缺点:还需要手动指定初始学习率。如果初始梯度很大,则在整个训练过程中学习率将保持较小。在模型训练的后期,模型会在局部最小值附近反复抖动,导致学习时间变长。

RMSProp

Root Mean Square Prop,均方根传递

RMSProp简单修改了AdaGrad方法,它做了一个梯度平方的滑动平均。

%5Ctheta_%7Bt%7D%3D%5Ctheta_%7Bt-1%7D-%5Cfrac%7B%5Ceta%7D%7B%5Csqrt%7Bv_%7Bt%7D%7D%7D%20g_%7Bt-1%7D
v_%7B1%7D%3Dg_%7B0%7D%5E%7B2%7D
v_%7Bt%7D%3D%5Calpha%20v_%7Bt-1%7D%2B%281-%5Calpha%29%5Cleft%28g_%7Bt-1%7D%5Cright%29%5E%7B2%7D

通过累计各个变量的梯度的平法v,然后用每个变量的梯度除以v,即可有效缓解变量间的梯度差异。以下伪代码:

  1. 初始化 x,y (假设只有两个学习参数)
  2. 初始化学习率 lr
  3. 初始化平滑常数(或衰减率)%5Calpha
  4. 初始化 e 防止分母为0
  5. 初始化,梯度的平方v_x%3D0,v_y%3D0(有几个参数就有几个v)
  6. while 没有停止训练 do
  7. 计算梯度g_x%2C%20g_y
  8. 累积梯度的平方:v_x%20%3D%20%5Calpha%20v_x%20%2B%20%281-%5Calpha%29%28g_x%29%5E2(类似于v_y
  9. ​ 更新可学习参数:x%20%3D%20x%20-%20%5Cfrac%7Bg_x%7D%7B%5Csqrt%7Bv_x%7D%2Be%7Dlr(y的更新同理)
  10. end while

思想:梯度震动较大的项,在下降时,减小其下降速度,对于震动幅度较小的项,在下降时,加速其下降速度。采用均方根作为分母,可缓解Adagrad学习率下降较快的问题。

torch.optim.RMSProp(params,lr=0.01,alpha=0.99,eps=1e-8,weight_decay=0,momentum=0,centered=False)

范围:

  • params (iterable): 待优化参数
  • lr(float, optional) 学习率 默认0.01
  • momentum (float, optional) 动量因子,默认0。
    在伪代码第8行,如果momentum=0,则继续后边的计算;
    否则计算过程变为:%5Chat%20v_x%20%3D%20%5Chat%20v_x%20%2A%20momentum%20%2B%20%5Cfrac%7Bg_x%7D%7B%5Csqrt%7Bv_x%7D%20%2B%20e%7D%2C%20x%20%3D%20x%20-%20%5Chat%20v_x%20%2Alr, 其中%5Chat%20v_x初始化为0,g_x是x的梯度,v_x是上述累计的x的梯度的平方。
  • alpha (float, optional) 平滑常数,默认0.99
  • eps (float, optional) 防止分母为0, 默认1e-8
  • centered(bool, optional) 如果是false,按照伪代码计算
    如果为True,即:%5Chat%20g_x%20%3D%20%5Calpha%20%5Chat%20g_x%20%2B%20%281-%5Calpha%29g_x%2C%20v_x%3Dv_x-%28%5Chat%20g_x%29%5E2, 这里%5Chat%20g_x初始化为0
    centered 和 RMSProp并无直接联系,是为了让结果更加平稳
  • weight_decay (float, optional) L2惩罚 默认为0

优点:可缓解Adagrad学习率下降较快的问题,并且引入均方根,可减少摆动,对于RNN效果很好。

缺点:仍然依赖全局学习率

推荐尝试,已被证明是一种有效实用的深度神经网络优化算法。

Adam

Adam看起来像是RMSProp的动量版本,做了两个改进:梯度滑动平均和偏差纠正。

梯度移动平均线

在RMSProp中,梯度的平方是通过平滑常数得到的,但并没有对梯度本身做平滑处理。在Adam中,对梯度也做了平滑处理,平滑后的滑动均值用m表示:即m_t%20%3D%20%5Cbeta%20%2A%20m_%7Bt-1%7D%20%2B%20%281-%5Cbeta%29%2Ag_t

偏差校正

上述m的滑动均值计算,当t=1,m_1%20%3D%20%5Cbeta%20%2A%20m_0%20%2B%20%281-%5Cbeta%29%2Ag_1,由于m_0初始为0,且%5Cbeta接近1,因此t较小时,m的值偏向于0,v也一样。这里通过除以1-%5Cbeta%20%5E%20t来进行偏差纠正,即%5Chat%20m_t%20%3D%20%5Cfrac%7Bm_t%7D%7B1-%5Cbeta%20%5E%20t%7D

假代码:

初始lr,%5Cbeta_1%20%2C%20%5Cbeta_2分别用于平滑m和v,可学习参数%5Ctheta_0,m_0%3D0%2Cv_0%3D0%2Ct%3D0

while 没有停止训练 do

​ 训练次数更新 t=t+1

计算梯度:g_t(所有学习参数都有自己的梯度,所以g_t表示所有梯度的集合)

​ 累计梯度:m_t%3D%5Cbeta_1%20%2A%20m_%7Bt-1%7D%20%2B%20%281-%5Cbeta_1%29%2Ag_t(每个梯度对应一个m,m是一个集合)

​ 累计梯度的平方:v_t%20%3D%20%5Cbeta_2%20%2A%20v_%7Bt-1%7D%20%2B%20%281-%5Cbeta_2%29%2A%28g_t%29%5E2(v是一个集合)

​ 偏差纠正m:%5Chat%20m_t%20%3D%20%5Cfrac%7Bm_t%7D%7B1-%28%5Cbeta_1%29%20%5E%20t%7D

​ 偏差纠正v:%5Chat%20v_t%20%3D%20%5Cfrac%7Bv_t%7D%7B1-%28%5Cbeta_2%29%20%5E%20t%7D

更新参数:%5Ctheta_t%20%3D%20%5Ctheta_%7Bt-1%7D%20-%20%5Cfrac%7B%5Chat%20m_t%7D%7B%5Csqrt%7B%5Chat%20v_t%7D%2Be%7Dlr

将momentum算法和RMSProp算法结合起来使用的一种算法,既用动量来累积梯度,又使得收敛速度更快,同时使得波动的幅度更小,并进行了偏差修正。

torch.optim.Adam(params, lr=0.001, betas=(0.9,0.999),eps=1e-8,weight_decay=0,amsgrad=False)

范围:

  • params
  • lr (float, optional) 学习率 默认1e-3
  • betas (Tuple[float, float], optional) 用于计算梯度以及梯度平法的滑动平均值的系数 (默认[0.9,0.99])
  • eps (float, optional) 防止分母为0 默认1e-8
  • weight_decay (float, optional) L2惩罚
  • amsgrad(bool, optional) 是否使用amsgrad。如果为true,在上述伪代码基础上,保留历史最大的v_t, 记为v_%7Bmax%7D,每次计算都用最大的v_%7Bmax%7D,否则用当前v_t

优势:

  1. 对目标函数没有平稳要求,即loss function可以随时间变化
  2. 参数的更新不受梯度的缩放变换的影响
  3. 更新步长与梯度大小无关,

性能比较

论文中的给出的结论,在训练数据上Adam比较好,在验证数据上SGDM表现比较好,一般选择Adam或者SGDM。或者在训练后期,使用SGDM。

常用SGDM,adam, RMSProp

warmup

学习率调整时模型训练中重要的参数之一,针对学习率的优化方法有很多种,warmup是重要的一种

什么是warmup

warmup是一种学习率优化方法,最早出现在resnet论文中,在模型训练初期选用较小的学习率,训练一段时间之后(10epoch 或者 10000steps)使用预设的学习率进行训练

为什么使用

  1. 模型训练初期,权重随机化,对数据的理解为0,在第一个epoch中,模型会根据输入的数据进行快速的调参,此时如果采用较大的学习率,有很大的可能使模型学偏,后续需要更多的轮次才能拉回来
  2. 当模型经过一段时间的训练后,对数据有一定的先验知识。这时候,使用较大的学习率模型来学习bias并不容易,可以使用较大的学习率来加快训练速度。
  3. 当模型以较大的学习率训练一段时间后,模型的分布相对稳定。此时,从数据中学习新特征是不合适的。如果继续使用较大的学习率,模型的稳定性就会被破坏。学习率越小越好。

完成:

wenet

class WarmupLR(_LRScheduler):
    """The WarmupLR scheduler

    This scheduler is almost same as NoamLR Scheduler except for following
    difference:

    NoamLR:
        lr = optimizer.lr * model_size ** -0.5
             * min(step ** -0.5, step * warmup_step ** -1.5)
    WarmupLR:
        lr = optimizer.lr * warmup_step ** 0.5
             * min(step ** -0.5, step * warmup_step ** -1.5)

    Note that the maximum lr equals to optimizer.lr in this scheduler.

    """
    def __init__(
        self,
        optimizer: torch.optim.Optimizer,
        warmup_steps: Union[int, float] = 25000,
        last_epoch: int = -1,
    ):
        assert check_argument_types()
        self.warmup_steps = warmup_steps

        # __init__() must be invoked before setting field
        # because step() is also invoked in __init__()
        super().__init__(optimizer, last_epoch)

    def __repr__(self):
        return f"{self.__class__.__name__}(warmup_steps={self.warmup_steps})"

    def get_lr(self):
        step_num = self.last_epoch + 1
        return [
            lr
            * self.warmup_steps ** 0.5
            * min(step_num ** -0.5, step_num * self.warmup_steps ** -1.5)
            for lr in self.base_lrs
        ]

    def set_step(self, step: int):
        self.last_epoch = step

亲爱的读者,请点赞和关注😜
欢迎交流深度学习、语音识别、声纹识别等相关知识

版权声明:本文为博主栋次大次原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/weixin_39529413/article/details/123049102

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(1)
社会演员多的头像社会演员多普通用户
上一篇 2022年2月23日 下午6:44
下一篇 2022年2月23日 下午7:14

相关推荐