Py学习  »  机器学习算法

Scikit-Learn与TensorFlow机器学习实用指南 中文精要 十一、Training Deep Neural Nets(3)

龙哥盟飞龙 • 6 年前 • 492 次点击  

本篇文章是个人翻译的,如有商业用途,请通知本人谢谢.

Faster Optimizers 

训练一个非常大的深度神经网络可能会非常缓慢。 到目前为止,我们已经看到了四种加速培训的方法(并且达到更好的解决方案):对连接权重应用良好的初始化策略,使用良好的激活功能,使用批量规范化以及重用预训练网络的部分。 另一个巨大的速度提升来自使用比普通渐变下降优化器更快的优化器。 在本节中,我们将介绍最流行的:动量优化,Nesterov加速梯度,AdaGrad,RMSProp,最后是Adam优化。

剧透:本节的结论是,您几乎总是应该使用Adam_optimization,所以如果您不关心它是如何工作的,只需使用AdamOptimizer替换您的GradientDescentOptimizer,然后跳到下一节! 只需要这么小的改动,训练通常会快几倍。 但是,Adam优化确实有三个可以调整的超参数(加上学习率)。 默认值通常工作的不错,但如果您需要调整它们,可能会有助于知道他们做什么。  Adam optimization结合了来自其他优化算法的几个想法,所以先看看这些算法是有用的。

Momentum optimization
想象一下,一个保龄球在一个光滑的表面上平缓的斜坡上滚动:它会缓慢地开始,但是它会很快地达到最终的速度(如果有一些摩擦或空气阻力的话)。 这是Boris Polyak在1964年提出的Momentum优化背后的一个非常简单的想法.相比之下,普通的Gradient Descent只需要沿着斜坡进行小的有规律的下降步骤,所以需要更多的时间才能到达底部。

回想一下,梯度下降只是通过直接减去损失函数J(θ)相对于权重(θJ(θ))乘以学习率η的梯度来更新权重θ。 方程是:θ←θ-η∇θJ(θ)。 它不关心早期的梯度是什么。 如果局部梯度很小,则会非常缓慢。


动量优化很关心以前的梯度:在每次迭代时,它将动量矢量m(乘以学习率η)的局部梯度相加,并且通过简单地减去该动量矢量来更新权重(参见公式11-4)。 换句话说,梯度用作加速度,不用作速度。 为了模拟某种摩擦机制,避免动量过大,该算法引入了一个新的超参数β,简称为动量,它必须设置在0(高摩擦)和1(无摩擦)之间。 典型的动量值是0.9。

您可以很容易地验证,如果梯度保持不变,则终端速度(即,权重更新的最大大小)等于该梯度乘以学习率η乘以例如,如果β= 0.9,则最终速度等于学习速率的梯度乘以10倍,因此动量优化比梯度下降快10倍! 这使Momentum优化比Gradient Descent快得多。 特别是,我们在第四章中看到,当输入量具有非常不同的尺度时,损失函数看起来像一个细长的碗(见图4-7)。 梯度下降速度很快,但要花很长的时间才能到达底部。 相反,动量优化会越来越快地滚下山谷底部,直到达到底部(最佳)。


在不使用批处理标准化的深层神经网络中,上层通常最终输入具有不同的尺度,所以使用Momentum优化会有很大的帮助。 它也可以帮助滚过局部的最佳状态。

由于动力的原因,优化器可能会超调一些,然后再回来,再次超调,并在稳定在最小值之前多次振荡。 这就是为什么在系统中有一点摩擦的原因之一:它消除了这些振荡,从而加速了收敛。

在TensorFlow中实现Momentum优化是一件简单的事情:只需用MomentumOptimizer替换GradientDescentOptimizer,然后躺下来赚钱!


Momentum优化的一个缺点是它增加了另一个超参数来调整。 然而,0.9的动量值通常在实践中运行良好,几乎总是比梯度下降快。


Nesterov Accelerated Gradient
Yurii Nesterov在1983年提出的Momentum优化的一个小变量几乎总是比vanilla Momentum优化更快。 Nesterov Momentum优化或Nesterov加速梯度(Nesterov Accelerated Gradient,NAG)的思想是测量损失函数的梯度不是在局部位置,而是在动量方向稍微靠前(见公式11-5)。 与vanilla Momentum优化的唯一区别在于梯度是在θ+βm而不是在θ处测量的。


这个小小的调整是可行的,因为一般来说,动量矢量将指向正确的方向(即朝向最优方向),所以使用在该方向上测得的梯度稍微更精确,而不是使用 原始位置的梯度,如图11-6所示(其中∇1代表在起点θ处测量的代价函数的梯度,∇2代表位于θ+βm的点处的梯度)。


正如你所看到的,Nesterov更新稍微靠近最佳值。 过了一段时间,这些小的改进加起来,NAG最终比常规的Momentum优化快得多。 此外,请注意,当动量推动权重横跨山谷时,▽1继续推进越过山谷,而▽2推回山谷的底部。 这有助于减少振荡,从而更快地收敛。

与常规的动量优化相比,NAG几乎总能加速训练。 要使用它,只需在创建MomentumOptimizer时设置use_nesterov = True:



