|
前置
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(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(&#34;.bias&#34;):
no_decay.append(param)
else:
decay.append(param)
return [
{&#39;params&#39;: no_decay, &#39;weight_decay&#39;: 0.},
{&#39;params&#39;: decay, &#39;weight_decay&#39;: weight_decay}] |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|