神经网络的学习过程中需要利用优化算法去更新参数,训练模型。优化器的种类有多个,比较常见的是随机梯度下降算法,接下来系统梳理一下各种优化算法的原理、实现方法和优劣之处。
梯度下降算法的优化思想是每次沿着当前位置梯度的反方向为搜索方向。梯度的几何含义是:函数增长最快的方向,那么为了使损失函数达到最小,就得取负方向,即沿着反向梯度方向更新参数,使损失函数沿着下降最快的方向走。
计算损失函数梯度就是相对于各参数求偏导的矢量和,即
w i = w i − learning rate ∗ ∂ L o s s ∂ w i w_i = w_i - \text{learning rate} * \dfrac{\partial Loss }{\partial w_i} wi=wi−learning rate∗∂wi∂Loss
注:
当损失函数是凸函数时,可求得全局最优解,否则不能保证
梯度下降法的速度也未必是最快的
梯度下降算法的学习效果取决于初始值、学习率
其越接近目标值、步长越小,前进越慢
按每次训练过程中投入数据量的大小可将GD优化算法的实现分为以下三类:
在每次计算中,采用整个训练集的数据来计算Loss function 对参数的梯度:
W = W − l r ⋅ ∇ W J ( W ) W= W - lr\cdot \nabla _WJ(W) W=W−lr⋅∇WJ(W)
SGD在每次更新过程中,只对每个样本进行梯度更新。
W = W − l r ⋅ ∇ W J ( W ; x ( i ) ; y ( i ) ) W= W - lr\cdot \nabla _WJ(W;x^{(i)};y^{(i)}) W=W−lr⋅∇WJ(W;x(i);y(i))
在每次训练过程中,从总体m个样本中随机取n个样本计算Loss的梯度。
W = W − l r ⋅ ∇ W J ( W ; x ( i : i + n ) ; y ( i : i + n ) ) W= W - lr\cdot \nabla _WJ(W;x^{(i:i+n)};y^{(i:i+n)}) W=W−lr⋅∇WJ(W;x(i:i+n);y(i:i+n))
1、不能很好保证收敛性,lr取太小的话收敛速度会过慢、lr取太大的话loss就会在极小值处不同震荡直至偏离。(措施:lr预先设置大一点,前后两次迭代的loss变化小于某个阈值后,就减小lr,但阈值的设定需要数据集本身的特点)
2、对于非凸函数,GD容易被困在局部极小值或者鞍点处(鞍点附近的loss都一样,所有维度的梯度接近0);若是用BGD,则优化停止不动;若是MBGD或SGD,那么每次找到的梯度是不同的,会发生震荡,来回跳动
3、SGD对所有参数更新采用的lr是相同的,若数据稀疏,我们更希望对出现频率低的特征进行大一点的更新,即学习率lr是一个可变量,根据特征出现频率以及更新次数发生变化
momentum是模拟物理里动量的概念,积累之前的动量来替代真正的梯度。它通过加入 γ v t − 1 \gamma v_{t-1} γvt−1来加速SGD的同时可以抑制震荡。
v t = γ v t − 1 + l r ∗ ∇ w J ( w ) v_t = \gamma v_{t-1} + lr*\nabla_w J(w) vt=γvt−1+lr∗∇wJ(w) 一般 γ \gamma γ常取0.9左右
w = w − v t w = w-v_t w=w−vt
原理:模拟小球从山顶滚落的过程,若没有阻力,小球的动量会增大,若遇上阻力,速度就会变小,加入速度变化的这一项可以使梯度不变的维度上速度变快,梯度方向改变的维度上更新速度变慢,这样可以加快收敛并减小震荡。
数学思维的理解:参数每次更新的数值不再是直接取负梯度值,二是参考上一步的数值,有点类似做一个“滑动平均”?此外对于加速SGD且抑制震荡这块,可以这么理解:梯度计算结果和上次更新值 v t − 1 v_{t-1} vt−1符号一致时,相当于对最终对参数更新得更多,而符号不一致时,相当于更新得少。
下降初期时,使用上一次参数更新,下降方向一致,乘上较大的 γ \gamma γ能够进行很好的加速
下降中后期时,在局部最小值来回震荡的时候,gradient→0, γ \gamma γ 使得更新幅度增大,跳出陷阱
在梯度改变方向的时候, γ \gamma γ 能够减少更新
同momentum 一样,在更新参数时添加动量信息,不同的是在计算梯度时,不是计算当前位置的梯度,而是计算未来位置的梯度
v t = γ v t − 1 + l r ∗ ∇ w J ( w − γ v t − 1 ) v_t = \gamma v_{t-1} + lr*\nabla_w J(w- \gamma v_{t-1}) vt=γvt−1+lr∗∇wJ(w−γvt−1) 一般 γ \gamma γ常取0.9左右
w = w − v t w = w-v_t w=w−vt
相比于momentum,NAG收敛速度会更快一点,这种算法能够提前看到前方的地形梯度,若前面的梯度比当前位置大,就可以把步子迈得大一点,若前方梯度小,就可以迈小步一点,这个大与小是相对于当前位置梯度信息去计算的。
避免前进太快,同时提高灵敏度
momentum首先计算一个梯度(短的蓝色向量),然后在加速更新梯度的方向进行一个大的跳跃(长的蓝色向量),nesterov项首先在之前加速的梯度方向进行一个大的跳跃(棕色向量),计算梯度然后进行校正(绿色梯向量)
该算法针对GD算法的第三个不足(lr对数据频率的变化)做一个改进,即可以对低频参数做较大的更新,对高频参数做较小的更新。对于稀疏数据来说表现更好,且很好的提高了SGD的鲁棒性。
首先介绍自适应算法的第一个实现方法
对学习率做一个约束:
g t = ∇ W J ( W ) g_t = \nabla _WJ(W) gt=∇WJ(W)
n t = n t − 1 + g t 2 n_t = n_{t-1} + g_t^2 nt=nt−1+gt2
W t = W t − l r n t + ϵ ∗ g t W_t = W_t - \dfrac{lr}{\sqrt{\smash[b]{n_t +\epsilon}}}*g_t Wt=Wt−nt+ϵlr∗gt , ϵ \epsilon ϵ保证分母不为0, n t n_t nt表示从1到t的梯度累计,形成一个约束。
前期gt较小的时候, regularizer较大,能够放大梯度
后期gt较大的时候,regularizer较小,能够约束梯度
适合处理稀疏梯度
缺点:
1、由公式可以看出,仍依赖于人工设置一个全局学习率
2、η设置过大的话,会使regularizer过于敏感,对梯度的调节太大
3、中后期,分母上梯度平方的累加将会越来越大,使gradient→0,使得训练提前结束
对Adagrad的改进(解决Adagrad学习率急剧下降的问题),将 n t n_t nt做一个变换,变成梯度平方的衰减平均数
E [ g 2 ] t = γ E [ g 2 ] t − 1 + ( 1 − γ ) g t 2 E[g^2]_t = \gamma E[g^2]_{t-1} + (1-\gamma)g_t^2 E[g2]t=γE[g2]t−1+(1−γ)gt2
w = w − l r E [ g 2 ] t + ϵ ∗ g t w = w - \dfrac{lr}{\sqrt{\smash[b]{E[g^2]_t+\epsilon}}}*g_t w=w−E[g2]t+ϵlr∗gt
此处的lr还是需要预先设定的,但作者经过一定的改进之后将学习率lr 替换成了每次更新值的求和开方值。
即
训练初中期,加速效果不错,很快
训练后期,反复在局部最小值附近抖动
RMSprop 也是为了解决Adagrad 学习率急剧下降的问题。RMSprop可以算作Adadelta的一个特例:当 γ \gamma γ=0.5时, E [ g 2 ] t = γ E [ g 2 ] t − 1 + ( 1 − γ ) g t 2 E[g^2]_t = \gamma E[g^2]_{t-1} + (1-\gamma)g_t^2 E[g2]t=γE[g2]t−1+(1−γ)gt2就变为了求梯度平方和的平均数。 如果再求根的话,就变成了RMS(均方根):
w = w − l r R M S [ g ] t ∗ g t w = w - \dfrac{lr}{RMS[g]_t}*g_t w=w−RMS[g]tlr∗gt
其实RMSprop依然依赖于全局学习率
RMSprop算是Adagrad的一种发展,和Adadelta的变体,效果趋于二者之间
适合处理非平稳目标
对于RNN效果很好
本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。公式如下:
结合了Adagrad善于处理稀疏梯度和RMSprop善于处理非平稳目标的优点
对内存需求较小
为不同的参数计算不同的自适应学习率
也适用于大多非凸优化
适用于大数据集和高维空间
Adam 就是在RMSprop 的基础上加了bias-correction 和 momentum的效果
对于稀疏数据,尽量使用自适应学习方法,即Adagrad,Adadelta, RMSprop, Adam
SGD通常训练时间更长,容易陷入鞍点,但是在好的初始化和学习率调度方案的情况下,结果更可靠
如果在意更快的收敛,并且需要训练较深较复杂的网络时,推荐使用学习率自适应的优化方法
Adadelta,RMSprop,Adam是比较相近的算法,在相似的情况下表现差不多
在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果
牛顿法的每一步需要求解目标函数的海塞矩阵的逆矩阵;拟牛顿法通过正定矩阵近似还塞矩阵的逆矩阵或海塞矩阵,简化计算过程。
牛顿法:主要是利用迭代点 X k X_k Xk处的一阶导数和二阶导数去拟合一个二次函数,并求当前函数的极小值,然后将当前函数的极小值作为新的迭代点并不断重复该过程,直至得到满足精度的极小值点。
牛顿法的具体实现:基本牛顿法和全局牛顿法
(1)原理:对于一维函数问题,将函数的极值问题转化为求f’(x)=0的问题。将函数做二阶泰勒展开: 对等式两边关于x求导,得到 得到 x = x k − f ’ ( x k ) f ’ ’ ( x k ) x=x_k-\dfrac{f’(x_k)}{f’’(x_k)} x=xk−f’’(xk)f’(xk) 这是牛顿法的基本更新公式
(2)基本流程:
优缺点:
1、牛顿法收敛速度很快且具有局部二阶收敛性
2、基本牛顿法依赖于初始点的选取,若初始点未足够靠近极小点,会导致算法不收敛.
全局牛顿法是对基本牛顿法的一个改进
(1)基本流程
(2)Armijo搜索
就是在更新 x k x_k xk时,给 d k d_k dk加一个系数,改变更新值
共轭梯度法介于最速下降法和牛顿法之间,用于求解无约束最优化问题,仅需要利用一阶导数的相关信息,但可以克服最速下降法收敛慢的缺点,是解决大型线性方程组/大型非线性最优化的有效方法之一。
基本思想:将共轭性和最速下降法相结合,利用已知点的梯度构造一组共轭方向,并沿该方向搜索,求出目标函数的极小点。
目标函数: x ∗ = a r g min 1 2 x T A x − b x x^* = arg \min\dfrac {1}{2}x^TAx -bx x∗=argmin21xTAx−bx
梯度: ∇ f ( x ∗ ) = A x ∗ − b = 0 \nabla f(x^*) = Ax^* - b =0 ∇f(x∗)=Ax∗−b=0
共轭方向的定义:
算法流程:
其中r为残差,d为梯度方向,x为出发点
优点:存储量小,具有步收敛性,稳定性高,且不需要任何外来参数。
梯度下降法和牛顿法:
两者都是梯度求解,而牛顿法/拟牛顿法是用二阶海塞矩阵的逆矩阵或伪逆矩阵求解。相对而言,使用牛顿法收敛更快,但是每次迭代时间比梯度下降法长。
梯度下降算法和共轭梯度法:
共轭梯度法克服了梯度下降算法中的收敛慢的缺点。
torch.optim是一个实现了各种优化算法的库。大部分常用的方法得到支持,并且接口具备足够的通用性,使得未来能够集成更加复杂的方法。
优化函数 | 特殊参数说明 |
---|---|
torch.optim.Adadelta(params, lr=1.0, rho=0.9, eps=1e-06, weight_decay=0) | lr_decay (float, 可选) – 学习率衰减;weight_decay (float, 可选) – 权重衰减(L2惩罚) |
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0) | betas (Tuple[float, float], 可选) – 用于计算梯度以及梯度平方的运行平均值的系数;eps (float, 可选) – 为了增加数值计算的稳定性而加到分母里的项; |
torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False) | momentum (float, 可选) – 动量因子;alpha (float, 可选) – 平滑常数;centered (bool, 可选) – 如果为True,计算中心化的RMSProp,并且用它的方差预测值对梯度进行归一化; |
torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False) | dampening (float, 可选) – 动量的抑制因子;nesterov (bool, 可选) – 使用Nesterov动量 |
torch.optim.Adamax(params, lr=0.002, betas=(0.9, 0.999), eps=1e-08, weight_decay=0) | |
torch.optim.ASGD(params, lr=0.01, lambd=0.0001, alpha=0.75, t0=1000000.0,weight_decay=0) | lambd (float, 可选) – 衰减项; alpha (float, 可选) – eta更新的指数 ; t0 (float, 可选) – 指明在哪一次开始平均化 |
以SGD为例,实现完整的一次优化步骤:
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
optimizer.zero_grad() #累计梯度清零
loss_fn(model(input), target).backward() # backward计算损失函数梯度
optimizer.step() # 做一次更新
1)https://blog.csdn.net/u012759136/article/details/52302426/
2)https://www.cnblogs.com/shixiangwan/p/7532830.html
3)https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-optim/