深度学习常用的优化方法
参考资料:《Deep Learning》、pytorch 文档
深度学习中,优化算法的 目标函数 通常是一个基于训练集的损失函数,优化的目标在于降低训练误差。
这意味着用训练集上的 经验分布 代替 真实分布。
最小化这种平均训练误差的训练过程,被称为经验风险最小化(empirical risk minimization)
虽然是讲随机梯度下降,但是还是也介绍一下 梯度下降的3兄弟:
●批量梯度下降(Batch Gradient Descent):使用整个训练集的样本,通常是能到达局部最小点的。
●随机梯度下降(Stochastic Gradient Descent):每次只使用单个样本,也被称为online(在线)算法;
会向着局部最小逼近,但是最终无法到达局部最小点
●小批量梯度下降(Mini-Batch GraMini-Batchdient Descent):每次使用一个以上而又不是全部的训练样本
深度学习中一般都是采用的小批量(Mini-Batch)梯度下降,通常简单的将它们称为随机(Stochastic),具体实践中,通常将mini-batch的大小设置为 2的整数次方,例如 2、4、8、16...
算法如下:
使用SGD算法,最主要的就是要选择合适的Batch_Size,
a. 内存利用率提高了,大矩阵乘法的并行化效率提高。
b. 在一定范围内,一般来说 Batch_Size 越大,其确定的下降方向越准。
pytorch 上有此优化器模块,torch.optim.SGD
是对随机梯度下降算法的一种优化,目的是加速学习。
SGD有一个缺点,其更新方向完全依赖于当前的batch计算的梯度,更新参数时可能在一些区域出现震荡。解决这一问题的一个简单的做法便是引入momentum--动量:它模拟的是物体运动时的惯性。
动量算法 积累了之前梯度 指数级衰减的移动平均,并且继续沿着该方向移动。
说白一点,就是更新参数的时候在一定程度上保留之前更新的方向,同时利用当批次的计算出的梯度微调,
并得到最终的更新方向。
更新规则如下:
可以看到,先计算当前的梯度,
然后叠加上: 超参数 α 乘以 积累的动量 v 这一项;得到新的方向v;
然后再沿着此方向更新参数值θ;
这样可以在一定程度上增加稳定性,减少训练震荡过程,加速学习。
momentum超参数(也就是上式中的α)一般取值为 0.5、0.9、0.99.
将动量超参数视为 1/(1-momentum) 有助于理解,例如 0.9 对应着最大速度 10倍于梯度下降算法。
和学习率一样,momentum也会随着时间不断调整,一般初始值是一个较小的值,因为一开始的梯度会很大,随着梯度值逐渐减小,momentum的值可以慢慢变大。
pytorch上直接通过在SGD方面里面添加momentum参数:
torch.optim.SGD(xxx, xxx, momentum=0.9)
这又是对momentum 方法的改进,其速度更快:
我们从更新公式中就能看到其与标准动量算法的区别,
那就是在计算当前批次梯度的时候,已经考虑进了之前积累的动量,也就是超前计算梯度。
但是要注意的是,后面更新参数的时候,却不是基于超前点更新的。具体来说如下:
①先假设我们沿着动量方向更新了参数:θ' := θ + α v;
②在 θ' 的基础上计算本次迭代的梯度, 然后叠加之前的动量,这一步如同标准动量方法一样;
③对 θ更新,而不是对θ' 更新。
如图所示:
标准动量算法,先在当前O点计算当前批次的梯度 g0(黑色短线), 然后与O点之前积累的动量V0进行叠加,得到蓝色的线 V1,则O点参数就朝着蓝色方向更新;
但是Nestrov方法,先假设O点沿着V0方向更新了参数,到了另一个点,然后在这个点上计算梯度g0',(黄色的短线),然后此梯度与V0进行叠加,得到更新方向V1',即黄色的长线,那么O点就朝着黄色方向更新。
pytorch上面直接把SGD中的nestrov开关打开:
torch.optim.SGD(xxx, xxx, momentum=0.9, nesterov = True)
定义为:此方法独立的适应所有模型参数的学习率,缩放每个参数反比于其所有梯度历史平方值总和的平方根。
每个字都认识但是连起来很不好懂……
其实就是说:首先要求梯度平方的积累量,
在进行参数更新时,学习速率要除以这个积累量的平方根,(这是个向量)
所以,就是对学习率除以 梯度平方累积和 的开根号,
这样使得梯度大的参数的学习率小,而梯度小的或者更新频率小的参数的学习率大(因此,更AdaGrad适用于稀疏数据)。
算法如图:
优点是:
不用手动调节学习率了,相当于每一次更新学习率都会缩减,因为分母在逐渐积累变大;
目标函数中每个参数都分别拥有自己的学习率,(因为r和g是同维度的向量,相当于是在对每个子分量进行操作);
缺点就是:
由于 积累量不断增加,分母越来越大,可能导致学习率过早和过量减小。
pytorch中有此模块:torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0)
对AdaGrad的改进,通过滑动平均去统计梯度的平方,利用该滑动平均的值替换AdaGrad中梯度平方的累加和,如此一来,便不用担心分母越来越大了
相比于AdaGrad的改进,如下图中红色框所示,一般ρ取0.9
pytorch上有此模块:
torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
pytorch貌似默认把ρ设置为0.99,也就是括号中的的 alpha参数
然后pytorch的版本上,可以将RMSProp加入momentum,你会发现那里有一个momentum参数
AdaDelta算法也是对AdaGrad的改进,与RMSProp十分相近,
与RMSProp算法不同的是,AdaDelta可以看作是用 Δθ的 均方根 代替了学习率:
论文:https://arxiv.org/pdf/1212.5701.pdf
下图是论文中的算法图,
然后我把上表 “翻译” 了一下
下图是pytorch中的实现方法:我把上面算法表中的步骤注释到了旁边,方便理解。
AdaDelta算法没有学习率超参数,上表中 group[‘lr’]这一参数默认值为1.0,所以我就没有乘上去了
它通过使用有关自变量更新量平方的指数加权移动平均的项来替代RMSProp算法中的学习率。
pytorch实现中,倒数两步的顺序调换了一下,不影响结果
https://pytorch.org/docs/0.4.1/_modules/torch/optim/adadelta.html#Adadelta
同时引入了一阶矩估计量和二阶矩估计量,
而且为了防止零初始化导致的开始阶段积累量较小,还进行了偏差修正操作
具体算法如下图所示:
pytorch上有:
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0)
现在Adam优化方法算是比较常用的一种优化算法了,基本上很多算法都直接用Adam优化方法了