找回密码
 立即注册
查看: 334|回复: 1

pytorch中常见优化器的SGD,Adagrad,RMSprop,Adam ...

[复制链接]
发表于 2022-4-25 22:18 | 显示全部楼层 |阅读模式
前置

EMA

指数移动平均,EMA(Exponential Moving Average),是一种给予近期数据更高权重的平均方法。
Nicolas:【炼丹技巧】指数移动平均(EMA)的原理及PyTorch实现
核心公式为: ,xt是t时刻测量值,yt是指数平均值,beta是范围在0-1之间的系数。
也可以拓展为:

yt的初始值可以设置为0,这会导致当t比较小时这个平均和理想情况偏差很大,所以加入偏差修正步骤:


因为beta小于1,所以当t很小时,偏差修正作用比较大,使得yt接近xt的平均;当t很大时,偏差修正可以忽略

一些使用了EMA的地方:

  • 更平稳的更新模型权重的一种方式(和梯度无关)

    • 防止模型迭代更新太剧烈,利用不参与训练的影子权重(第二套模型参数),把训练权重进行指数移动平均
    • timm库中有Model EMA的封装后的类 Model EMA (Exponential Moving Average)

  • 强化学习DQN中提出的target Network的权重更新

    • Q网络的更新采用TD算法,其中涉及自举,很容易出现高估的偏差。所以引入另外一个模型Qtarget来降低偏差
    • 其中Qtarget的权重采用了EMA方式的更新机制

  • 优化器中,梯度的动量机制的实现

    • 很多优化器都加入了梯度动量机制,可以使梯度更平稳,不过会消耗额外的内存/显存

  • MoCo动量对比学习中使用EMA来更新momentum encoder的权重参数


    • 其中  是用来产生query的encoder模型的参数,  是用来产生key的momentum encoder模型的参数, m属于(0,1)
    • 采用梯度下降方式来更新,  采用EMA的方式从  来缓慢更新

动量梯度

给模型每个参数记录一个不断累计更新的梯度的动量值,使用EMA方式,将最近的loss计算的梯度更新到动量值里。再使用该动量来作为更新具体每个参数的梯度。若模型的参数量为N,这个动量的记录也要再额外消耗N单位的内存/显存。

    或   
由于动量是对最近一批梯度的指数加权平均,所以用它来更新模型权重,比直接用原始梯度值,抖动更小更加稳定,也可以快速冲过error surface上的鞍点等比较平坦的区域。常见的优化器的实现里都加入了动量机制,不过默认不一定开启。
L2正则(L2 Regularization)


  • L2正则是在损失上加一个w的二范数平方项,效果是让w尽可能不要太大
  • L2正则一般不考虑bias参数的正则,只是计入weights的正则,因为bias参数量小,不易增加模型的复杂性,也不易带来模型输出的方差。对bias加入正则反而可能导致模型欠拟合。



权重衰减(Weight Decay)

在更新权重时直接让权重按比例缩小。常见的优化器中都加入了权重衰减机制。


或者


权重衰减在形式上和L2正则是一致的,但也有如下区别:

  • 两个lambda含义和数值不一样
  • pytorch中的优化器的weight decay的默认实现/用法是不区分weights和bias,统一都decay(如何不将bias加入weightdecay的方式请见结尾部分)
常见优化器分析

SGD

pytorch中的SGD优化器融合了:

  • 动量梯度
  • 权重衰减
  • NAG
NAG(Nesterov Accelerated Gradient)
NAG由俄罗斯数学家Yurii Nesterov在凸优化研究中提出,在使用动量梯度场景下,通过往前看一步的机制可以加速优化的收敛过程。不过这里面涉及套娃过程 ,理论对应到具体实现时比较难懂。



CM与NAG

pytorch官方文档里对SGD的算法描述如下:



pytorch文档中SGD介绍

pytorch1.6中的SGD优化器的实现核心代码如下,采用1.6版本来分析而不是更高版本是因为这个版本的实现中没有再调用到C库的优化器底层实现,全部计算细节都在python代码中了。区别于上述伪代码的是,nesterov开启后没有用 ,而是直接 ,应该是可以减少内存消耗的近似机制。



sgd.py

从官方文档中可看到:

  • SGD开启动量梯度时,额外的内存/显存消耗只有momentum_buffer,是1倍的模型参数量;不开启动量梯度则没有额外内存/显存消耗。

Adagrad

Adagrad(Adaptive Gradient)的核心思想是,深度模型带来的稀疏性,导致模型中一些参数可能频繁获得较大梯度,另一些参数偶尔获得较大梯度,若采用统一学习率导致后者的更新会非常缓慢。基于此,可以调节模型中不同参数的学习率,而不是用统一的学习率。如果一个参数的历史累计梯度更新量大,则降低该参数的学习率;如果一个参数的历史累计梯度更新量小,则增大该参数的学习率。

  • 因为梯度有正有负所以对梯度的平方进行累计。
  • 对每个参数分别调节学习率,也可以看成调节每个参数的梯度。
  • 缺点是随着迭代次数增多,累计值越来越大,导致模型参数的实际更新越来微弱



