复习1: 深度学习优化算法 SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam 详细解释 + 如何选择优化算法

送分小仙女□ 提交于 2020-02-26 09:14:05

 

深度学习优化算法经历了 SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam 这样的发展历程。优化器其实就是采用何种方式对损失函数进行迭代优化,也就是有一个卷积参数我们初始化了,之后loss还很大,我们让这个参数根据loss的梯度如何变,每次变多少可以让loss函数在凸曲面上不断变小而找到最优解?

目录

梯度下降相关(GD-miniGD-SGD-SGDM-NAG)

Gradient Descent 梯度下降

mini-batch Gradient Descent 小批量梯度下降

Stochastic Gradient Descent 随机梯度下降 (SGD)

Stochastic Gradient Descent Momentum 带动量的梯度下降法(SGDM)

Nesterov Gradient Descent 带牛顿动量的梯度下降法 (NAG)

Adam相关(AdaGrad -AdaDelta -RMSProp-Adam -Nadam)

Adaptive Gradient Descent (AdaGrad)

AdaDelta

RMSprop 加速梯度下降

Adaptive Moment Estimation (Adam)

Nadam=Nesterov + Adam

二阶近似的优化算法

总结

总结

Adam那么棒,为什么还对SGD念念不忘

Adam罪状一:在某些情况下可能会不收敛

Adam罪状二:可能错过全局最优解

如何选择优化算法

优化器其实就两个部分:

总结:

优化算法的常用tricks

以上参考:


梯度下降相关(GD-miniGD-SGD-SGDM-NAG)

Gradient Descent 梯度下降

选择负梯度方向进行参数更新

 

def update_parameters_with_gd(parameters, grads, learning_rate):
    #Update parameters using one step of gradient descent
    #parameters -- python dictionary containing your parameters to be updated:Wl
    #grads -- python dictionary containing your gradients to update each parameters:dWl
    #learning_rate -- the learning rate, scalar.
    L = len(parameters) // 2 # number of layers in the neural networks# Update rule for each parameterfor l in range(L):  
        parameters['W' + str(l+1)] = parameters['W' + str(l+1)] - learning_rate * grads['dW' + str(l+1)]
        parameters['b' + str(l+1)] = parameters['b' + str(l+1)] - learning_rate * grads['db' + str(l+1)]  
    return parameters

 

 

mini-batch Gradient Descent 小批量梯度下降

直接对大数据执行梯度下降法训练往往处理速度缓慢,将训练集分割成小一点的子集进行训练。这个被分割成的小的子集就叫做 mini-batch,意为小批量。对每一个小批量同时执行梯度下降会大大提高训练效率。

小批量梯度下降算法通常包括两个步骤:充分打乱数据(shuffle)和分组组合数据(partition)。如下图所示。需要注意的细节在于最后一个小批量所含的训练样本数,通常而言最后一个小批量会少于前面批量所含样本数。

 

Stochastic Gradient Descent 随机梯度下降 (SGD)

需要注意的是有必要随迭代步数,逐渐降低学习率。一种常见从做法是线性衰减学习率。直到 次迭代: 其中  在  之后,学习率一般保持常数。

特点:

  • 选择合适的learning rate 较难,对所有参数更新使用同样的learning rate。

  • 容易收敛到局部最优,并且在某些情况下可能被困在鞍点。

  • SGD应用于凸问题时,k次迭代后泛化误差的数量级是O(1/sqrt(k)),强凸下是O(1/k)。

  • 理论上GD比SGD有着更好的收敛率,然而[1]指出,泛化误差的下降速度不会快于O(1/k)。鉴于SGD只需少量样本就能快速更新,这远超过了缓慢的渐进收敛,因此不值得寻找使用收敛快O(1/k)。

  • 可能由于SGD在学习中增加了噪声,有正则化的效果

  • 在某些硬件上使用特定大小的数组时,运行时间会更少。尤其是在使用GPU时,通常使用2 的幂数作为批量大小可以获得更少的运行时间。一般,2 的幂数的取值范围是32 到256,16 有时在尝试大模型时使用。

 

Stochastic Gradient Descent Momentum 带动量的梯度下降法(SGDM)