AdaGrad
再次考虑细长碗的问题:梯度下降从最陡峭的斜坡快速下降,然后缓慢地下到谷底。 如果算法能够早期检测到这个问题并且纠正它的方向来指向全局最优点,那将是非常好的。

AdaGrad算法通过沿着最陡的维度缩小梯度向量来实现这一点(见公式11-6):


第一步将梯度的平方累加到矢量s中(⊗符号表示单元乘法)。 这个向量化形式相当于向量s的每个元素si计算si←si +(∂/∂θiJ(θ))^ 2; 换一种说法,每个si关于参数θi累加损失函数的偏导数的平方。 如果损失函数沿着第i维陡峭,则在每次迭代时,si将变得越来越大。

第二步几乎与梯度下降相同,但有一个很大的不同:梯度矢量按比例缩小(⊘符号表示元素分割,ε是避免被零除的平滑项,通常设置为 ). 这个矢量化的形式相当于计算对于所有参数θi(同时)。

简而言之,这种算法会降低学习速度,但对于陡峭的尺寸,其速度要快于具有温和的斜率的尺寸。 这被称为自适应学习率。 它有助于将更新的结果更直接地指向全局最优(见图11-7)。 另一个好处是它不需要那么多的去调整学习速率超参数η。


对于简单的二次问题,AdaGrad经常表现良好,但不幸的是,在训练神经网络时,它经常停止得太早。 学习速率被缩减得太多,以至于在达到全局最优之前,算法完全停止。 所以,即使TensorFlow有一个AdagradOptimizer,你也不应该用它来训练深度神经网络(虽然对线性回归这样简单的任务可能是有效的)。


RMSProp
尽管AdaGrad的速度变慢了一点,并且从未收敛到全局最优,但是RMSProp算法通过仅累积最近迭代(而不是从训练开始以来的所有梯度)的梯度来修正这个问题。 它通过在第一步中使用指数衰减来实现(见公式11-7)。


他的衰变率β通常设定为0.9。 是的,它又是一个新的超参数,但是这个默认值通常运行良好,所以你可能根本不需要调整它。

正如您所料,TensorFlow拥有一个RMSPropOptimizer类:


除了非常简单的问题,这个优化器几乎总是比AdaGrad执行得更好。 它通常也比Momentum优化和Nesterov加速梯度表现更好。 事实上,这是许多研究人员首选的优化算法,直到Adam optimization出现。


Adam Optimization 

Adam,代表自适应矩估计,结合了动量优化和RMSProp的思想:就像动量优化一样,它追踪过去梯度的指数衰减平均值,就像RMSProp一样,它跟踪过去平方梯度的指数衰减平均值 (见方程式11-8)。


T代表迭代次数(从1开始)。

如果你只看步骤1,2和5,你会注意到Adam与Momentum优化和RMSProp的相似性。 唯一的区别是第1步计算指数衰减的平均值,而不是指数衰减的和,但除了一个常数因子(衰减平均值只是衰减和的1 - β1倍)之外,它们实际上是等效的。 步骤3和步骤4是一个技术细节:由于m和s初始化为0,所以在训练开始时它们会偏向0,所以这两步将在训练开始时帮助提高m和s。

动量衰减超参数β1通常初始化为0.9,而缩放衰减超参数β2通常初始化为0.999。 如前所述,平滑项ε通常被初始化为一个很小的数,例如.这些是TensorFlow的AdamOptimizer类的默认值,所以你可以简单地使用:



实际上,由于Adam是一种自适应学习速率算法(如AdaGrad和RMSProp),所以对学习速率超参数η的调整较少。 您经常可以使用默认值η= 0.001,使Adam更容易使用x相对于梯度下降。

迄今为止所讨论的所有优化技术都只依赖于一阶偏导数(雅可比矩阵)。 优化文献包含基于二阶偏导数(Hessians)的惊人算法。 不幸的是,这些算法很难应用于深度神经网络,因为每个输出有n ^ 2个Hessians(其中n是参数的数量),而不是每个输出只有n个Jacobian。 由于DNN通常具有数以万计的参数,二阶优化算法通常甚至不适合内存,甚至在他们这样做时,计算Hessians也是太慢了。

Training Sparse Models(训练稀疏模型)

所有刚刚提出的优化算法都会产生密集的模型,这意味着大多数参数都是非零的。 如果你在运行时需要一个非常快速的模型,或者如果你需要它占用较少的内存,你可能更喜欢用一个稀疏模型来代替。

实现这一点的一个微不足道的方法是像平常一样训练模型,然后摆脱微小的权重(将它们设置为0)。
另一个选择是在训练过程中应用强正则化,因为它会推动优化器尽可能多地消除权重(如第4章关于套索回归的讨论)。

但是,在某些情况下,这些技术可能仍然不足。 最后一个选择是应用双重平均,通常称为遵循正规化领导者(FTRL),一种由尤里·涅斯捷罗夫(Yurii Nesterov)提出的技术。 当与l1正则化一起使用时,这种技术通常导致非常稀疏的模型。 TensorFlow在FTRLOptimizer类中实现称为FTRL-Proximal的FTRL变体。

