自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率

代码堆栈: https://github.com/brandonlyg/cute-dl
(转载请注明出处!)

目的

  1. 增添学习率优化器, 加速模子在小学习率下模子的训练速率。
  2. 使用MNIST数据集对照同一个模子使用差异学习率优化器的显示。

常见的学习率优化算法

        在上个阶段,我们使用牢固学习率优化器训练识别MNIST手写数字模子。在后面的示例中将会看到: 若是学习习设置太大,模子将无法收敛; 若是设置学习率太小模子大概率会收敛速率会异常缓慢。因此必须要要给学习率设置一个合适的值,这个合适的值到底是什么需要频频试验。
        训练模子的本质是,在由损失函数界说的高纬超平面中尽可能地找到最低点。由于高纬超平面十分复杂,找到全局最低点往往不现实,因此找到一个只管靠近全局最低点的局部最低点也是可以的。
        由于模子参数是随机初始化的,在训练的初始阶段, 可能远离最低点,也可能距最低点较较近。为了使模子能够收敛,较小的学习率对照大的学习率更有可能到达目地, 至少不会使模子发散。
        理想的状态下,我们希望,学习率是动态的: 在远离最低点的时刻有较大的学习率,在靠近最低点的时刻有较小的学习率。
        学习率算法在训练过程中动态调整学习率,试图使学习率靠近理想状态。常见的学习率优化算法有:

  • 动量算法。
  • Adagrad算法。
  • RMSProp算法。
  • Adadelta算法。
  • Adam算法。

        现在没有一种理论能够给出定量的结论断言一种算法比另一种更好,详细选用哪种算法视详细情形而定。
        接下来将会详细讨论每种算法的数学性子及实现,为了利便讨论,先给出一些统一的界说:

  • 模子履历一次向前流传和一次反向流传称训练称为一次迭代。用t示意模子当前的迭代次数,t=0,1,2,…。 当t=0是示意模子处于初始状态。
  • g示意反向流传的梯度, \(g_t\)是第t次迭的梯度。其他量的示意方式和g相同。
  • w示意模子学习的参数。α示意学习率超参数。
  • 符号a ⊙ v若是a和v都是向量, a, v必须有相同的维度a ⊙ v示意他们相同位置上的元素相乘,结果是和a,v具有相同维度的向量. 若是a是标量v是向量a ⊙ v示意向量的标量乘法等价于av。

动量算法

数学原理

\[\begin{matrix} v_t = v_{t-1}γ + αg_t \\ w = w – v_t\\ \end{matrix} \]

        其中v是动量\(v_0=0\), γ是动量的衰减率\(γ∈(0,1)\). 现在把\(v_t\)睁开看一下\(g_i, i=1,2,…t\)对v_t的影响.

\[v_t = α(γ^{t-1}g_1 + γ^{t-2}g_2 + … + γg_{t-1} + g_t) \]

        个项系数之和的极限情形:

\[\lim_{t \to ∞} \sum_{i=1}^t γ^{t-i} = \frac{1}{1-γ} \]

        令\(t=\frac{1}{1-γ}\)则有\(\frac{1}{t}=1-γ\), \(g_i\)的指数加权平均值可用下式示意:

\[\bar v_t = \frac{1}{t}(γ^{t-1}g_1 + γ^{t-2}g_2 + … + γg_{t-1} + g_t) = (1-γ)(γ^{t-1}g_1 + γ^{t-2}g_2 + … + γg_{t-1} + g_t) \]

        若是把学习率α示意为:\(α=\frac{α}{1-γ}(1-γ)\),因此\(v_t\)可以看成是最近的\(1-γ\)次迭代梯度的指数加权平均乘以一个缩放量\(\frac{α}{1-γ}\), 这里的缩放量\(\frac{α}{1-γ}\)才是真正的学习率参数。
        设\(\frac{1}{1-γ}=n\), 最近n次迭代梯度的权重占总权重的比例为:

