深度学习笔记之优化算法

文章目录

  • 一. 优化算法
    • 1.1 基本算法
      • 1.1.1 随机梯度下降(SGD)
      • 1.1.2 动量
    • 1.2 自适应学习率算法
      • 1.2.1 AdaGrad
      • 1.2.2 RMSProp
      • 1.2.3 Adam
      • 1.2.4其他优化算法:
        • AdaMax
        • Nadam
        • AMSGrad
    • 1.3 牛顿法
        • 拟牛顿法:
  • 二. 一些优化算法的代码实现
    • 2.1 批量随机梯度下降:
    • 2.2带动量的梯度下降
    • 2.3 Adam
  • 参考文献

一. 优化算法

1.1 基本算法

1.1.1 随机梯度下降(SGD)

我们从数据集 M M M中随机抽取 m m m个小批量样本,通过计算它们梯度均值,可以得到梯度的无偏估计。

换句话讲,就是在模型训练的时候,不是将整个数据集都作为输入来训练模型,而是从数据集中抽取一定的样本来作为输入。此时,参数的更新方式为:
θ = θ − α ∇ L ( f ( θ ; x ) , y ) \theta = \theta - \alpha∇L(f(\theta;x),y) θ=θαL(f(θ;x),y)
其中 θ \theta θ为模型的参数, α \alpha α为学习率, ∇ L ∇L L为模型的梯度。

线性衰减学习率
在模型训练过程中,我们希望学习率能够随着模型的训练而发现相应的改变,使得模型能够更便于找到最优解。因此就引入了线性衰减学习率。
ϵ k = ( 1 − α ) ϵ 0 + α ϵ τ \epsilon_k=(1-\alpha)\epsilon_0+\alpha\epsilon_\tau ϵk=(1α)ϵ0+αϵτ
其中, α = k ϵ \alpha={k\over\epsilon} α=ϵk ϵ k \epsilon_k ϵk表示第 k k k次迭代的学习率, τ \tau τ被设置为需要反复遍历训练集几百次的迭代次数。通常 ϵ τ \epsilon_\tau ϵτ大约设置为 ϵ 0 \epsilon_0 ϵ0的1%。

引入随机梯度下降的优点

  • 每一步更新不依赖于数据集的大小。

1.1.2 动量

随机梯度下降存在学习过程过慢这一问题。而为了解决这一问题,我们引入了动量,记作 v v v,动量算法累计了之前梯度指数衰减的移动平均,并继续沿着该方向移动。引入动量后,参数更新的方式变为了:
v = α v − ϵ ∇ L ( f ( x ; θ ) , y ) θ = θ + v v=\alpha v-\epsilon∇L(f(x;\theta),y) \\ \theta=\theta+v v=αvϵL(f(x;θ),y)θ=θ+v
其中 α \alpha α表示之前梯度对衰减的贡献, α ∈ [ 0 , 1 ) \alpha∈[0,1) α[0,1) ϵ \epsilon ϵ表示学习率。

使用动量法相当于每次在进行参数更新的时候,会将之前的速度考虑进来,每个参数在各方向上的移动幅度不仅取决于当前的梯度,还取决于过去各个梯度在各个方向上是否一致,如果一个梯度一直沿着当前方向进行更新(水平方向),那么每次更新的幅度就越来越大,如果一个梯度在一个方向上不断变化(竖直方向),那么其更新幅度就会被衰减,这样我们就可以使用一个较大的学习率,使得收敛更快,同时梯度比较大的方向就会因为动量的关系每次更新的幅度减少。

Nesterov动量
动量算法存在一个问题,那就是动量 v v v会随着迭代的增加导致 v v v累计过大,导致在遇到最优解时直接“冲出去”,而不是收敛于该点。
Nesterov动量是在动量算法基础上的改进,引进Nesterov后,优化算法可以在“山坡”再次上升之前“减速”。其参数更新的方法为:
v = α v − ϵ ∇ L ( f ( x ; θ − α v ) , y ) θ = θ + v v=\alpha v-\epsilon∇L(f(x;\theta-\alpha v),y) \\ \theta=\theta+v v=αvϵL(f(x;θαv),y)θ=θ+v
Nesterov动量和动量的区别在于梯度计算上,Nesterov动量的梯度计算是在施加了动量 v v v之后。

1.2 自适应学习率算法

1.2.1 AdaGrad

AdaGrad算法独立地适应所有模型参数的学习率,缩放每个参数反比于其梯度历史平方值总和的平方根。具有最大偏导的参数相应在参数空间有一个快速下降的学习率,而具有小偏导的参数在学习率上有较小的下降。

