在深度学习模型中使用学习率调度器

到目前为止,训练神经网络和大型的深度学习网络是一个困难的优化问题。

随机梯度下降在神经网络的训练中是一个很经典的算法。随机梯度下降和学习率一起决定着神经网络中权重的更新,为了同时保证训练速度和收敛范围,目前最常用的方法就是动态衰减神经网络的学习率(可以保持固定的Batch Size,对训练机器显存的利用和规划有利)。

在本文中,我们使用Python的Keras进行实验,尝试两种不同的学习率衰减方法来训练我们的神经网络模型。

本文解决的主要问题:

  • 如何配置和评估连续衰减的学习率规划器。
  • 如何配置和评估阶梯式衰减的的学习率规划器。

让我们开始吧。

  • 作者代码运行环境:Keras 2.0.2,TensorFlow 1.0.1,Theano 0.9.0。

模型训练中的学习率规划器

在使用梯度下降算法的机器学习模型中,相比恒定的学习率,使用自适应的学习率可以提升模型的性能,缩短训练时间。

由此,学习率规划器也被称为学习率的模拟退火,自适应学习率。在本文中统一称为学习率规划器。在每批次的训练结束后,默认情况下学习率规划器将使用相同的学习率更新权重。

在训练过程中,最简单的调整学习率的方法就是让学习率随着时间的推移而不断衰减。在训练开始时,我们通常使用较大的学习率来让模型快速达到较好的性能,随后通过衰减学习率使模型权重更好地逼近最优的配置。

这就是学习率规划器可以达到更快的速度和更优的性能的原因。

下面我们将细致探讨两个易于使用的学习率规划器:

  • 学习率随训练批次连续衰减。
  • 学习率在特定的批次衰减,即阶梯式衰减学习率。

让我们使用Keras分别尝试实现一下这两种规划器。

连续衰减的学习率规划器

Keras内置的学习率规划器就是随训练批次连续衰减的。

在Keras的SGD类实现了随机梯度下降算法,在调用它时可以手动指定衰减系数decay,它的学习率规划器关于decay的数学表达式如下:

1
LearningRate = LearningRate / (1 + decay * epoch)

默认情况下,decay的值为0,学习率在训练过程中为常数。

1
2
LearningRate = 0.1 * 1/(1 + 0.0 * 1)
LearningRate = 0.1

在指定学习率之后,随着训练批次数的增加学习率会逐渐下降。

我们尝试设定初始学习率为0.1,衰减系数为0.001,得到前五个训练批次的学习率:

1
2
3
4
5
6
Epoch Learning Rate
1 0.1
2 0.0999000999
3 0.0997006985
4 0.09940249103
5 0.09900646517

我们将训练批次从1到100对应的学习率用曲线绘制出来:

连续衰减的学习率规划器

在设置衰减常数时,下面的公式可以作为参考,通常可以达到不错的效果:

1
2
3
Decay = LearningRate / Epochs
Decay = 0.1 / 100
Decay = 0.001

下面我们将演示如何在Keras中使用连续衰减的学习率规划器。

我们将以一个物理场景下的二分类问题(点击查看详情)(点击下载数据集)为例来测试我们的学习率规划器。下载到你为本文准备的工程目录后将文件名重命名为ionosphere.csv

我选用这个数据集训练神经网络的原因是它的所有输入值都是小的数值量而且使用的物理单位相同,因此我们不需要再做特征的缩放。

我们将针对这个问题构造一个小的神经网络,它只有一个隐含层,共包含34个神经元,我们还使用了线性整流函数(Relu函数)。它的输出层有一个神经元,该神经元使用sigmoid函数来输出0-1的概率值。

我们设置一个较高的学习率0.1作为初始值,设定训练的批次数(epochs)为50,根据上面的公式计算得到衰减系数为0.1/50=0.002。除此之外,在使用自适应的学习速率时,引入动量系数(可以在两次梯度下降方向相同时加速下降的速度从而更快达到收敛)也是一个不错的选择,在本例中选取0.8为动量系数。