Learning Rate Scheduling(学习速率调度)

找到一个好的学习速度可能会非常棘手。 如果设置太高,训练实际上可能偏离(如我们在第4章)。 如果设置得太低,训练最终会收敛到最佳状态,但这需要很长时间。 如果将其设置得太高,开始的进度会非常快,但最终会围绕最佳方式跳舞,永远不会安顿下来(除非您使用自适应学习速率优化算法,如AdaGrad,RMSProp或Adam,但是 即使这样可能需要时间来解决)。 如果您的计算预算有限,那么您可能必须在正确收敛之前中断培训,产生次优解决方案(参见图11-8)。


通过使用各种学习速率和比较学习曲线,在几个时期内对您的网络进行多次训练,您也许能够找到相当好的学习速度。 理想的学习速度将会快速学习并收敛到良好的解决方案。

然而,你可以做得比不断的学习速度更好:如果你从一个高的学习速度开始,然后一旦它停止快速的进步就减少它,你可以比最佳的恒定学习速度更快地达到一个好的解决方案。 有许多不同的策略,以减少训练期间的学习率。 这些策略被称为学习时间表(我们在第4章中简要介绍了这个概念),其中最常见的是:


预定的分段恒定学习率:

例如,首先将学习率设置为η0= 0.1,然后在50个时期之后将学习率设置为η1= 0.001。 虽然这个解决方案可以很好地工作,但是通常需要弄清楚正确的学习速度以及何时使用它们。


性能调度:

每N步测量验证错误(就像提前停止一样),当错误停止下降时,将学习速率降低一个因子λ。


指数调度:

将学习率设置为迭代次数t的函数:。 这很好,但它需要调整η0和r。 学习率将由每r步下降10个因素。


幂调度:

设学习率为。 超参数c通常被设置为1.这与指数调度类似,但是学习速率下降要慢得多.

Andrew Senior等2013年的论文。 比较了使用Momentum优化训练深度神经网络进行语音识别时一些最流行的学习计划的性能。 作者得出结论:在这种情况下,性能调度和指数调度都表现良好,但他们更喜欢指数调度,因为它实现起来比较简单,容易调整,收敛速度略快于最佳解决方案。

使用TensorFlow实现学习计划非常简单:

    initial_learning_rate = 0.1
    decay_steps = 10000
    decay_rate = 1/10
    global_step = tf.Variable(0, trainable=False, name="global_step")
    learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step,
                                               decay_steps, decay_rate)
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss, global_step=global_step)

设置超参数值后,我们创建一个不可跟踪的变量global_step(初始化为0)以跟踪当前的训练迭代次数。 然后我们使用TensorFlow的exponential_decay()函数来定义指数衰减的学习率(η0= 0.1和r = 10,000)。 接下来,我们使用这个衰减的学习率创建一个优化器(在这个例子中是一个MomentumOptimizer)。 最后,我们通过调用优化器的minimize()方法来创建训练操作; 因为我们将global_step变量传递给它,所以请注意增加它。 就是这样!

由于AdaGrad,RMSProp和Adam优化自动降低了培训期间的学习率,因此不需要添加额外的学习计划。 对于其他优化算法,使用指数衰减或性能调度可显着加速收敛。

完整代码:

n_inputs = 28 * 28  # MNIST
n_hidden1 = 300
n_hidden2 = 50
n_outputs = 10

X = tf.placeholder(tf.float32, shape=(None, n_inputs), name="X")
y = tf.placeholder(tf.int64, shape=(None), name="y")

with tf.name_scope("dnn"):
    hidden1 = tf.layers.dense(X, n_hidden1, activation=tf.nn.relu, name="hidden1")
    hidden2 = tf.layers.dense(hidden1, n_hidden2, activation=tf.nn.relu, name="hidden2")
    logits = tf.layers.dense(hidden2, n_outputs, name="outputs")

with tf.name_scope("loss"):
    xentropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=logits)
    loss = tf.reduce_mean(xentropy, name="loss")

with tf.name_scope("eval"):
    correct = tf.nn.in_top_k(logits, y, 1)
    accuracy = tf.reduce_mean(tf.cast(correct, tf.float32), name="accuracy")
with tf.name_scope("train"):       # not shown in the book
    initial_learning_rate = 0.1
    decay_steps = 10000
    decay_rate = 1/10
    global_step = tf.Variable(0, trainable=False, name="global_step")
    learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step,
                                               decay_steps, decay_rate)
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
    training_op = optimizer.minimize(loss, global_step=global_step)
init = tf.global_variables_initializer()
saver = tf.train.Saver()
n_epochs = 5
batch_size = 50

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        for iteration in range(mnist.train.num_examples // batch_size):
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch, y: y_batch})
        accuracy_val = accuracy.eval(feed_dict={X: mnist.test.images,
                                                y: mnist.test.labels})
        print(epoch, "Test accuracy:", accuracy_val)

    save_path = saver.save(sess, "./my_model_final.ckpt")



今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/92gRmM73ME
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/10646
 
492 次点击