AdaGrad参数更新的方式为:
r = r + g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ r=r+g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta r=r+ggθ=δ+r ϵgθ=θ+θ
其中

  • ⊙ ⊙ 表示逐元素乘法;
  • ϵ \epsilon ϵ为学习率;
  • δ \delta δ为一个小常量;通常为 1 0 − 7 10^{-7} 107
  • r r r为梯度积累变量,初始化为0;
  • g g g为梯度。

优点:消除了手动调整学习率的需要。
缺点:会导致累计量 r r r在迭代过程中不断增加,最终导致学习率变得无限小,导致参数不再更新。

1.2.2 RMSProp

RMSProp是在AdaGrad的基础上继续的修改,使其在非凸问题中效果更好,它改变梯度累计为指数加权平均的移动平均,该算法使用指数衰减平均以丢弃遥远过去的历史。

RMSProp参数更新的方式为:
r = ρ r + ( 1 − ρ ) g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta r=ρr+(1ρ)ggθ=δ+r ϵgθ=θ+θ
带Nesterov动量的RMSProp参数更新的方式为:
θ ^ = θ + α v g = ∇ L ( f ( x ; θ ^ ) , y ) r = ρ r + ( 1 − ρ ) g ⊙ g △ θ = − ϵ δ + r ⊙ g θ = θ + △ θ \hat\theta=\theta+\alpha v \\ g=∇L(f(x;\hat\theta),y) \\ r=\rho r+(1-\rho)g⊙g\\ △\theta=-{\epsilon\over{\delta + \sqrt{r}}}⊙g\\ \theta=\theta+△\theta θ^=θ+αvg=L(f(x;θ^),y)r=ρr+(1ρ)ggθ=δ+r ϵgθ=θ+θ

1.2.3 Adam

Adam更新参数:

  • 更新一阶矩估计(相当于动量): s = ρ 1 s + ( 1 − ρ 1 ) g s=\rho_1s+(1-\rho_1)g s=ρ1s+(1ρ1)g
  • 更新二阶矩估计(相当于梯度累积): r = ρ 2 r + ( 1 − ρ 2 ) g ⊙ g r=\rho_2r+(1-\rho_2)g⊙g r=ρ2r+(1ρ2)gg
  • 修正一阶矩估计偏差: s ^ = s 1 − ρ 1 t \hat s={s\over {1-\rho^t_1}} s^=1ρ1ts
  • 修正二阶矩估计偏差: r ^ = r 1 − ρ 1 t \hat r={r\over {1-\rho^t_1}} r^=1ρ1tr
  • 计算更新: △ θ = − ϵ s ^ r ^ + δ △\theta=-\epsilon{\hat s \over\sqrt{\hat r}+\delta} θ=ϵr^ +δs^
  • 更新: θ = θ + △ θ \theta=\theta+△\theta θ=θ+θ

其中:

  • ρ 1 , ρ 2 ∈ [ 0 , 1 ) \rho_1,\rho_2∈[0,1) ρ1,ρ2[0,1),一般 ρ 1 = 0.9 , ρ 2 = 0.999 \rho_1=0.9,\rho_2=0.999 ρ1=0.9,ρ2=0.999
  • δ \delta δ为一个小常量;通常为 1 0 − 8 10^{-8} 108
  • 一阶、二阶矩估计变量 s , r s,r s,r初始值为0.

1.2.4其他优化算法:

AdaMax

在Adam中, s s s的更新是基于梯度 g g g L 2 L_2 L2范数即 ∣ g ∣ 2 |g|^2 g2,我们可以将其推广到 L p L_{p} Lp范数,即
s = ρ 1 s + ( 1 − ρ 1 ) ∣ g ∣ p s=\rho_1s+(1-\rho_1)|g|^p s=ρ1s+(1ρ1)gp
p p p很大时,会导致数值变得不稳定,所以通常采用 L 1 L_1 L1 L 2 L_2 L2范数,而当 p → ∞ p→∞ p时,通常也会表现为稳定。
所以就有了:
s = ρ 1 ∞ s + ( 1 − ρ 1 ∞ ) ∣ g ∣ ∞ = m a x ( ρ 1 s , ∣ g ∣ ) s=\rho_1^∞s+(1-\rho_1^∞)|g|^∞=max(\rho_1s,|g|) s=ρ1s+(1ρ1)g=max(ρ1s,g)

Nadam

Adam与Nesterov的结合。