\[(\sum_{i=1}^{n} γ^i) / \frac{1}{1-γ} = \frac{\frac{1-γ^n}{1-γ}}{\frac{1}{1-γ}} = 1-γ^n = 1 – γ^{\frac{1}{1-γ}} \]

        当γ=0.9时, \(1 – γ^{10}≈0.651\), 就是说, 这个时刻, 最近的10次迭代占总权重的比例约为65.1%, 换言之\(v_t\)的值的数量级由最近10次迭代权重值决议。
        当我们设置超参数γ,α时, 可以以为取了最近\(\frac{1}{1-γ}\)次迭代梯度的指数加权平均值为动量积累量,然后对这个积累量作\(\frac{α}{1-γ}\)倍的缩放。 例如: γ=0.9, α=0.01, 示意取最近10次的加权平均值,然后将这个值缩小到原来的0.1倍。
        动量算法能够有效地缓解\(g_t \to 0\)时参数更新缓慢的问题和当\(g_t\)很大时参数更新幅度过大的问题。
        对照原始的梯度下降算法和使用动量的梯度下降算法更新参数的情形:

\[\begin{matrix} w = w – αg_t & (1) & 原始梯度下降算法 \\ w = w – v_t & (2) & 动量算法 \end{matrix} \]

        \(g_t \to 0\)时, 有3种可能:

  1. 当前位置是超平面的一个全局最低点。是期望的理想收敛位置,(1)式会住手更新参数, (2)式会使参数在这个位置周围以越来越小的幅度震荡, 最终收敛到这个位置。
  2. 当前位置是超平面的一个局部最低点。(1)式会停留在这个位置, 最终学习到一个不理想的参数。(2)式中\(v_t\)由于积累了最近n步的势能, 会冲过这个区域, 继续寻找更优解。
  3. 当前位置是超平面的一个鞍点。 (1)式会停留在这个位置, 最终学习到一个不可用的参数。(2)式中\(v_t\)由于记录了最近n步的势能, 会冲过这个区域, 继续寻找更优解

        当\(g_t\)很大时(1)式导致参数大幅度的更新, 会大概率导致模子发散。(2)式当γ=0.9时, \(g_t\)\(v_t\)的影响权重是0.1; 式当γ=0.99时,\(g_t\)\(v_t\)的影响权重是0.01, 相比于\(g_{t-1}\)\(g_t\)的增添幅度, \(v_{t-1}\)\(v_t\)增添幅度要小的多, 参数更新也会平滑许多。

实现

        文件: cutedl/optimizers.py, 类名:Momentum.

  def update_param(self, param):
      #pdb.set_trace()
      if not hasattr(param, 'momentum'):
          #为参数添加动量属性
          param.momentum = np.zeros(param.value.shape)

      param.momentum = param.momentum * self.__dpr + param.gradient * self.__lr

      param.value -= param.momentum

Adagrad算法

数学原理

\[\begin{matrix} s_t = s_{t-1} + g_t ⊙ g_t \\ Δw_t = \frac{α}{\sqrt{s_t} + ε} ⊙ g_t \\ w = w – Δw_t \end{matrix} \]

        其中\(s_t\)是梯度平方的积累量, \(ε=10^{-6}\)用来保持数值的稳固, Δw_t是参数的转变量。\(s_t\)的每个元素都是正数, 随着迭代次数增添, 数值会越来越大,响应地\(\frac{α}{\sqrt{s_t} + ε}\)的值会越来越小。\(\frac{α}{\sqrt{s_t} + ε}\)相当于为\(g_t\)中的每个元素盘算自力的的学习率, 使\(Δw_t\)中的每个元素位于(-1, 1)区间内,随着训练次数的增添会向0收敛。这意味着\(||Δw_t||\)会越来越小, 迭代次数对照大时, \(||Δw_t|| \to 0\), 参数w将不会有更新。相比于动量算法, Adagrad算法调整学习率的偏向对照单一, 只会往更小的偏向上调整。α不能设置较大的值, 由于在训练初期\(s_t\)的值会很小, \(\frac{α}{\sqrt{s_t} + ε}\)会放大α的值, 导致较大的学习率,从而导致模子发散。