我们假设梯度下降的横向为参数 W 的下降方向,而偏置 b 的下降方向为纵轴,我们总是希望在纵轴上的震荡幅度小一点,学习速度慢一点,而在横轴上学习速度快一点,无论是小批量梯度下降还是随机梯度下降,好像都不能避免这个问题。为了解决这个问题,带动量的梯度下降法来了。带动量的梯度下降考虑历史梯度的加权平均值作为速率进行优化。

红色为SGD+Momentum。黑色为SGD。可以看到黑色为典型Hessian矩阵病态的情况,相当于大幅度的徘徊着向最低点前进。而由于动量积攒了历史的梯度,如点P前一刻的梯度与当前的梯度方向几乎相反。因此原本在P点原本要大幅徘徊的梯度,主要受到前一时刻的影响,而导致在当前时刻的梯度幅度减小。

直观上讲就是,要是当前时刻的梯度与历史时刻梯度方向相似,这种趋势在当前时刻则会加强;要是不同,则当前时刻的梯度方向减弱。

学习率乘的不再是grad,而是v=g+beta(v-g)。v-g如果大于0,那么相当于当前g与上一个梯度v相同方向,则促进,当前的v比当前的g还大。反之抑制。

 

def update_parameters_with_momentum(parameters, grads, v, beta, learning_rate):
    #Update parameters using Momentum
    #v -- python dictionary containing the current velocity:
    #beta -- the momentum hyperparameter, scalar
    L = len(parameters) // 2 # number of layers in the neural networks# Momentum update for each parameterfor l in range(L):        # compute velocities
        v['dW' + str(l+1)] = beta * v['dW' + str(l+1)] + (1-beta)* grads['dW' + str(l+1)]
        v['db' + str(l+1)] = beta * v['db' + str(l+1)] + (1-beta)* grads['db' + str(l+1)]        # update parameters
        parameters['W' + str(l+1)] = parameters['W' + str(l+1)] - learning_rate*v['dW' + str(l+1)]
        parameters['b' + str(l+1)] = parameters['b' + str(l+1)] - learning_rate*v['db' + str(l+1)] 
    return parameters, v

 

v公式也可以变为:

特点:

  • 下降初期时,使用上一次参数更新,下降方向一致,乘上较大的动量因子能够进行很好的加速;下降中后期时,在局部最小值来回震荡的时候,梯度接近0,动量因子使得更新幅度增大,可能会出现跳出最小值范围的问题。

  • 不过考虑最开始的加速,在梯度改变方向的时候,动量因子能够减少更新 总而言之,momentum项能够在相关方向加速SGD,抑制振荡,从而加快收敛

  • 将参数设为0.5,0.9,或者0.99,分别表示最大速度2倍,10倍,100倍于SGD的算法。

  • 通过速度v,可以积累了之前梯度指数级衰减的平均,并且继续延该方向移动

Nesterov Gradient Descent 带牛顿动量的梯度下降法 (NAG)

nesterov梯度下降的方法继承了动量梯度的思想,但是它认为,即使当前的梯度为0,由于动量的存在,更新梯度依然会存在并继续更新w。而继续当前点w的梯度是不太有意义的,有意义的是,假设下一个点w(仅靠动量滚到下一个点的w)的梯度方向才是决定当前梯度的重要因素。

举个通俗的例子就是,你在下坡时,如果在下坡快到底,但又未到底时,动量梯度下降会让你冲到坡的对面去。Nesterov梯度下降会预知你的下一步将会时到坡的对面去,所以会提示你提前刹车,避免过度冲到坡的对面去。这包含了一种提前计算下一步的梯度,来指导当前梯度的想法。

其中θ相当于权重,v相当于梯度,那么在求当前的g的时候其实用的不是上一步的权重,而是有一个假象的这步结束之后的权重,及:临时更新那步。之后得到当前步的g,剩下的与牛顿动量相同。就是每次计算梯的时候用下一次的权重,这样下一次的变化就会反应在这次计算的梯度上,如果下一次权重计算的梯度很大,则说明更新权重的时候更应该把下一次的梯度给加上去。