Adagrad

pytorch官方文档里对Adagrad的算法描述如下:



pytorch文档中Adagrad介绍

从官方文档中可看到:

  • adagrad优化器实现中还加入了学习率衰减机制、权重衰减机制
  • 该算法需要给每一个待更新的模型参数设置一个累计统计量 ,所以额外的内存/显存消耗是,1倍的模型参数量

RMSprop

RMSProp(root mean square propagation)优化算法是祖师爷Geoff Hinton提出的,也属于自适应梯度范畴。区别于adagrad之处是它采用了EMA方式来统计每个参数的最近的累计梯度量,所以多次迭代后不会导致模型参数更新缓慢。



RMSprop

pytorch官方文档里对RMSprop的算法描述如下:



pytorch文档中RMSprop介绍

从官方文档中可看到:

  • RMSprop优化器实现中还加入了权重衰减机制、动量梯度机制
  • 该算法需要给每一个待更新的模型参数设置一个累计梯度统计量  、一个自适应梯度的动量  ,所以额外的内存/显存消耗是,2倍的模型参数量

Adam

Adam(Adaptive Moment Estimation)优化算法,整合了RMSprop中的自适应梯度机制和动量梯度机制。
其中的amsgrad(默认不开启)对原始adam进行了改进,多了一个max步骤:



amsgrad

pytorch官方文档里对Adam的算法描述如下:



pytorch文档中Adam介绍

从官方文档中可看到:

  • Adam优化器实现中还加入了权重衰减机制
  • 该算法需要给每一个待更新的模型参数设置一个累计梯度统计量  、一个自适应梯度的max值 、一个自适应梯度的动量  ,所以额外的内存/显存消耗是,3倍的模型参数
pytorch1.6中Adam的核心代码如下:



adam.py

AdamW

AdamW优化器修正了Adam中权重衰减的bug,Decoupled Weight Decay Regularization.
pytorch官方文档里对AdamW的算法描述如下:



pytorch文档中AdamW介绍

从官方文档中可看到:

  • AdamW与Adam对比,主要是修改了权重衰减计算的方式,一上来直接修改了  ,而不是把权重衰减放到梯度里,由梯度更新间接缩小
  • 显存/内存消耗等其他细节和Adam没有区别

总结

特点额外消耗内存/显存
SGD没有自适应学习率机制,但仔细调参最终模型效果可能最好最多1倍模型参数量
Adam学习率相对不敏感,减少调参试验代价,但weight decay实现不好,能用Adam的地方可以都用AdamW来代替最多3倍模型参数量
AdamW相对于Adam,weight decay实现解耦,效果更好最多3倍模型参数量

模型的不同参数设置不同的优化器参数

pytorch中各个优化器的基类Optimizer中实现了param_groups的机制,每个group有自己的模型参数、优化器参数。例如,可以通过其实现让模型不同layer用不同学习率来训练的效果。



一个模型的3种参数用不同的优化器参数

权重衰减去掉bias和BN

来自yolov5源码,把模型参数分成g0,g1,g2三组,可以精细控制每组模型参数的优化器参数
    g0, g1, g2 = [], [], []  # optimizer parameter groups
    for v in model.modules():
        if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):  # bias
            g2.append(v.bias)
        if isinstance(v, nn.BatchNorm2d):  # weight (no decay)
            g0.append(v.weight)
        elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):  # weight (with decay)
            g1.append(v.weight)

    if opt.optimizer == 'Adam':
        optimizer = Adam(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999))  # adjust beta1 to momentum
    elif opt.optimizer == 'AdamW':
        optimizer = AdamW(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999))  # adjust beta1 to momentum
    else:
        optimizer = SGD(g0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)
    optimizer.add_param_group({'params': g1, 'weight_decay': hyp['weight_decay']})  # add g1 with weight_decay
    optimizer.add_param_group({'params': g2})  # add g2 (biases)
来自timm源码
def param_groups_weight_decay(model: nn.Module, weight_decay=1e-5):
    decay = []
    no_decay = []
    for name, param in model.named_parameters():
        if not param.requires_grad:
            continue
        if param.ndim <= 1 or name.endswith(".bias"):
            no_decay.append(param)
        else:
            decay.append(param)

return [
        {'params': no_decay, 'weight_decay': 0.},
        {'params': decay, 'weight_decay': weight_decay}]

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2022-4-25 22:20 | 显示全部楼层
你好,我想请问一下,如果我想要把pytorch下的Adam优化器的上一个step的更新与当前step的更新加权求和的话要怎么修改代码呢?
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-22 13:41 , Processed in 0.068965 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表