【深度学习】新的深度学习优化器探索(协同优化)
【深度学习】新的深度学习优化器探索(协同优化)

文章目录
1 RAdam VS Adam
2 自适应优化
3 LookAhead
3.1 “侵入式”优化器
3.2 LookAhead 中的参数:
4 RAdam 加 LookAhead 的一个实现:Ranger
代码解读
1 RAdam VS Adam
1,目的
寻求一套性能较为均衡的优化器方案,在保证整体性能的同时兼顾各维度的优化效果。
现有sgd方法在全局最优附近的表现较为理想。
然而,在实际应用中发现其存在以下两方面的问题:
一方面(adam)算法以其快速迭代特性著称,在全局最优附近往往停留于局部极值点;
另一方面(adam)算法由于其自适应特性导致其全局搜索能力相对有限。
针对上述问题,
通常采用自适应重启策略来改善adam算法的全局搜索能力。
2,adam方法的问题
adam在训练的初期,学习率的方差较大。
根本原因是因为缺少数据,导致方差大。
学习率的方差大,本质上自适应率的方差大。
可以控制自适应率的方差来改变效果。
3, Radam, 调节自适应参数的波动程度;一堆数学公式用来推导出自适应率的最大可能值及其动态演变特征
提出了Radam的优化过程
它集Adam与SGD之优点于一身,在优化效率上表现出色的同时也具备较高的稳定性;其收敛结果对初始学习率的变化极为稳健,在较大的学习率下表现甚至超越了SGD

RAdam被定义为"整流版的Adam"(Rectified Adam)。它根据方差分散度能够动态地开关自适应学习率,并且无需调节参数就能实现自我调节的学习机制。
一位Medium网友Less Wright在测试完RAdam算法后,给予了很高的评价:
RAdam可以说是最先进的AI优化器,可以永远取代原来的Adam算法了。
已有研究者已将RAdam开源。此外,FastAI现已成为一个支持RAdam的框架。该功能可通过简洁的几行代码轻松实现。
总结:RAdam可以说是AI最新state of the art优化器
据观察可知,在此问题背景下,RAdam采用了动态调度机制,通过自动化减少计算过程中手动调整学习率的需求,这显著降低了训练阶段 warmup 过程中所需进行的手动参数调节的影响。
此外,RAdam对于适应度度量(关键因素)表现出更高的鲁棒性,并能够实现各数据集与各AI架构下的优化目标,并展现出卓越的学习能力。
将RAdam内置于您的AI架构中会带来显著的好处,请您务必试试看!我们承诺无条件退款保障,并且该方法的价格仅为零美元。
PyTorch的官方发布的GitHub仓库包含RAdam的实现:https://github.com/LiyuanLucasLiu/RAdam.
FastAI用户可以很容易地使用RAdam如下:

导入RAdam,声明为一个局部函数(根据需要添加参数)

2 自适应优化
针对简单SGD和动量算法存在的不足,在2011年时John Duchi及其团队提出了自适应梯度优化算法AdaGrad(Adaptive Gradient)。该算法能根据各个不同参数的具体情况设定相应的学习速率。对于变化频繁的参数,则采用较小的学习步长进行更新;而对于稀疏性较高的参数,则采用较大的学习步长以实现优化效果。

于2014年12月时,Kingma与Lei Ba两位学者提出了Adam优化算法,并将其视为融合AdaGrad与RMSProp两种优化技术的优势所得。该算法通过巧妙地计算参数更新过程中的均值与方差来指导学习率的调整机制,在理论层面实现了对传统Batching Gradient Descent方法的有效补充与完善。在具体实现上,则将梯度的一阶动量估计与二阶动量估计分别命名为First Moment Estimation与Second Moment Estimation,并在此基础上构建了完整的自适应学习率框架体系
M估计量(即基于梯度的未中心化计算得出的方差)通过综合考量或评估各维度特征信息,在此基础上完成适应性的更新步长参数计算。
主要包含以下几个显著的优点:
实现简单,计算高效,对内存需求少
参数的更新不受梯度的伸缩变换影响
超参数具有很好的解释性,且通常无需调整或仅需很少的微调
更新的步长能够被限制在大致的范围内(初始学习率)
能自然地实现步长退火过程(自动调整学习率)
很适合应用于大规模的数据及参数的场景
适用于不稳定目标函数
适用于梯度稀疏或梯度存在很大噪声的问题
普通更新 基于负梯度的方向进行参数调整(因为梯度表示损失函数上升的方向)。假设有一个参数向量\mathbf{x}及其对应的导数d\mathbf{x}, 则按照上述形式进行普通更新:# 普通更新
x += - learning_rate * dx
代码解读
在模型训练中,在处理整个数据集时,在设置适当的学习率前提下,在优化过程中,在使用预设值的学习率情况下,在逐步逼近最优解的过程中
该方法从物理角度上对最优化问题获得了启发,并引入了动量(Momentum)更新机制。损失值可类比为地形的高度,在这种情形下,“高度势能”即是对应的“损失值”,因此有相应的等式成立。将参数空间视为地形模型后,最优化过程可类比于质点在此地形模型上的滚动运动。由于作用于质点的力与梯度潜在能量相关联,在这种情形下,质点所受合力等于损失函数对应位置处负梯度矢量。