特点:

  • 与Momentum唯一区别就是,计算梯度的不同,Nesterov先用当前的速度v更新一遍参数,在用更新的临时参数计算梯度。

  • 相当于添加了矫正因子的Momentum。

  • 在GD下,Nesterov将误差收敛从O(1/k),改进到O(1/k^2)

Adam相关(AdaGrad -AdaDelta -RMSProp-Adam -Nadam)

Adaptive Gradient Descent (AdaGrad)

在随机正常梯度下降(SGD)基础上,改进了学习率,原来求的g之后,之接θ=θ-εg,而现在对ε进行了改进。相当于学习率是当前学习率/全局梯度和

特点:

  • 简单来讲,设置全局学习率之后,每次通过全局学习率 逐参数的 除以历史梯度平方和的平方根,使得每个参数的学习率不同

  • 效果是:在参数空间更为平缓的方向,会取得更大的进步(因为平缓,所以历史梯度平方和较小,对应学习下降的幅度较小。在某些模型对于稀疏的数据它的表现很好。

  • 缺点是,使得学习率过早,过量的减少,及:分母会不断积累,这样学习率就会收缩并最终会变得非常小。

 

AdaDelta

对 Adagrad 的改进,和 Adagrad 相比,就是分母的 G 换成了过去的梯度平方的衰减平均值,指数衰减平均值。

RMS:梯度的均方根 root mean squared RMS[g]是对( + ε)求平均的。E 的计算公式相当于t 时刻的依赖于前一时刻的平均和当前的梯度。之前所有的梯度的加和这一项给一个可以改动的权重,此外,还将学习率 η 换成了 RMS[Δθ],这样的话,我们甚至都不需要提前设定学习率了。

特点;

  • 可以解决在自适应学习 ε中 ε过早降低的问题。

 

RMSprop 加速梯度下降

RMSprop 与 Adadelta 的第一种形式相同:(使用的是指数加权平均,旨在消除梯度下降中的摆动,与Momentum的效果一样,也就是当前的g对结果的影响要考虑之前的g,某一维度的导数比较大,则指数加权平均就大,某一维度的导数比较小,则其指数加权平均就小,这样就保证了各维度导数都在一个量级,进而减少了摆动。允许使用一个更大的学习率η)。但是初始的学习率仍然是赋予初始值的(0.001)

及:RMSProp增加了一个衰减系数来控制历史信息的获取多少

 

Adaptive Moment Estimation (Adam)

是在带动量的梯度下降法的基础上融合了一种称为 RMSprop(加速梯度下降)的算法而成的。动量梯度改的是g,而RMSprop改的是学习率。带动量的梯度下降法,其中的改进思路都在于如何让横轴上的学习更快以及让纵轴上的学习更慢,也就是找到正确的梯度改变方向。RMSprop 和 Adam 在带动量的梯度下降法的基础上,引入了平方梯度,并对速率进行了偏差纠正。

因为:一阶动量是各个时刻梯度方向的指数移动平均值,约等于最近 1/(1-β1^t) 个时刻的梯度向量和的平均值。所以偏差修正就是得到的s和v除以(1-β1^t)和(1-β2^t)

 

 

def update_parameters_with_adam(parameters, grads, v, s, t, learning_rate = 0.01,
                                beta1 = 0.9, beta2 = 0.999,  epsilon = 1e-8):"""
    #Update parameters using Adam
    #v -- Adam variable, moving average of the first gradient, python dictionary
    #s -- Adam variable, moving average of the squared gradient, python dictionary
    #learning_rate -- the learning rate, scalar.
    #beta1 -- Exponential decay hyperparameter for the first moment estimates 
    #beta2 -- Exponential decay hyperparameter for the second moment estimates 
    #epsilon -- hyperparameter preventing division by zero in Adam updates

    L = len(parameters) // 2                 
    v_corrected = {}                        
    s_corrected = {}                         

    # Perform Adam update on all parameters
    for l in range(L):
        v["dW" + str(l+1)] = beta1 * v["dW" + str(l+1)] + (1 - beta1) * grads['dW'+str(l+1)]
        v["db" + str(l+1)] = beta1 * v["db" + str(l+1)] + (1 - beta1) * grads['db'+str(l+1)]        # Compute bias-corrected first moment estimate. Inputs: "v, beta1, t". Output: "v_corrected".   
        v_corrected["dW" + str(l+1)] = v["dW" + str(l+1)] / (1 - beta1**t)
        v_corrected["db" + str(l+1)] = v["db" + str(l+1)] / (1 - beta1**t)        # Moving average of the squared gradients. Inputs: "s, grads, beta2". Output: "s".
        s["dW" + str(l+1)] = beta2 * s["dW" + str(l+1)] + (1 - beta2) * (grads["dW" + str(l+1)])**2
        s["db" + str(l+1)] = beta2 * s["db" + str(l+1)] + (1 - beta2) * (grads["db" + str(l+1)])**2


        # Compute bias-corrected second raw moment estimate. Inputs: "s, beta2, t". Output: "s_corrected".
        s_corrected["dW" + str(l+1)] = s["dW" + str(l+1)] / (1 - beta2**t)
        s_corrected["db" + str(l+1)] = s["db" + str(l+1)] / (1 - beta2**t)        # Update parameters. Inputs: "parameters, learning_rate, v_corrected, s_corrected, epsilon". Output: "parameters".

        parameters["W" + str(l+1)] = parameters["W" + str(l+1)] - learning_rate * v_corrected["dW" + str(l+1)] / (np.sqrt(s_corrected["dW" + str(l+1)]) + epsilon)
        parameters["b" + str(l+1)] = parameters["b" + str(l+1)] - learning_rate * v_corrected["db" + str(l+1)] / (np.sqrt(s_corrected["db" + str(l+1)]) + epsilon)    
    return parameters, v, s

 

特点:

  • Adam算法可以看做是修正后的Momentum+RMSProp算法

  • 动量直接并入梯度一阶矩估计中(指数加权)

  • Adam通常被认为对超参数的选择相当鲁棒

  • 学习率建议为0.001

 

Nadam=Nesterov + Adam

 

二阶近似的优化算法

二阶近似作为早期处理神经网络的方法,在此并不另起blog展开细讲。

  • 牛顿法是基于二阶泰勒级数展开在某点附近来近似损失函数的优化方法。主要需要求得Hessian矩阵的逆。如果参数个数是k,则计算你所需的时间是O(k^3)由于在神经网络中参数个数往往是巨大的,因此牛顿法计算法消耗时间巨大。

具体更新公式如下:

  • 共轭梯度法(CG)是通过迭代下降的共轭方向(conjugate directions)以有效避免Hessian 矩阵求逆计算的方法。

  • BFGS Broyden-Fletcher-Goldfarb-Shanno(BFGS)算法具有牛顿法的一些优点,但没有牛顿法的计算负担。在这方面,BFGS和CG 很像。然而,BFGS使用了一个更直接的方法近似牛顿更新。用矩阵Mt 近似逆,迭代地低秩更新精度以更好地近似Hessian的逆。

  • L-BFGS 存储受限的BFGS(L-BFGS)通过避免存储完整的Hessian 逆的近似矩阵M,使得BFGS算法的存储代价显著降低。L-BFGS算法使用和BFGS算法相同的方法计算M的近似,但起始假设是M^(t-1) 是单位矩阵,而不是一步一步都要存储近似。

总结

  • 总结

SGD没有动量的概念,是最简单的。

SGD最大的缺点是1)下降速度慢,而且2)可能会在沟壑的两边持续震荡,停留在一个局部最优点。

  1. 为了抑制SGD的震荡,SGDM认为梯度下降过程可以加入惯性。下坡的时候,如果发现是陡坡,那就利用惯性跑的快一些,在SGD基础上引入了一阶动量,一阶动量是各个时刻梯度方向的指数移动平均值,约等于最近 1/(1-β1) 个时刻的梯度向量和的平均值。也就是说,t 时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。β1的经验值为0.9,这就意味着下降方向主要是此前累积的下降方向,并略微偏向当前时刻的下降方向。想象高速公路上汽车转弯,在高速向前的同时略微偏向,急转弯可是要出事的。

  2. SGD 还有一个问题是困在局部最优的沟壑里面震荡。想象一下你走到一个盆地,四周都是略高的小山,你觉得没有下坡的方向,那就只能待在这里了。可是如果你爬上高地,就会发现外面的世界还很广阔。NAG是针对这个做改动的。我们知道在时刻 t 的主要下降方向是由累积动量决定的,自己的梯度方向说了也不算,那与其看当前梯度方向,不如先看看如果跟着累积动量走了一步,那个时候再怎么走。因此,NAG在步骤 1,不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,那个时候的下降方向。然后用下一个点的梯度方向,与历史累积动量相结合,计算步骤 2 中当前时刻的累积动量。

 

二阶动量的出现,才意味着“自适应学习率”优化算法时代的到来。SGD及其变种以同样的学习率更新每个参数,但深度神经网络往往包含大量的参数,这些参数并不是总会用得到,及学习率应该是不一样的。对于经常更新的参数,我们已经积累了大量关于它的知识,不希望被单个样本影响太大,希望学习速率慢一些;对于偶尔更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本身上多学一些,即学习速率大一些。

 

AdaGrad:怎么样去度量历史更新频率呢?那就是二阶动量该维度上,迄今为止所有梯度值的平方和。一般为了避免分母为0,会在分母上加一个小的平滑项。因此是恒大于0的,而且参数更新越频繁,二阶动量越大,学习率就越小。

1)这一方法在稀疏数据场景下表现非常好。但也存在一些问题:因为是单调递增的,会使得学习率单调递减至0,可能会使得训练过程提前结束,即便后续还有数据也无法学到必要的知识。AdaDelta / RMSProp:不累积全部历史梯度,而只关注过去一段时间窗口的下降梯度。这就避免了二阶动量持续累积、导致训练过程提前结束的问题了。

Adam:Adaptive + Momentum Nesterov + Adam = Nadam

 

  • Adam那么棒,为什么还对SGD念念不忘

Adam罪状一:在某些情况下可能会不收敛

《On the Convergence of Adam and Beyond》

SGD没有用到二阶动量,因此学习率是恒定的(实际使用过程中会采用学习率衰减策略,因此学习率递减)。AdaGrad的二阶动量不断累积,单调递增,因此学习率是单调递减的。因此,这两类算法会使得学习率不断递减,最终收敛到0,模型也得以收敛。但AdaDelta和Adam则不然。二阶动量是固定时间窗口内的累积,随着时间窗口的变化,遇到的数据可能发生巨变,使得 可能会时大时小,不是单调变化。这就可能在训练后期引起学习率的震荡,导致模型无法收敛。

这篇文章也给出了一个修正的方法。由于Adam中的学习率主要是由二阶动量控制的,为了保证算法的收敛,可以对二阶动量的变化进行控制,避免上下波动。新的学习率和之前的学习率求max,哪个大哪个是当前的学习率。通过取最大值这一步, 可以保证学习速率的递减.

 

Adam罪状二:可能错过全局最优解

《The Marginal Value of Adaptive Gradient Methods in Machine Learning》。文中说到,同样的一个优化问题,不同的优化算法可能会找到不同的答案,但自适应学习率的算法往往找到非常差的答案(very poor solution)。

《Improving Generalization Performance by Switching from Adam to SGD》,进行了实验验证。Adam的收敛速度比SGD要快,但最终收敛的结果并没有SGD好。主要是后期Adam的学习率太低,影响了有效的收敛。对Adam的学习率的下界进行控制,发现效果好了很多。于是他们提出了:前期用Adam,享受Adam快速收敛的优势;后期切换到SGD,慢慢寻找最优解。

主要是根据经验来选择切换的时机和切换后的学习率,里面有两个技术问题:1)什么时候切换优化算法?如果切换太晚,Adam可能已经跑到自己的盆地里去了,SGD再怎么好也跑不出来了。2)切换算法以后用什么样的学习率?Adam用的是自适应学习率,依赖的是二阶动量的累积,SGD接着训练的话,用什么样的学习率?

SGD下降方向必定可以分解为Adam下降方向及其正交方向上的两个方向之和,那么其在Adam下降方向上的投影就意味着SGD在Adam算法决定的下降方向上前进的距离,而在Adam下降方向的正交方向上的投影是 SGD 在自己选择的修正方向上前进的距离。如果SGD要走完Adam未走完的路,那就首先要接过Adam的大旗——沿着 方向走一步,而后在沿着其正交方向走相应的一步。这样我们就知道该如何确定SGD的步长(学习率)了——SGD在Adam下降方向上的正交投影,应该正好等于Adam的下降方向(含步长)。

 

谈到现在,到底Adam好还是SGD好?而从这几篇怒怼Adam的paper来看,多数都构造了一些比较极端的例子来演示了Adam失效的可能性。这些例子一般过于极端,实际情况中可能未必会这样,但这提醒了我们,理解数据对于设计算法的必要性。优化算法的演变历史,都是基于对数据的某种假设而进行的优化,那么某种算法是否有效,就要看你的数据是否符合该算法的胃口了。算法固然美好,数据才是根本。另一方面,Adam之流虽然说已经简化了调参,但是并没有一劳永逸地解决问题,默认的参数虽然好,但也不是放之四海而皆准。因此,在充分理解数据的基础上,依然需要根据数据特性、算法特性进行充分的调参实验。

 

  • 如何选择优化算法

优化器其实就两个部分:

前半部分是实际的学习率(也即下降步长),后半部分是实际的下降方向。SGD算法的下降方向就是该位置的梯度方向的反方向,带一阶动量的SGD的下降方向则是该位置的一阶动量方向。自适应学习率类优化算法为每个参数设定了不同的学习率,在不同维度上设定不同步长,因此其下降方向是缩放过(scaled)的一阶动量方向。

 

总结:

如果数据是稀疏的,就用自适用方法,即 Adagrad, Adadelta, RMSprop, Adam。RMSprop, Adadelta, Adam 在很多情况下的效果是相似的。Adam 就是在 RMSprop 的基础上加了 bias-correction 和 momentum,随着梯度变的稀疏,Adam 比 RMSprop 效果会好。整体来讲,Adam 是最好的选择。很多论文里都会用 SGD,没有 momentum 等。SGD 虽然能达到极小值,但是比其它算法用的时间长,而且可能会被困在鞍点。如果需要更快的收敛,或者是训练更深更复杂的神经网络,需要用一种自适应的算法。

 

优化算法的常用tricks

1.如果是刚入门,优先考虑 SGD+Nesterov Momentum或者Adam.

2. 选择你熟悉的算法——这样你可以更加熟练地利用你的经验进行调参。

3.充分了解你的数据——如果模型是非常稀疏的,那么优先考虑自适应学习率的算法。

4. 根据你的需求来选择——在模型设计实验过程中,要快速验证新模型的效果,可以先用Adam进行快速实验优化;在模型上线或者结果发布前,可以用精调的SGD进行模型的极致优化。

5. 先用小数据集进行实验。有论文研究指出,随机梯度下降算法的收敛速度和数据集的大小的关系不大。因此可以先用一个具有代表性的小数据集进行实验,测试一下最好的优化算法,并通过参数搜索来寻找最优的训练参数。

6. 考虑不同算法的组合。先用Adam进行快速下降,而后再换到SGD进行充分的调优。切换策略可以参考本文介绍的方法。

7. 数据集一定要充分的打散(shuffle)。这样在使用自适应学习率算法的时候,可以避免某些特征集中出现,而导致的有时学习过度、有时学习不足,使得下降方向出现偏差的问题。

8. 训练过程中持续监控训练数据和验证数据上的目标函数值以及精度或者AUC等指标的变化情况。对训练数据的监控是要保证模型进行了充分的训练——下降方向正确,且学习率足够高;对验证数据的监控是为了避免出现过拟合。

9. 制定一个合适的学习率衰减策略。可以使用定期衰减策略,比如每过多少个epoch就衰减一次;或者利用精度或者AUC等性能指标来监控,当测试集上的指标不变或者下跌时,就降低学习率。

10、目标检测最近的trick warm-up方法

 

以上参考:

https://blog.csdn.net/BVL10101111/article/details/72614711

https://www.jianshu.com/p/973878fb9f27

https://blog.csdn.net/jiachen0212/article/details/80086926

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!