BypassUAC

实现

        文件: cutedl/optimizers.py, 类名:Adagrad.

  def update_param(self, param):
      #pdb.set_trace()
      if  not hasattr(param, 'adagrad'):
          #添加积累量属性
          param.adagrad = np.zeros(param.value.shape)

      a = 1e-6
      param.adagrad += param.gradient ** 2
      grad = self.__lr/(np.sqrt(param.adagrad) + a) * param.gradient
      param.value -= grad

RMSProp算法

数学原理

        为了战胜Adagrad积累量不停增添导致学习率会趋近于0的缺陷, RMSProp算法的设计在Adagrad的基础上引入了动量头脑。

\[\begin{matrix} s_t = s_{t-1}γ + g_t ⊙ g_t(1-γ) \\ Δw_t = \frac{α}{\sqrt{s_t} + ε} ⊙ g_t \\ w = w – Δw_t \end{matrix} \]

        算法设计者给出的推荐参数是γ=0.99, 即\(s_t\)是最近100次迭代梯度平方的积累量, 由于盘算转变量时使用的是\(\sqrt{s_t}\), 对转变量的影响只相当于最近10次的梯度积累量。
        的Adagrad类似, \(s_t\)\(g_t\)的偏向影响较小, 但对\(||g_t||\)巨细影响较大,会把它缩小到(-1, 1)区间内, 差异的是不会单调地把\(||g_t||\)收敛到0, 从而战胜了Adagrad的缺陷。

实现

        文件: cutedl/optimizers.py, 类名:RMSProp.

  def update_param(self, param):
      #pdb.set_trace()
      if not hasattr(param, 'rmsprop_storeup'):
          #添加积累量属性
          param.rmsprop_storeup = np.zeros(param.value.shape)

      a = 1e-6

      param.rmsprop_storeup = param.rmsprop_storeup * self.__sdpr + (param.gradient**2) * (1-self.__sdpr)
      grad = self.__lr/(np.sqrt(param.rmsprop_storeup) + a) * param.gradient

      param.value -= grad

Adadelta算法

数学原理

        这个算法的最大特点是不需要全局学习率超参数, 它也引入了动量头脑,使用转变量平方的积累量和梯度平方的积累量共同为\(g_t\)的每个元素盘算自力的学习率。

\[\begin{matrix} s_t = s_{t-1}γ + g_t ⊙ g_t(1-γ) \\ Δw_t = \frac{\sqrt{d_{t-1}} + ε}{\sqrt{s_t} + ε} ⊙ g_t \\ d_t = d_{t-1}γ + Δw_t ⊙ Δw_t(1-γ)\\ w = w – Δw_t \end{matrix} \]

        这个算法引入了新的量\(d_t\), 是转变量平方的积累量, 示意最近n次迭代的参数转变量平方的加权平均. \(ε=10^{-6}\). 推荐的超参数值是γ=0.99。这个算法和RMSProp类似, 只是用\(\sqrt{d_{t-1}}\)取代了学习率超参数α。

实现

        文件: cutedl/optimizers.py, 类名:Adadelta.

  def update_param(self, param):
      #pdb.set_trace()
      if not hasattr(param, 'adadelta_storeup'):
          #添加积累量属性
          param.adadelta_storeup = np.zeros(param.value.shape)

      if not hasattr(param, "adadelta_predelta"):
          #添加上步的转变量属性
          param.adadelta_predelta = np.zeros(param.value.shape)

      a = 1e-6

      param.adadelta_storeup = param.adadelta_storeup * self.__dpr + (param.gradient**2)*(1-self.__dpr)
      grad = (np.sqrt(param.adadelta_predelta)+a)/(np.sqrt(param.adadelta_storeup)+a) * param.gradient
      param.adadelta_predelta = param.adadelta_predelta * self.__dpr + (grad**2)*(1-self.__dpr)

      param.value -= grad