v = mu * v - learning_rate * dx # 与速度融合
x += v # 与位置融合
代码解读
梯度下降:我们知道,在曲面上某一点处函数上升最快的方向就是该点的梯度向量所指的方向。当我们进行梯度下降优化时,在每一次迭代中我们需要将参数权重沿着负梯度方向进行更新。这种策略能够帮助我们找到全局最优解。
3 LookAhead
它源自论文《Lookahead Optimizer: k steps forward, 1 step back》,近期首次提出的一种新型优化器。值得注意的是,在深度学习领域享有盛誉的大牛Hinton及其Adam optimizer的合著者Jimmy Ba也参与了该研究的合作团队中。凭借两位重量级专家的支持与参与,《Lookahead》算法自发布以来便备受关注并引发了广泛讨论。
该算法的思路极为直接——虽然它并非传统意义上的优化器但它提供了一种独特的解决方案以整合现有优化技术。具体而言它是通过以下三个步骤依次循环执行完成其功能:
1、备份模型现有的权重θ;
2、从θ出发,用指定优化器更新k步,得到新权重θ̃ ;
3、更新模型权重为θ←θ+α(θ̃ −θ)。
代码解读
3.1 “侵入式”优化器
采用该方案较为简便即遵循Keras的设计规范因此相对容易实施。然而我曾尝试自行设计一个优化器却未能采用这种方式而是通过深入研究源码发现了这样一种非传统的方式这种技术手段类似于直接调用后端库功能的技术手段虽然实现了预期效果但它并不是严格意义上的标准化方法
from keras.optimizers import Optimizer
from keras import backend as K
class InjectOptimizer(Optimizer):
"""定义注入式优化器的基类
需要传入模型,直接修改模型的训练函数,而不按常规流程使用优化器,所以称为“侵入式”
其实下面的大部分代码,都是直接抄自keras的源码:
https://github.com/keras-team/keras/blob/master/keras/engine/training.py#L497
也就是keras中的_make_train_function函数。
"""
def get_updates(self, loss, params):
return []
def get_grouped_updates(self, loss, params):
raise NotImplementedError
def inject(self, model):
"""传入模型做注入
"""
if not hasattr(model, 'train_function'):
raise RuntimeError('You must compile your model before using it.')
model._check_trainable_weights_consistency()
if model.train_function is None:
inputs = (model._feed_inputs +
model._feed_targets +
model._feed_sample_weights)
if model._uses_dynamic_learning_phase():
inputs += [K.learning_phase()]
with K.name_scope('training'):
train_functions = []
with K.name_scope(model.optimizer.__class__.__name__):
grouped_training_updates = self.get_grouped_updates(
params=model._collected_trainable_weights,
loss=model.total_loss)
for i, updates in enumerate(grouped_training_updates[1:]):
f = K.function(
inputs,
[model.total_loss],
updates=updates,
name='train_function_%s' % (i + 1),
**model._function_kwargs)
train_functions.append(f)
# Gets loss and metrics. Updates weights at each call.
first_updates = (model.updates +
grouped_training_updates[0],
model.metrics_updates)
first_train = K.function(
inputs,
[model.total_loss] + model.metrics_tensors,
updates=first_updates,
name='train_function',
**model._function_kwargs)
def F(inputs):
R = first_train(inputs)
for f in train_functions:
f(inputs)
return R
model.train_function = F
class HeunOptimizer(InjectOptimizer):
"""Heun优化器
( https://en.wikipedia.org/wiki/Heun%27s_method )
"""
def __init__(self, lr, **kwargs):
super(HeunOptimizer, self).__init__(**kwargs)
with K.name_scope(self.__class__.__name__):
self.lr = K.variable(lr, name='lr')
def get_updates_1(self, loss, params, cache_grads):
updates = []
grads = self.get_gradients(loss, params)
for p, g, cg in zip(params, grads, cache_grads):
updates.append(K.update(cg, g))
updates.append(K.update(p, p - self.lr * g))
return updates
def get_updates_2(self, loss, params, cache_grads):
updates = []
grads = self.get_gradients(loss, params)
for p, g, cg in zip(params, grads, cache_grads):
updates.append(K.update(p, p - 0.5 * self.lr * (g - cg)))
return updates
def get_grouped_updates(self, loss, params):
cache_grads = [K.zeros(K.int_shape(p)) for p in params]
return [
self.get_updates_1(loss, params, cache_grads),
self.get_updates_2(loss, params, cache_grads)
]
代码解读
用法是:
opt = HeunOptimizer(0.1)
model.compile(loss='mse', optimizer=opt)
opt.inject(model) # 必须执行这步
model.fit(x_train, y_train, epochs=100, batch_size=32)
代码解读
用法就很简单了:
model.compile(optimizer=Adam(1e-3), loss='mse') # 用你想用的优化器
lookahead = Lookahead(k=5, alpha=0.5) # 初始化Lookahead
lookahead.inject(model) # 插入到模型中
代码解读
在评估效果方面,原论文中进行了系列实验,并取得了一定幅度的提升(主要体现在CIFAR10和CIFAR100数据集上),而在某些复杂任务中实现了显著提升(例如基于LSTM的语言模型训练)。个人在简单实验中尝试改进未见显著效果,这让我一直对优化器的作用保持怀疑态度。我认为优化器选择至关重要,在不同场景下可能需要不同的优化器策略:有时非得SGD才能达到最优效果(特别是在计算资源有限的情况下),而Adam在很多情况下难以保证稳定的收敛性。此外,在探索Lookahead方法时也让我多了一种选择的可能性——对于训练时间充足的用户而言值得深入研究一下。
该研究表明这种更新机制能够显著地减少方差。研究者发现Lookahead对次优超参数不那么依赖于精确设置,并因此对大规模调参的需求相对较低。此外,在结合Lookahead及其内部优化器(如SGD或Adam)的情况下,模型能够显著提高收敛速度的同时还降低了计算负担。
研究者通过多组实验考察了 Lookahead 的性能。其中,在 CIFAR 和 ImageNet 数据集上分别应用该方法进行分类器训练,并观察到采用 Lookahead 后 ResNet-50 和 ResNet-152 架构均呈现出更快的收敛速度。
研究者基于 Penn Treebank 数据集进行 LSTM 语言模型的训练,并在 WMT 2014 English-to-German 数据集上构建基于 Transformer 的神经机器翻译模型。通过采用 Lookahead 算法,在所有任务中实现了更快的收敛速度和更卓越的泛化能力,并且该模型对超参数变化表现出更高的鲁棒性。
这些实验显示,在调整内部循环优化器模块、fast weight 的更新频率以及 slow weights 的学习速率方面,Lookahead 展现出了很强的稳定性。
Lookahead Optimizer 怎么做
Lookahead通过迭代机制调整一组慢权重φ和另一组快权重θ;其中慢权重φ每隔k次快权重θ的更新就会同步一次;该方法将预设的标准优化器A用作内部组件以驱动快权重θ的优化。
采用优化器 A 经过 k 次内部优化器更新后,在线性插值的基础上,在权重空间θ−φ中更新slow weights的方向由最后一个fast weights决定。
在每次更新时, fast weights 会被重置为其当前值; 而 Lookahead 的伪代码如图所示

