模型训练时的学习率默认设置为0.01。_人人都能懂的机器学习——训练深度神经网络——学习率规划...
学习率规划找到一个合适的学习率非常重要。如果将学习率设置过高,模型训练可能会发散。如果设置过低,那么训练虽然会收敛至最优,但是会耗费大量的时间。如果你将学习率设置得稍微有点高,那么模型在一开始训练收敛的速度会很快,但是最终会在最优点附近徘徊,无法真正收敛至最优。如果你拥有的计算资源有限,那么你可能会在收敛前就中止训练,生成一个次优解(见图2.8)。图2.8 不同学习率的学习曲线在之前的文...
·
学习率规划
找到一个合适的学习率非常重要。如果将学习率设置过高,模型训练可能会发散。如果设置过低,那么训练虽然会收敛至最优,但是会耗费大量的时间。如果你将学习率设置得稍微有点高,那么模型在一开始训练收敛的速度会很快,但是最终会在最优点附近徘徊,无法真正收敛至最优。如果你拥有的计算资源有限,那么你可能会在收敛前就中止训练,生成一个次优解(见图2.8)。
图2.8 不同学习率的学习曲线在之前的文章中就提到过,一个寻找合适的学习率的策略是,对模型进行几百次迭代训练,并且从一个很小的学习率开始成倍提高。然后根据学习曲线,如果有一个学习曲线开始向上发散,那么就挑选比发散学习率稍小的即可。然后再重新初始化模型,用选中的学习率进行训练。但是我们的模型可以比恒定学习率做得更好:如果你从一个很大的学习率开始,然后在训练无法取得进展时降低它,你就可以比最优恒定学习率更快地得到一个好的结果。这里有很多不同的在训练中降低学习率的方法。有些甚至会从一个小的学习率开始,不断调高,然后再减小。这些策略被称为学习率的规划(learning schedules)。接下来我们来看一些常用的学习率策略:
- 幂规划
- 指数规划
- 分段常数规划
- 性能规划
- 单循环规划
Andrew Senior等人在2013年的一篇论文中比较了一些最流行了学习率规划方法与动量优化配合训练出用于语音识别的深度神经网络。作者的结论是,在这种情况下,性能规划和指数规划的表现比较出色。他们更倾向于指数规划法,因为它易于调优,并且收敛速度也稍快。他们还提到,指数规划比性能规划更容易实现,不过在Keras当中,这两个都很容易。作者还指出,使用单循环规划表现得更为出色。在Keras中实现幂规划非常简单,只要在创建优化器时设置好decay超参数:
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-4)decay超参数是参数s的倒数,并且Keras假设c等于1。指数规划和分段规划的部署也很简单。首先要定义一个接收当前epoch并返回学习速率的函数,我们来拿一个指数规划做例子:
def exponential_decay_fn(epoch):return 0.01 * 0.1**(epoch / 20)如果你不想硬编码η0和s,你可以创建一个返回配置函数的函数:
def exponential_decay(lr0, s):def exponential_decay_fn(epoch):return lr0 * 0.1**(epoch / s)return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)接着创建一个LearningRateScheduler回调函数,输入一个规划函数,然后将这个回调函数输入给fit()方法:
lr_scheduler = keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train_scaled, y_train, [...], callbacks=[lr_scheduler])LearningRateScheduler会在每个epoch开始的时候将优化器的learning_rate属性更新一次。一般来说每个epoch更新一次学习率就足够了,但是如果想要更加频繁地更新学习率,比如在每一步都更改学习率,那么可以写自己的回调函数。实际上如果每个epoch当中有很多步的话,对于每一步都更新学习率是有意义的。或者,可以使用keras.optimizers.schedules方法,在稍后会介绍这种方法。规划函数还可以将当前学习率作为第二个参数。比如,下面这个规划函数就将当前学习率乘以0.11/20,这样也能够形成指数衰减(不同的是这个衰减从第0个epoch就开始了,而不是第1个)。
def exponential_decay_fn(epoch, lr):return lr * 0.1**(1 / 20)这个实现方法比较依赖优化器的初始学习率,所以需要确保设置一个合适的初始值。当保存一个模型时,优化器和学习率也会一起被保存。也就是说,有了上面这个规划函数,你可以加载一个已经训练过的模型,然后在它停止训练的地方继续训练,不会有任何问题。不过如果规划函数使用了epoch参数,那就不那么简单了:因为epoch不会被保存下来,并且每次调用fit()都会将epoch重新设为0。那么如果要在模型停止处继续训练,那么学习率可能会过大,那么可能会影响到模型权重。其中一种解决办法就是手动设置fit()方法中的initial_epoch参数,使得epoch从你想要的数字开始。对于分段常数规划,你可以使用一个类似下面的规划函数(你甚至可以定义一个更为通用的函数),然后创建一个LearningRateScheduler回调函数,并将其输入fit()方法,跟前面的方法一样:
def piecewise_constant_fn(epoch):if epoch 5:return 0.01elif epoch 15:return 0.005else:return 0.001对于性能规划方法,使用ReduceLROnPlateau回调函数即可。比如,将下述回调函数传递给fit()方法,它会在连续5个验证损失不下降的epoch后,将学习率乘以0.5。
lr_scheduler = keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)最后,tf.keras提供了一种学习率规划的替代方法:使用keras.optimizers.schedules中可用的规划来定义学习率,然后将学习率输入任意的优化器。这个方法会在每一步都更新学习率而不是每一个epoch。这里展示一下如何实现与上文相同的指数规划:
s = 20 * len(X_train) // 32 # number of steps in 20 epochs (batch size = 32)
learning_rate = keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)
optimizer = keras.optimizers.SGD(learning_rate)这就非常方便了,并且当我们保存模型时,学习率和它的规划(以及规划的状态)都会被保存下来。不过这个方法,并不是Keras的API,而只专属于tf.keras。而单循环方法,目前并没有直接可用的方法。不过实现起来也并不困难,需要创建一个定制的回调,在每次迭代中修改学习率即可(可以通过修改self.model.optimizer.lr来更新优化器的学习率)。
总而言之,指数衰减,性能规划和单循环规划都可以显著加快收敛速度。所以推荐尝试一下。下一回我们将来讲述如何防止过拟合。敬请期待啦!
更多推荐
所有评论(0)