人人都能懂的机器学习——训练深度神经网络——学习率规划

学习率规划

找到一个合适的学习率非常重要。如果将学习率设置过高,模型训练可能会发散。如果设置过低,那么训练虽然会收敛至最优,但是会耗费大量的时间。如果你将学习率设置得稍微有点高,那么模型在一开始训练收敛的速度会很快,但是最终会在最优点附近徘徊,无法真正收敛至最优。如果你拥有的计算资源有限,那么你可能会在收敛前就中止训练,生成一个次优解(见图2.8)。

图2.8 不同学习率的学习曲线

在之前的文章中就提到过,一个寻找合适的学习率的策略是,对模型进行几百次迭代训练,并且从一个很小的学习率开始成倍提高。然后根据学习曲线,如果有一个学习曲线开始向上发散,那么就挑选比发散学习率稍小的即可。然后再重新初始化模型,用选中的学习率进行训练。

但是我们的模型可以比恒定学习率做得更好:如果你从一个很大的学习率开始,然后在训练无法取得进展时降低它,你就可以比最优恒定学习率更快地得到一个好的结果。这里有很多不同的在训练中降低学习率的方法。有些甚至会从一个小的学习率开始,不断调高,然后再减小。这些策略被称为学习率的规划(learning schedules)。接下来我们来看一些常用的学习率策略:

  • 幂规划

将学习率设置为循环次数t的函数,如下式:
\eta(t)=\eta_{0}/(1+t/s)^{c}
初始学习率η0,幂c(通常设置为1),步数s都是超参数。学习率会在每一步训练都下降。在s步后,学习率下降为η0/2;2s步后,下降为η0/3,依次类推。可以看到,这个规划方法一开始学习率下降很快,之后会越来越慢,并且,上述三个超参数都是需要调整的。

  • 指数规划

将学习率设置为:
\eta(t)=\eta_{0}0.1^{t/s}
那么每过s步学习率就会逐渐降低至原来的十分之一。上面提到的幂规划法减小学习率的速度会逐渐降低,而指数规划方法每过s步就变成0.1倍。

  • 分段常数规划

在几个epoch以内的训练中,使用一个常数作为学习率(比如在5个epoch中η0 = 0.1),然后下几个epoch使用另一个更小的学习率(比如在下50个epoch当中η1 = 0.001),如此进行。这个方法的表现可以很出色,但是它需要大量的尝试来找出正确的学习率序列,以及每个学习率的最佳使用时长。

  • 性能规划

在每N步评估一次验证误差(就像早停方法),当发现验证误差不再下降时,就将学习率降低λ分之一。

  • 单循环规划

Leslie Smith2018年发表的论文中介绍了单循环法。与其他的方法不同,单循环法首先从提高初始学习率η0开始,随着训练的进行在训练至一半时线性增长至η1。然后在训练的后半段再次线性下降至η0,在训练的最后几个epoch,学习率会线性减小几个数量级。其中最大学习率η1的选择方法与上述的最佳学习率选择方法相同,在选好了最大学习率之后只需将初始学习率η0设置为最大的十分之一即可。

当配合使用动量优化时,也首先需要设定一个较高的动量参数(比如0.95),随后在训练的前半段线性降低至一个较低的动量值(比如0.85),之后再训练的后半段再提升至最大动量值,最后一直保持这个最大值直至训练结束。

作者Smith的实验结果表明,这种方法通常能够大大加快训练速度,提高模型性能。比如在CIFAR10图形数据集上,这种方法仅在100个epoch内就达到了91.9%的验证精度,而用普通方法需要800个epoch才达到90.3%的精度。

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.01
elif epoch < 15:
    return 0.005
else:
    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来更新优化器的学习率)。

总而言之,指数衰减,性能规划和单循环规划都可以显著加快收敛速度。所以推荐尝试一下。

下一回我们将来讲述如何防止过拟合。

敬请期待啦!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341