pytorch学习系列(4):常用优化算法
日期:2024-05-26 09:53 | 人气:
损失函数容易陷入这两个点,而下一步的梯度很小或者为0,不能继续训练。
不过,对于神经网络这样非常复杂的高维非凸模型,已经有理论证明局部最小值与全局最小值非常接近,所以第一个问题目前不算太关注,鞍点才是更关注的问题。
w:参数;f(w):损失函数;η:学习率;t:迭代次数(iteration);▽:梯度
Δwt?=?▽f(wt?)
wt+1?=wt?+η?Δwt?
这里的损失函数计算的是所有训练集数据的损失。
注:知乎上latex公式转不了,看起来很不好看,可以直接转csdn上看这篇文章:https://blog.csdn.net/weixin_41786536/article/details/100187562
优点:
一般来说收敛性能比较好
缺点:
1.训练速度慢
2.对内存和算力的要求比较高,只适合小数据集的学习,目前几乎不会使用这种方法。
计算公式与全量梯度下降相同,只不过这里一次只取一个样本而不是所有的样本。
优点:
训练速度快
缺点:
每次只有一个样本,随机性比较大,很可能不收敛,非常震荡,目前几乎不会使用这种方法
注:其实SGD应该是上面那个单样本随机梯度下降的简称,但现在它已经不用了,所以默认小批量随机梯度下降为SGD
每次随机取相同数量的样本,这个样本数量为batch size.
优点:
结合了全量和单样本梯度下降的优点,很大程度上减弱了二者的劣势。
缺点(与下面的几种算法相比):
1.收敛比较慢;
2.容易陷入鞍点,尤其是在这些点会非常震荡,如果可视化出来,会呈现“之”字型来回曲折;
3.对学习率比较敏感,需要合适的学习率。
针对SGD容易陷入鞍点的问题,这里借助物理学的动量的概念(不如说是惯性更好理解)。当损失到达一个梯度很小的地方时,SGD可能就难以继续下去了,但是类比于小球在坑坑洼洼的空间滚动,当其落入局部洼地(局部最小值)或平缓地带(鞍点),借助于惯性还是可以继续走下去的,这个惯性是当前的速度决定的,于是在这里引入速度变量vt?,且v0?=0.
Δwt?=?▽f(wt?)
vt+1?=γ?vt?+η?Δwt?
wt+1?=wt?+vt+1?
其中γ为常量(一般为0.9),代表衰减(物理里的摩擦)系数。
优点:
1.加速收敛,减小震荡;
2.缓解SGD陷入鞍点的问题。
缺点(与后面的几种算法相比):
依然有震荡现象
在SGD with m 的算法中,用当前时刻的梯度来计算下一时刻的速度,这可能不是非常准确,NAG方法先估计下一时刻的梯度,再用这个梯度来求速度。
Δwt?=?▽f(wt?+γ?vt?)
vt+1?=γ?vt?+η?Δwt?
wt+1?=wt?+vt+1?
该方法没有对参数求梯度,这就没有计算损失函数,所以在实际中不好用,一般会对wt?+γ?vt?进行换元,使得能够同时计算对参数的梯度。
优点:
震荡情况比SGD with m更小
总结以上五种优化算法,其特点是对所有的参数使用相同的学习率η。但实际上,神经网络参数非常多,在训练过程中有的参数会更新得很快,下降很快;有的参数更新得很慢,下降很慢。如果对不同的更新快的参数用小的学习率,对更新慢的参数用大的学习率,能够使得所有的参数都更新的比较好。于是出现了下面的自适应优化算法。
pytorch用一个优化器提供了SGD、SGD with m和NAG的算法的实现,由于全量和单样本梯度下降几乎不会用到,所以pytorch并不提供相关算法,而且这两种也可以通过改变batch_size的大小来实现。
optimizer=optim.SGD(params, lr=required, momentum=0, dampening=0, weight_decay=0, nesterov=False)
lr:学习率
momentum:动量大小,即γ,一般设为0.9,如果为0则不带动量项。
weight_decay:l2正则化的系数
nesterov:是否使用NAG优化算法
该方法引入了各个维度的参数的梯度的平方和∑i=1t?(▽f(wi?))2,其中i为参数的维度,ε为很小的正数(一般为1e-7),防止分母为0.
(wt+1?)i?=(wt?)i∑i=1t?(▽f(wi?))2+ε?η(▽f(wi?))i?
学习率实际上是∑i=1t?(▽f(wi?))2η?,当这个维度的参数的历史梯度较大时,学习率较小,当其历史梯度较小时,学习率较大,这就实现了对不同的参数自适应分配学习率。
优点:
1.实现了不同参数的学习率自适应分配;
2.比较适合具有系数梯度的模型,这种模型的特点就是不同参数梯度差距比较大
缺点:
在训练的后期,梯度平方和的累积项可能会非常大,导致学习率很低,使得无法继续更新,早早就不能训练 (尤其是在非凸的神经网络模型上,而在凸函数模型上无此问题)
optimizer=optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)
RMSProp算法用到了指数加权移动平均。
这是一种预测方法,对历史最近的一部分观察值赋予不同权重,并且靠近当前的值赋予大权重,远离当前的值赋予小权重,求得移动平均值,以该移动平均值来预测未来的值。
yt?为要预测的变量,xt?为相关的一个变量.
yt?=γ?yt?1?+(1?γ)?xt?=(1?γ)?xt?+γ?yt?1?=(1?γ)?xt?+(1?γ)?γxt?1?+γ2?yt?2?.......
换元n=1?γ1?,则(1?n1?)n=γ1?γ1?
又n→+∞lim?(1?n1?)n=e?1?<1
所以当γ接近于1时,γ1?γ1?=e?1,于是可以忽略没有γ1?γ1?低的项。
因此yt?=(1?γ)?i=0∑1?γ11?vi?xt?i?
yt?就是最近的1?γ1?时间步的xt?的加权平均。
与Adagrad相比,不再累积历史全部梯度的平方和,而是只取最近的过去的一段时间内的梯度信息
vt+1?=γ?vt?+(1?γ)?(▽f(wt+1?))2
(wt+1?)i?=(wt?)ivt+1?+ε?η(▽f(wi?))i?
vt+1?是(▽f(wt+1?))2的指数加权移动平均,即1?γ1?个时间步长的梯度平方和的加权平均,越靠近当前时间的梯度的权重越大。
优点:
解决了Adagrad在训练后半期难以更新的问题
optimizer=optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
对RMSProp做了改进,引入了一个额外的变量Δxt?,且x0?=0,用Δxt代替学习率
vt+1?=γ?vt?+(1?γ)?(▽f(wt+1?))2
(wt+1?)i?=(wt?)ivt+1?+εΔxt?+ε?(▽f(wt?))i?
Δxt+1?=γ?Δxt?+(1?γ)?(vt+1?+εΔxt?+ε?▽f(wt?)i?)2
Δxt+1?是(vt+1?+εΔxt?+ε?▽f(wt?)i?)2的指数加权移动平均。
优点:
1.不需要设定全局学习率,一下子解决了调参过程中最重要的参数的选择
2.同样解决了Adagrad在训练后半期难以更新的问题
optimizer=optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0)
Adam相当于RMSProp+momentum,将两类方法的优势结合起来,集大成者。momentum使用了一阶梯度,RMSProp使用了二阶梯度,Adam二者均使用。
一阶梯度:mt?=β1mt?1?+(1?β)?▽f(wt?)
二阶梯度:vt?=β2vt?1?+(1?β2?)?(▽f(wt?))2
偏差修正:在训练的前期,梯度权值之和比较小,需要将权值之和修正为1.
mt?^?=1?β1?mt
vt?^?=1?β2?vt
(wt+1?)i?=(wt?)ivt?^+εηmt?^?
其中β1?=0.9,β2?=0.999
optimizer=optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
pytorch还提供了另外四种优化算法,由于用的不是很多,我也没有仔细研究。
以目前的情况来看,Adam成为了一个无脑选择,绝大数工程和研究都用Adam,但实际上有研究证明自适应的算法不一定能找到最优的结果,这个只能算是“懒人做法”。使用最原始的SGD再配合更合适的学习率下降和训练技巧能获得更好的结果,但这对算法和模型的理解要求比较高,毕竟深度学习的研究中优化算法往往不是考虑的最重要因素,更重要的是结构模型、数据等,所以使得Adam成为了万金油的选择。