AMSGrad

  • 更新一阶矩估计(相当于动量): s = ρ 1 s + ( 1 − ρ 1 ) g s=\rho_1s+(1-\rho_1)g s=ρ1s+(1ρ1)g
  • 二阶矩估计(相当于梯度累积): r ^ = ρ 2 r + ( 1 − ρ 2 ) g ⊙ g \hat{r}=\rho_2r+(1-\rho_2)g⊙g r^=ρ2r+(1ρ2)gg
  • 更新二阶矩估计: r = m a x ( r , r ^ ) r=max(r,\hat{r}) r=max(r,r^)
  • 计算更新: △ θ = − ϵ s ^ r ^ + δ △\theta=-\epsilon{\hat s \over\sqrt{\hat r}+\delta} θ=ϵr^ +δs^
  • 更新: θ = θ + △ θ \theta=\theta+△\theta θ=θ+θ

1.3 牛顿法

Hessian矩阵:
如果一个函数 F F F的二阶导数都存在,则 F F F的Hessian矩阵为
[ ∂ 2 F ∂ x 1 2 ∂ 2 F ∂ x 1 ∂ x 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x 1 ∂ x n ∂ 2 F ∂ x 2 ∂ x 1 ∂ 2 F ∂ x 2 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x 2 ∂ x n . . . . . . . . . . . . ∂ 2 F ∂ x n ∂ x 1 ∂ 2 F ∂ x n ∂ x 2 ⋅ ⋅ ⋅ ∂ 2 F ∂ x n 2 ] \begin{bmatrix} {∂^2F\over∂x_1^2}&{∂^2F\over∂x_1∂x_2}&···&{∂^2F\over∂x_1∂x_n} \\ {∂^2F\over∂x_2∂x_1}&{∂^2F\over∂x_2^2}&···&{∂^2F\over∂x_2∂x_n} \\.&.&.&. \\.&.&.&. \\.&.&.&. \\{∂^2F\over∂x_n∂x_1}&{∂^2F\over∂x_n∂x_2}&···&{∂^2F\over∂x_n^2} \end{bmatrix} x122Fx2x12F...xnx12Fx1x22Fx222F...xnx22F...x1xn2Fx2xn2F...xn22F
牛顿法解决优化问题:
对于函数 f ( x ) f(x) f(x),我们要想求得 f ( x ) f(x) f(x)的最小值,实际上就是找到 f ( x ) f(x) f(x)一阶导数为0,二阶导数大于0的点,将这些点记为 x i x_i xi,那么 f m i n ( x ) = m i n ( f ( x i ) ) f_{min}(x)=min(f(x_i)) fmin(x)=min(f(xi))

那么又如何通过类似于梯度下降算法的迭代算法帮助我们找到 f m i n ( x ) f_{min}(x) fmin(x)呢?

现在我们假设 f ( x ) f(x) f(x)具有二阶偏导数,在迭代第 k k k次时的自变量为 x ( k ) x^{(k)} x(k),那么可将 f ( x ) f(x) f(x) x ( k ) x^{(k)} x(k)附近进行二阶泰勒展开:
f ( x ) = f ( x ( k ) ) + g k T ( x − x ( k ) ) + 1 2 ( x − x ( k ) ) T H ( x ( k ) ) ( x − x ( k ) ) {f(x) = f(x^{(k)})+g^T_k(x-x^{(k)})+{1\over2}(x-x^{(k)})^TH(x^{(k)})(x-x^{(k)})} f(x)=f(x(k))+gkT(xx(k))+21(xx(k))TH(x(k))(xx(k))
其中, g k T = ∇ f ( x ( k ) ) g^T_k=∇f(x^{(k)}) gkT=f(x(k)) f ( x ) f(x) f(x) x ( k ) x^{(k)} x(k)点处的梯度。 H ( x ( k ) ) H(x^{(k)}) H(x(k)) f ( x ) f(x) f(x) x ( k ) x^{(k)} x(k)点处的Hessian矩阵。
为了找到一阶导数为0,二阶导数大于0的点。我们需要对二阶展开式再次求导,即:
∇ f ( x ) = g k + H k ( x − x ( k ) ) ∇f(x) = g_k+H_k(x-x^{(k)}) f(x)=gk+Hk(xx(k))
∇ f ( x ) = 0 ∇f(x)=0 f(x)=0,得到 g k + H k ( x − x ( k ) ) = 0 g_k+H_k(x-x^{(k)})=0 gk+Hk(xx(k))=0,那么 x = x ( k ) − H k − 1 g k x=x^{(k)}-H_k^{-1}g_k x=x(k)Hk1gk
于是我们就得到了迭代求解极小值点 x x x的方法,即
x ( k + 1 ) = x ( k ) − H k − 1 g k x^{(k+1)}=x^{(k)}-H_k^{-1}g_k x(k+1)=x(k)Hk1gk
牛顿法存在的缺点:

  1. 计算量太大。Hessian矩阵中元素的个数是参数个数的平方,对与参数数目为 k k k,牛顿法需要计算 k × k k×k k×k矩阵的逆,计算复杂度为 O ( k 3 ) O(k^3) O(k3)
  2. Hessian矩阵需满足正定。若果Heissian矩阵不满足正定,会导致更新朝着错误的方向移动。