Adam算法

数学原理

        前面讨论的Adagrad, RMSProp和Adadetal算法, 他们使用的加权平均积累量对\(g_t\)的范数影响较大, 对\(g_t\)的偏向影响较小, 另外它们也不能缓解\(g_t \to 0\)的情形。Adam算法同时引入梯度动量和梯度平方动量,理论上可以战胜前面三种算法共有的缺陷的缺陷。

\[\begin{matrix} v_t = v_{t-1}γ_1 + g_t(1-γ_1) \\ s_t = s_{t-1}γ_2 + g_t ⊙ g_t(1-γ_2) \\ \hat{v_t} = \frac{v_t}{1 – γ_1^t} \\ \hat{s_t} = \frac{s_t}{1 – γ_2^t} \\ Δw_t = \frac{α\hat{v_t}}{\sqrt{s_t} + ε} \\ w = w – Δw_t \end{matrix} \]

        其中\(v_t\)和动量算法中的\(v_t\)寄义一样,\(s_t\)和RMSProp算法的\(s_t\)寄义一样, 对应的超参数也有一样的推荐值\(γ_1=0.9\), \(γ_2=0.99\)。用于稳固数值的\(ε=10^{-8}\). 对照稀奇的是\(\hat{v_t}\)\(\hat{s_t}\), 他们是对\(v_t\)\(s_t\)的一个修正。以\(\hat{v_t}\)为例, 当t对照小的时刻, \(\hat{v_t}\)近似于最近\(\frac{1}{1-γ}\)次迭代梯度的加权和而不是加权平均, 当t对照大时, \(1-γ^t \to 1\), 从而使\(\hat{v_t} \to v_t\)。也就是所\(\hat{v_t}\)时对对迭代次数较少时\(v_t\)值的修正, 防止在模子训练的最先阶段发生太小的学习率。\(\hat{s_t}\)的作用和\(\hat{v_t}\)是类似的。

实现

        文件: cutedl/optimizers.py, 类名:Adam.

  def update_param(self, param):
      #pdb.set_trace()
      if not hasattr(param, 'adam_momentum'):
          #添加动量属性
          param.adam_momentum = np.zeros(param.value.shape)

      if not hasattr(param, 'adam_mdpr_t'):
          #mdpr的t次方
          param.adam_mdpr_t = 1

      if not hasattr(param, 'adam_storeup'):
          #添加积累量属性
          param.adam_storeup = np.zeros(param.value.shape)

      if not hasattr(param, 'adam_sdpr_t'):
          #动量sdpr的t次方
          param.adam_sdpr_t = 1

      a = 1e-8
      #盘算动量
      param.adam_momentum = param.adam_momentum * self.__mdpr + param.gradient * (1-self.__mdpr)
      #误差修正
      param.adam_mdpr_t *= self.__mdpr
      momentum = param.adam_momentum/(1-param.adam_mdpr_t)

      #盘算积累量
      param.adam_storeup = param.adam_storeup * self.__sdpr + (param.gradient**2) * (1-self.__sdpr)
      #误差修正
      param.adam_sdpr_t *= self.__sdpr
      storeup = param.adam_storeup/(1-param.adam_sdpr_t)

      grad = self.__lr * momentum/(np.sqrt(storeup)+a)
      param.value -= grad

差异学习率对训练模子的影响

        接下来我们仍然使用上个阶段的模子做为示例, 使用差异的优化算法训练模子,对比差异。代码在examples/mlp/mnist-recognize.py中
        代码中有两个竣事训练的条件:

  1. 延续20次验证没有获得更小的验证误差,示意模子模子已经无法进一步优化或者已经最先发散了,竣事训练。
  2. 延续20次验证模子验证正确率都在91%以上,示意模子性能已经到达预期目的且是收敛的,竣事训练。

