zoukankan      html  css  js  c++  java
  • 神经网络之优化算法

    摘要

    本文概述了常见的梯度下降优化算法的不同变种,分析了初始化在优化过程中的重要性以及如何初始化,最后列举出不同优化算法的具体公式,计算过程。

    优化概述

    下面概述一下常见的优化算法,优化算法的核心是梯度下降,不同优化算法改进的地方在于梯度的方向和大小。可以将优化算法粗分为两大类,一类是改变方向的 Momentum,一类是改变学习率即梯度大小的 adagrad,最常用的 Adam 结合了这两类的优点。

    • SGD:梯度下降。这里有几个概念需要辨析:GD 使用全部的样本累积梯度,用累积的梯度更新权值;SGD 每次用一个样本,计算梯度,更新权值;mini-batch SGD,使用一个 batch 的样本累积梯度,更新权值,一般 SGD 的实现用的就是一个 batch 来更新权值,而不是一个样本。
    • Momentum:保存一个称之为动量的变量,不断累加梯度到动量上,最后动量更新权值。我们可以将梯度视为力,如果每一步都朝着同个方向前进,那么累加的力就不会抵消,还会因为累加的效果可以在合力方向前进得更快,如果梯度某个方向上发生了震荡,即力的方向经常发生改变,那么力会不断抵消,因为在累加,所以这一步可能和上一步的力发生了抵消,在震荡的方向上将改变的少。可以阅读 [5] 相关章节,用了一个小球的例子,挺形象的。
    • Nesterov momentum:和 Momentum 差不多,区别在于,累加的梯度计算。Momentum 使用的梯度是当前的梯度,而 Nesterov 使用的是“假想下一步位置的梯度” ,先用动量更新权值,然后计算新的位置,在新的位置上计算梯度,最后使用这个梯度去更新原来的动量,再用更新后的动量更新权值。[1]
    • adagrad:动态调整学习率,累加梯度的平方和,再用这个值计算学习率,累加的梯度越大,学习率就越小,因此 adagrad 的问题是,学习到后面,学习率越来越小,直接学不动了。
    • RMSProp:改进了 adagrad 的问题,RMSProp 不再是简简单单直接累加梯度,而是加权求和,权值之和为 1,设置旧的累积梯度为 (eta),新的梯度为 (1 - eta)。 [2]
    • adadelta:同样是为了改进 adagrad 的问题,[3] 给出了详细的公式,adadelta 在 RMSProp 的基础上,改进了学习率的计算,这个方法甚至不需要手动设置学习率,在 pytorch 的实现中,学习率是可选的,默认是 1。
    • Adam:最常用的优化器了,结合了 Momentum 和 adagrad 的思想,计算动量和累加梯度的平方和,接着就是一顿算,根据动量算方向和大小,根据累加梯度的平方和算学习率。
    • Yogi:改进 Adam 的问题,在累加梯度的平方时,这个梯度可能很大(blows up),使到 Adam 不能收敛(even in convex setting),结合公式看一下就知道为什么了,因为这个梯度大,学习率也大,后面看公式就知道了。Yogi 改进的点在于累加梯度的平方和那个公式。链接 [4] 的公式很详细。动量和累加梯度有一个初始值,Yogi 还建议了使用一个 batch 来设置初始值。

    关于学习率的讨论

    学习率太大,不能收敛;学习率太小,学得慢或者陷入局部最小。学习率衰减,如果学习率一直都保持那么大,会在最小值周围震荡,而到达不了最小值。

    初始化

    权值初始化。[5] 这本书总结了一个最佳实践:

    当激活函数为 sigmoid 或者 tanh 等 S 型曲线函数时,权重初始化使用 Xaiver 初始值。
    当激活函数使用 ReLU 时,权重初始化使用 He 初始值;

    如果不用这两种方法,还可以使用正态分布来初始化。不能使用一个常量来初始化所有的权值,因为如果使用了常量,那么每一层节点的输出都是一样的,最后一层每个节点输出也一样。这导致了梯度也是一样的,之后每次更新权值也一样,这样之后,不管怎么更新,整个神经网络权值还是一个量。

    为什么要进行 Xaiver 初始化、He 初始化?一个原因是解决梯度消失的问题,另一个是表达力受限的问题,很多节点都输出相同的值,那么可以由一个神经元表达同样的事情。Xaiver 和 He 初始化,本质还是正态分布,只不过使用了不同的标准差。Xaiver 的标准差取决于前一层的节点数,He 还要再乘个 2。[6] 的讨论之处,权值初始化的问题,Batch Normalization 已经很好地解决了。

    正态分布初始化的问题

    下面简要分析一下正态分布初始化存在的问题,具体看 [5] 的 P178 ~ P182,[5] 将 “正态分布” 的数据在一个 “正态分布初始化” 的权值的 5 层 MLP 网络上进行了前向传播。

    • 标准差为 1,输出偏向 0 或 1,因为使用的是 Sigmoid,在输出偏向 0 或 1 时,梯度很小,想象一下 Sigmoid 的图像,如果输出接近 0 或 1,曲线是不是很平缓呢。梯度在反向传播的过程中,经过连乘,就没有了,即存在梯度消失的问题。

    • 标准差为 0.01,输出全部都集中在 0.5 附近,输出太集中,存在的问题是表达力受限,因为多个节点输出都是相同的值。

    Xaiver 初始化存在的问题

    Xaiver 在使用 ReLU 作为激活函数的场景下,存在一些问题。输出值偏向 0,在反向传播的时候,梯度也会偏向 0,随着层的加深,会出现梯度消失的问题。

    Q: 为什么输出值偏小,在反向传播的时候,梯度也会偏向 0 呢?

    我们可以假设有这样一个网络,输入 x,中间有两层。

    [a_1 = ReLU(W_1 x + b_1) ]

    [y = W_2 a_1 + b_2 ]

    我们可以计算 (W_1) 的梯度,下面的公式定性地理解,本渣渣意识到矩阵并不能随便用链式法则,不过这里为了定性分析,还是可以的,你也可以将 (W_1) 视为某一个具体的权值变量,而不是矩阵。

    [frac{partial L}{partial W_1} = frac{partial L}{partial a_1} frac{partial ReLU(W_1 x + b_1)}{partial (W_1 x + b_1)} frac{partial (W_1 x + b_1)}{partial W_1} ]

    其中最后一项微分可以得到 (x),即上一层的输出。如果上一层的输出小了,那么梯度也会小,即偏向 0。叠多几层,梯度就消失了。

    优化的公式

    当前阶段,本渣渣只是知其然,不知所以然。天知道这些公式背后隐藏着多少的道理,为什么要用动量,为什么要累积梯度的平方?不过,虽然不知道为什么,但是至少要求自己知道怎么算。

    这里先声明几个符号:

    • (W),表示网络的权值。
    • (g_t),表示第 t 步的梯度,一般公式是 (frac{partial L}{partial W}),如果是 mini-batch,那么就是一个累积的梯度。
    • (v_t),表示第 t 步的动量,一般是梯度的累积。更新公式为 $v_t = alpha v_{t-1} - eta g_t $
    • (h_t),表示第 t 步的梯度平方和的累积。一般的更新公式为,(h_t = h_{t-1} + g_t^2)

    具体的计算,最好结合代码 [7] 来看:后面很多开方、除法操作,都是对矩阵的逐元素进行。

    SGD

    梯度下降,权值朝着梯度的反方向去。

    [W leftarrow W - eta g_t ]

    Momentum

    [v_t = alpha v_{t-1} - eta g_t ]

    [W leftarrow W + v_t ]

    Nesterov momentum

    先用上一步的动量,更新权值。

    [hat{W} leftarrow W + v_t ]

    使用 (hat{W}) 进行一次前向传播,重新计算梯度 (g_t)

    更新动量:

    [v_t = alpha v_{t-1} - eta g_t ]

    再更新权值:

    [W leftarrow W + v_t ]

    adagrad

    看到了下面公式,就知道为什么 adagrad 学久了就学不动了吧,梯度累积的越多,越学不动。看公式理解 adagrad,你会发现开根号的操作,这个操作应用到张量上的意义是什么呢?其实开根号在这里是逐元素操作,即对张量中的每个元素开根号,于是对应的权重学习率就不同了。这正是 adagrad 和以往的不同之处,每个参数都一个学习率,而不是所有参数都使用一个学习率。

    [h_t leftarrow h_{t-1} + g_t^2 ]

    [W leftarrow W - eta frac{1}{sqrt{h}} g_t ]

    RMSProp

    改进梯度累积的公式。

    [h_t leftarrow eta h_{t-1} + (1 - eta) g_t^2 ]

    [W leftarrow W - eta frac{1}{sqrt{h}} g_t ]

    adadelta

    改进了梯度的计算,注意到 adadelta 中的 delta 了吧,下面的公式真有 (Delta),至于如何计算,真的建议看 [7] 的代码,简单,清晰明了。再说一次,以下矩阵的开方和除法都是逐元素操作。

    [h_t leftarrow eta h_{t-1} + (1 - eta) g_t^2 ]

    [g_t' leftarrow frac{sqrt{Delta W_{t-1} + epsilon}}{sqrt{h_t + epsilon}} odot g_t ]

    [W leftarrow W - eta frac{1}{sqrt{h}} g_t' ]

    [Delta W_{t} leftarrow ho Delta W_{t-1} + (1 - ho) g_t'^2 ]

    Adam

    结合了 Momentum 和 adagrad 的思想

    [v_t leftarrow eta_1 v_{t-1} + (1 - eta_1) g_t ]

    [h_t leftarrow eta_2 h_{t-1} + (1 - eta_2) g_t^2 ]

    [hat{v_t} leftarrow frac{v_t}{1 - eta_1^t} ]

    [hat{h_t} leftarrow frac{h_t}{1 - eta_2^t} ]

    [g_t'leftarrow frac{eta hat{v_t}}{sqrt{hat{h_t}} + epsilon} ]

    [W leftarrow W - g_t' ]

    Yogi

    在累加梯度的平方时,这个梯度可能很大(blows up),使到 Adam 不能收敛(even in convex setting),因为这个梯度大,学习率也变大,结合上面的公式看看。

    于是有人提出了下面的公式进行改进

    [h_t leftarrow h_{t-1} - (1 - eta_2) (g_t^2 - h_{t-1}) ]

    [4] 提到了上述改进的问题:

    Whenever (g_t^2) has high variance or updates are sparse, (h_t) might forget past values too quickly.

    翻译翻译,当 (g_t^2) 方差较大的时候,(h_t) 前面的值可能很快就被忘记了,意思就是 (h_t) 前面的值在 (h_t) 中所占的比重减少了。为了解决这个问题,Yogi 提出了如下的公式。

    [h_t leftarrow h_{t-1} - (1 - eta_2) g_t^2 odot sign(g_t^2 - h_{t-1}) ]

    经过这个改进之后,(h_t) 可以变大,而对应到 Adam 中 (g_t') 的学习率会变小,这解决了 Adam 存在的问题。

    总结

    如上总结了常用的优化算法,本渣渣知其然,不知所以然,背后有许多为什么等待着探索。当前阶段,搞清楚每一种优化算法的优缺点,如何计算即可。此外,权值的初始化是一个比较重要的细节,需要稍加留意,在训练太慢的时候,可以检查一下。

    参考链接

    [1] https://zhuanlan.zhihu.com/p/73264637
    [2] https://towardsdatascience.com/understanding-rmsprop-faster-neural-network-learning-62e116fcf29a
    [3] https://d2l.ai/chapter_optimization/adadelta.html
    [4] https://d2l.ai/chapter_optimization/adam.html
    [5] 深度学习入门:斋藤康毅
    [6] https://www.quora.com/Does-Xavier-initialization-work-well-when-the-activation-function-is-a-ReLu
    [7] https://github.com/Lasagne/Lasagne/blob/master/lasagne/updates.py

  • 相关阅读:
    解析大型.NET ERP系统 权限模块设计与实现
    Enterprise Solution 开源项目资源汇总 Visual Studio Online 源代码托管 企业管理软件开发框架
    解析大型.NET ERP系统 单据编码功能实现
    解析大型.NET ERP系统 单据标准(新增,修改,删除,复制,打印)功能程序设计
    Windows 10 部署Enterprise Solution 5.5
    解析大型.NET ERP系统 设计异常处理模块
    解析大型.NET ERP系统 业务逻辑设计与实现
    解析大型.NET ERP系统 多国语言实现
    Enterprise Solution 管理软件开发框架流程实战
    解析大型.NET ERP系统 数据审计功能
  • 原文地址:https://www.cnblogs.com/zzk0/p/14988049.html
Copyright © 2011-2022 走看看