拟牛顿法:

思想:不用二阶偏导数而构造出可以近似Hessian矩阵(或Heissian矩阵的逆)的正定对称矩阵。

二. 一些优化算法的代码实现

2.1 批量随机梯度下降:

def update_parameters(parameters,grads,learning_rate):
    """
    使用梯度下降更新参数
    :param parameters:包含参数的字典
    :param grads:包含梯度的字典
    :param learning_rate:学习率,为超参数
    :return:
        parameters:更新后的参数
    """
    L = len(parameters) // 2
    for 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

2.2带动量的梯度下降

def update_parameters_with_momentun(parameters,grads,v,beta,learning_rate):
    """
    使用动量更新参数
    :param parameters:个字典类型的变量,包含了以下字段:
            parameters["W" + str(l)] = Wl
            parameters["b" + str(l)] = bl
    :param grads:一个包含梯度值的字典变量,具有以下字段:
            grads["dW" + str(l)] = dWl
            grads["db" + str(l)] = dbl
    :param v:包含当前速度的字典变量,具有以下字段:
            v["dW" + str(l)] = ...
            v["db" + str(l)] = ...
    :param beta:超参数,动量,实数
    :param learning_rate:学习率,实数
    :return:
        :parameters - 更新后的参数字典
        :v - 包含了更新后的速度变量
    """
    L = len(parameters) // 2
    for l in range(L):
        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)]

        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

2.3 Adam

def update_parameters_with_adam(parameters, grads, v, s, t, learning_rate=0.01, beta1=0.9, beta2=0.999, epsilon=1e-8):
    """
    使用Adam更新参数

    参数:
        parameters - 包含了以下字段的字典:
            parameters['W' + str(l)] = Wl
            parameters['b' + str(l)] = bl
        grads - 包含了梯度值的字典,有以下key值:
            grads['dW' + str(l)] = dWl
            grads['db' + str(l)] = dbl
        v - Adam的变量,第一个梯度的移动平均值,是一个字典类型的变量
        s - Adam的变量,平方梯度的移动平均值,是一个字典类型的变量
        t - 当前迭代的次数
        learning_rate - 学习率
        beta1 - 动量,超参数,用于第一阶段,使得曲线的Y值不从0开始(参见天气数据的那个图)
        beta2 - RMSprop的一个参数,超参数
        epsilon - 防止除零操作(分母为0)

    返回:
        parameters - 更新后的参数
        v - 第一个梯度的移动平均值,是一个字典类型的变量
        s - 平方梯度的移动平均值,是一个字典类型的变量
    """
    L = len(parameters) // 2
    v_corrected = {}  # 偏差修正后的值
    s_corrected = {}  # 偏差修正后的值

    for l in range(L):
        # 梯度的移动平均值,输入:"v , grads , beta1",输出:" v "
        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)]

        # 计算第一阶段的偏差修正后的估计值,输入"v , beta1 , t" , 输出:"v_corrected"
        v_corrected["dw" + str(l + 1)] = v["dw" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)
        v_corrected["db" + str(l + 1)] = v["db" + str(l + 1)] / (1 - np.power(beta1, t) + epsilon)

        # 计算平方梯度的移动平均值,输入:"s, grads , beta2",输出:"s"
        s["dw" + str(l + 1)] = beta2 * s["dw" + str(l + 1)] + (1 - beta2) * np.square(grads["dw" + str(l + 1)])
        s["db" + str(l + 1)] = beta2 * s["db" + str(l + 1)] + (1 - beta2) * np.square(grads["db" + str(l + 1)])

        # 计算第二阶段的偏差修正后的估计值,输入:"s , beta2 , t",输出:"s_corrected"
        s_corrected["dw" + str(l + 1)] = s["dw" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)
        s_corrected["db" + str(l + 1)] = s["db" + str(l + 1)] / (1 - np.power(beta2, t) + epsilon)

        # 更新参数,输入: "parameters, learning_rate, v_corrected, s_corrected, epsilon". 输出: "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)

参考文献

  • deep learning(花书)
  • 梯度下降法、牛顿法和拟牛顿法
  • 梯度下降优化算法概述

你可能感兴趣的