不使用优化算法的情形

使用较小的学习率

  def fit0():
    lr = 0.0001
    print("fit1 lr:", lr)
    fit('0.png', optimizers.Fixed(lr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        较小的牢固学习率0.0001可以使模子稳固地收敛,但收敛速率很慢, 训练靠近100万步, 最后由于收敛速率太慢而住手训练。

使用较大的学习率

def fit1():
    lr = 0.2
    print("fit0 lr:", lr)
    fit('1.png', optimizers.Fixed(lr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        较大的牢固学习率0.2, 模子在训练7万步左右的时刻因发散而住手训练。模子进度最先降低: 最大验证正确率为:0.8445, 竣事时的验证正确率为:0.8438.

适当的学习率

def fit2():
    lr = 0.01
    print("fit2 lr:", lr)
    fit('2.png', optimizers.Fixed(lr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        通过多次试验, 找到了一个合适的学习率0.01, 这时模子只需训练28000步左右即可到达期望性能。

动量算法优化器

def fit_use_momentum():
    lr = 0.002
    dpr = 0.9
    print("fit_use_momentum lr=%f, dpr:%f"%(lr, dpr))
    fit('momentum.png', optimizers.Momentum(lr, dpr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        这里的真实学习率为\(\frac{0.002}{1-0.9} = 0.02\)。模子训练23000步左右即可到达期望性能。这里的学习率稍大,证实动量算法可以顺应稍大学习率的数学性子。

Adagrad算法优化器

def fit_use_adagrad():
    lr = 0.001
    print("fit_use_adagrad lr=%f"%lr)
    fit('adagrad.png', optimizers.Adagrad(lr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        多次试验解释,Adagrad算法的参数最欠好调。由于这个算法的学习率会一直单调递减, 它只能对模子举行小幅度的优化, 故而这个算法并不适合从头最先训练模子,对照适合对预训练的模子参数举行微调。

RMSProp算法优化器

def fit_use_rmsprop():
    sdpr = 0.99
    lr=0.0001
    print("fit_use_rmsprop lr=%f sdpr=%f"%(lr, sdpr))
    fit('rmsprop.png', optimizers.RMSProp(lr, sdpr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        这里给出的是较小的学习率0.0001。多次试验解释, RMSProp在较大学习率下很容易发散,而在较小学习率下通常会有稳固的优越显示。

Adadelta算法优化器

def fit_use_adadelta():
    dpr = 0.99
    print("fit_use_adadelta dpr=%f"%dpr)
    fit('adadelta.png', optimizers.Adadelta(dpr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        这个算法不需要给出学习率参数。多次试验显示, 在这个简朴模子上, Adadelta算法显示得异常稳固。

Adam算法优化器

def fit_use_adam():
    lr = 0.0001
    mdpr = 0.9
    sdpr = 0.99
    print("fit_use_adam lr=%f, mdpr=%f, sdpr=%f"%(lr, mdpr, sdpr))
    fit('adam.png', optimizers.Adam(lr, mdpr, sdpr))

自己着手实现深度学习框架-5 使用学习率优化器加速模子训练速率
        只用这个算法在较小学习率0.0001的情形下20000步左右即可完成训练且最终到达了92.4%的验证准确率。

总结

        这个阶段为框架添加了常见的学习率优化算法,并在同一个模子上举行验证,对比。我发现纵然不使用优化算法,用牢固的学习率, 只要给出“合适”的学习率参数,仍然能够获得理想的训练速率, 但很难确定怎样才算“适合”。 学习率算法给出了参数调整的大致偏向,一般来说较小的学习率都不会有问题,至少不会使模子发散,然后可以通过调整衰减率来加速训练速率,而衰减率有对照简朴数学性子可以让我们在调整它的时知道这样调整意味着什么。
        现在为止cute-dl框架已经实现了对简朴MLP模子的周全支持,接下来将会为框架添一些层,让它能够支持卷积神经网络模子。

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/6352.html