其中最优化器 A 可能包括 Adam 和 SGD 等多种类型,在其内部循环中会采用常规方法对快权重 θ 进行更新操作,并且每次迭代都将从当前的慢权重 φ 值作为初始点展开计算。最终模型所采用的参数设置基于慢权重 φ 的策略进行选择,在此过程中快权重 θ 的每一次更新都相当于完成了一系列实验探索。随后通过慢权重 φ 的持续优化过程能够综合考虑各次实验结果并筛选出具有最优性能的方向指标。这种机制在本质上与 Nesterov 动量法所体现的思想存在一定的相似性
看上去这只是一个小技巧?
3.2 LookAhead 中的参数:
k参数决定了快优化器与LookAhead中的慢优化器在同步更新过程中的时间间隔。通常情况下,默认设置为5或6个时间步,默认情况下这一数值在LookAhead论文中最大设置为20个时间步。
alpha参数调节了基于快慢优化器权重差异的比例来更新快优化器的权重。其默认取值为0.5,在Hinton等人的实验结果表明这一设置接近最优选择;然而研究人员也建议可以根据具体需求进行微调试验。
研究者们进一步指出,在训练过程的不同阶段动态调整k和alpha可能会带来更好的性能提升效果。
4 RAdam 加 LookAhead 的一个实现:Ranger
在阐述了 LookAhead 的工作机制之后,我们能够认识到该快速优化器可以选择现有任何优化器。而在该论文中他们采用了原始的 Adam 优化器作为基础(因为 RAdam 在那个时候还未发布)

那么不言而喻的是说,在这种情况下"只需将原来的LookAhead中的Adam优化器升级为RAdam即可"
这一做法表明,在RAdam的基础上添加LookAhead只需修改其中的Adaptive优化器部分
这一策略的核心在于仅需更换原有的Adam优化器至RAdam便能整合LookAhead功能
在FastAI框架中集成RAdam和LookAhead这两个优化器变得轻而易举,在这一过程中他借鉴了LonePatient开源社区提供的LookAhead实现并采用了论文作者官方发布的RAdam算法版本。研究者LessWright将整合后的新优化器命名为Ranger其中双子字母RA来源于RAdam而整个单词"Ranger"寓意着"突击战士"这种命名方式充分展现了LookAhead算法能够在损失空间中进行高效探索的特点
Ranger 项目公开了源代码于 GitHub 上:https://github.com/lessw2020/Ranger-Deep-Learning-Optimizer?source=post_page-----2dc83f79a48d----------------------
使用方法:
把 ranger.py 拷贝到工作目录下
import ranger