下面给出实验所需的完整代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 连续衰减的学习率规划器实验代码
from pandas import read_csv
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
from sklearn.preprocessing import LabelEncode
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("ionosphere.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:34].astype(float)
Y = dataset[:,34]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
Y = encoder.transform(Y)
# create model
model = Sequential()
model.add(Dense(34, input_dim=34, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
epochs = 50
learning_rate = 0.1
decay_rate = learning_rate / epochs
momentum = 0.8
sgd = SGD(lr=learning_rate, momentum=momentum, decay=decay_rate, nesterov=False)
model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
# Fit the model
model.fit(X, Y, validation_split=0.33, epochs=epochs, batch_size=28, verbose=2)

该模型使用数据集中的67%作为训练集,33%作为测试集。

运行示例显示的分类准确率为99.14%。这比没有学习率衰减和动量为0时的95.69%的性能底线要高。

注:译者运行代码发现有无学习率衰减在本例中影响并不明显,在原作者的例子中甚至无学习率衰减的模型性能还要好一点,更多体现在后期准确率的稳定上,将正确率随epochs的曲线绘制出来发现有学习率衰减的模型在epochs较大时损失值和正确率曲线更为平滑,毛刺较少,即稳定性较高,译者选取了四个实验组,参数:learning_rate = 0.2, epochs = 300, decay_rate = 0 或 learning_rate / epochs, momentum = 0.8 或 0,关于如何绘制可以参考我之前的译文 使用Keras在训练深度学习模型时监控性能指标

1
2
3
4
5
6
7
8
9
10
11
12
13
...
Epoch 45/50
0s - loss: 0.0622 - acc: 0.9830 - val_loss: 0.0929 - val_acc: 0.9914
Epoch 46/50
0s - loss: 0.0695 - acc: 0.9830 - val_loss: 0.0693 - val_acc: 0.9828
Epoch 47/50
0s - loss: 0.0669 - acc: 0.9872 - val_loss: 0.0616 - val_acc: 0.9828
Epoch 48/50
0s - loss: 0.0632 - acc: 0.9830 - val_loss: 0.0824 - val_acc: 0.9914
Epoch 49/50
0s - loss: 0.0590 - acc: 0.9830 - val_loss: 0.0772 - val_acc: 0.9828
Epoch 50/50
0s - loss: 0.0592 - acc: 0.9872 - val_loss: 0.0639 - val_acc: 0.9828

阶梯式衰减的学习率规划器

在深度学习中另一种被广泛使用的学习率规划器是在特定的epochs降低学习率。

通常来说这种方法会在指定的epochs的公倍数对应的批次将学习率下调至原来的一半。举例来说,如果我们设置的初始学习率为0.1,衰减周期为10,那么,每过10个epochs学习率就会乘上0.5,前10个epoch学习率为0.1,10-19就会变为0.05,以此类推。

还是像上面一样绘制至epochs=100时的学习率图像:

阶梯式衰减的学习率规划器

在Keras中,我们可以在model.fit()方法中指定LearningRateScheduler作为回调来实现学习率的梯度下降。

LearningRateScheduler的回调允许我们自定义一个回调函数来根据epochs返回对应的学习率,输出的学习率将覆盖随机梯度下降类SGD中指定的学习率。

我们继续使用上面的数据集来进行实验,为了实现阶梯式下降,需要定义一个新的step_decay()函数来实现学习率更新公式:

1
LearningRate = InitialLearningRate * DropRate^floor(Epoch / EpochDrop)

其中InitialLearningRate是初始学习速率;DropRate代表对应点学习率衰减为原来的多少倍;Epoch是当前训练批次数;EpochDrop代表多久改变一次学习速率 。

注意代码中将SGD类中的学习率设置为0的原因是我们设定的LearningRateScheduler回调中的更新公式输出的值会覆盖SGD类设定的学习率。在这个例子中,你也可以尝试加入动量系数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 阶梯式下降的学习率衰减计划器
import pandas
from pandas import read_csv
import numpy
import math
from keras.models import Sequential
from keras.layers import Dense
from keras.optimizers import SGD
from sklearn.preprocessing import LabelEncoder
from keras.callbacks import LearningRateScheduler
# learning rate schedule
def step_decay(epoch):
initial_lrate = 0.1
drop = 0.5
epochs_drop = 10.0
lrate = initial_lrate * math.pow(drop, math.floor((1+epoch)/epochs_drop))
return lrate
# fix random seed for reproducibility
seed = 7
numpy.random.seed(seed)
# load dataset
dataframe = read_csv("ionosphere.csv", header=None)
dataset = dataframe.values
# split into input (X) and output (Y) variables
X = dataset[:,0:34].astype(float)
Y = dataset[:,34]
# encode class values as integers
encoder = LabelEncoder()
encoder.fit(Y)
Y = encoder.transform(Y)
# create model
model = Sequential()
model.add(Dense(34, input_dim=34, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
# Compile model
sgd = SGD(lr=0.0, momentum=0.9, decay=0.0, nesterov=False)
model.compile(loss='binary_crossentropy', optimizer=sgd, metrics=['accuracy'])
# learning schedule callback
lrate = LearningRateScheduler(step_decay)
callbacks_list = [lrate]
# Fit the model
model.fit(X, Y, validation_split=0.33, epochs=50, batch_size=28, callbacks=callbacks_list, verbose=2)

运行代码,可以从输出信息中看到测试集上的准确率最后可以达到99.14%的水平,满足性能要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
...
Epoch 45/50
0s - loss: 0.0546 - acc: 0.9830 - val_loss: 0.0634 - val_acc: 0.9914
Epoch 46/50
0s - loss: 0.0544 - acc: 0.9872 - val_loss: 0.0638 - val_acc: 0.9914
Epoch 47/50
0s - loss: 0.0553 - acc: 0.9872 - val_loss: 0.0696 - val_acc: 0.9914
Epoch 48/50
0s - loss: 0.0537 - acc: 0.9872 - val_loss: 0.0675 - val_acc: 0.9914
Epoch 49/50
0s - loss: 0.0537 - acc: 0.9872 - val_loss: 0.0636 - val_acc: 0.9914
Epoch 50/50
0s - loss: 0.0534 - acc: 0.9872 - val_loss: 0.0679 - val_acc: 0.9914

学习率计划器常用技巧

本节将列出神经网络训练过程中常用的技巧:

  • 提高初始学习率。因为学习率一般会随着训练批次的增加而降低,所以不妨让学习率从一个较高的水平开始下降。较大的学习率可以使模型在初始训练时权重有更大的变化,有助于后续低学习率调优时收敛至更优的权重范围。
  • 使用大的动量系数。使用大的动量系数可以保证在你的学习率衰减得比较小时,优化算法还可以使模型权重在正确的方向上以较快的速度收敛。
  • 尝试不同的学习率计划器。因为现在还没有理论明确地指出什么情况下应该使用什么样的学习率规划器,所以你需要尝试各种不同的配置来寻找最适合你当前问题的计划器配置。你可以按照指数规律划分学习率规划器的参数,也可以根据模型在训练集/测试集上响应的结果自适应地调整学习率规划器参数。

回顾总结

本片文章探究了神经网络训练过程中的学习率规划器。

本文解决的主要问题:

  • 如何配置和评估连续衰减的学习率规划器。
  • 如何配置和评估阶梯式衰减的的学习率规划器。
您的打赏是对我最大的鼓励!