前言
最近在进行神经网络的训练的时候,经常会遇到这种情况:在多次进行训练尝试之后,为了平衡模型的训练速度和损失大小,选择了一个相对比较合适的学习率,但是训练集的损失下降到一定的程度后,就出现不再下降的情况,而是在一个范围内上下震荡。
比如:我网络中所使用的损失函数是 MSE 均方误差函数,在训练了20轮后出现了 MSE 一直在0.01到0.03之间来回震荡的情况,不能进一步下降,继而收敛。这里用画图的方式来表示,横坐标是迭代次数,纵坐标是损失函数。
出现这种情况通常可以通过适当降低学习率的方式进行改善,但是又一个问题出现了,学习率低就会出现训练时间过长的情况,感觉上好像这两者是矛盾的双方,么有办法兼顾,这个时候有一个方案出现了,它可以作为一个平衡的解决方案。
学习率衰减的基本思想就是:学习率随着训练的进行逐渐衰减,这样就可以在前期节省时间,快速得到一个最优解,又可以在后期避免震荡,让模型的训练更稳定。下面我们一起入门学习率衰减。
如何使用本教程
别有心理压力,只要跟着我一步一步来,你会发现其实并没有想像中的那么困难。当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,这里只是让你明白基本的原理和怎么应用,以后你还需要多练习,多使用,才能熟练掌握学习率衰减的使用。
除了作为入门教程之外,本文还试图成为可以在日常工作中的参考手册。就博主本人的经历来说,这个目标还是完成得不错的——你看,我自己也没能把所有的东西记下来,不是吗?
最重要的是——请给我10分钟,不会让你失望的 😃
常用的学习率衰减
学习率衰减的方法有很多种,这里简单的介绍几个常用的学习率衰减方法:
tf.train.piecewise_constant
——分段常数衰减tf.train.inverse_time_decay
——反时限衰减tf.train.polynomial_decay
——多项式衰减tf.train.exponential_decay
——指数衰减tf.train.natural_exp_decay
——自然指数衰减tf.train.cosine_decay
——余弦衰减tf.train.linear_cosine_decay
——线性余弦衰减tf.train.noisy_linear_cosine_decay
——噪声线性余弦衰减tf.train.piecewise_constant()
——指定间隔的分段常数
是不是觉得太多了,有点大脑发蒙,别担心,我们这里只重点讲一下tf.train.exponential_decay
——指数衰减。
无论是哪个学习率衰减函数都返回衰减之后的学习率,当然也包括我们的指数衰减。
指数衰减
在训练模型时,通常建议随着训练的进行逐步降低学习率,这样可以科学的降低学习率,充分利用学习率高低各自的优势进行训练,进而找到最优解。先来看一下exponential_decay
的函数参数:
tf.train.exponential_decay(
learning_rate,
global_step,
decay_steps,
decay_rate,
staircase=False,
name=None
)
参数:
learning_rate
是初始学习率,这个是可以自行设置的。decay_rate
是衰减指数,可设为任意值,但是一般都是设置成略小于1的值,比如0.98,0.9。global_step
是用于衰减计算的全局步数,类似于一个时钟。decay_steps
是衰减步数,每间隔decay_steps
步衰减一次learning_rate
值。staircase
是控制衰减方式的参数,若为True
,则以不连续的间隔衰减学习率,即阶梯型衰减学习率;若为False
,则是标准指数型衰减,即连续型衰减学习率.name
是操作的名称,默认为ExponentialDecay
,可选项。
学习率的衰减公式如下:
decayed_learning_rate = learning_rate * np.power(decay_rate, (global_step / decay_steps))
使用到的参数是什么含义,都在上面的介绍中,已经写的很清楚了。下图中灰色的线条是staircase=False
的情况,也就是连续型衰减学习率;黑色的线条就是staircase=True
的情况,也就是阶梯型衰减学习率。
图源——Tensorflow+实战Google深度学习框架书籍的4.4.1节。下载方式在博客『学习资源』索引中。
实例对比
import tensorflow as tf
import matplotlib.pyplot as plt
learning_rate = 0.01
decay_rate = 0.9
global_steps = 1000
decay_steps = 100
global_step = tf.Variable(0, trainable=False)
c = tf.train.exponential_decay(
learning_rate,
global_step,
decay_steps,
decay_rate,
staircase=True)
d = tf.train.exponential_decay(
learning_rate,
global_step,
decay_steps,
decay_rate,
staircase=False)
S_T = []
S_F = []
with tf.Session() as sess:
for i in range(global_steps):
S_T = sess.run(c, feed_dict={global_step: i})
S_T.append(T_c)
S_F = sess.run(d, feed_dict={global_step: i})
S_F.append(F_d)
plt.figure()
plt.plot(range(global_steps), S_T, 'r-', label='staircase_True')
plt.plot(range(global_steps), S_F, 'b-', label='staircase_False')
plt.title('exponential_decay')
plt.xlabel('step')
plt.ylabel('learing rate')
plt.show()
红色的线条代表
staircase=True
,首字母缩写也就是S_T;
蓝色的线条代表staircase=False
,首字母缩写也就是S_F。
推荐代码
import tensorflow as tf
import numpy as np
x = tf.placeholder(tf.float32, shape=[None, 1], name='x')
y = tf.placeholder(tf.float32, shape=[None, 1], name='y')
w = tf.Variable(tf.constant(0.0))
global_steps = tf.Variable(0, trainable=False)
# ......
# ......
learning_rate = tf.train.exponential_decay(
0.01, global_steps, 10, 0.9, staircase=False)
loss = tf.pow(w * x - y, 2)# 损失函数可以随意变换
train_step = tf.train.AdamOptimizer(
learning_rate).minimize(loss, global_step=global_steps)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range(10):
sess.run(train_step, feed_dict={x: np.linspace(1, 2, 10).reshape(
[10, 1]), y: np.linspace(1, 2, 10).reshape([10, 1])})
print(sess.run(learning_rate))
print(sess.run(global_steps))
总结
有的小伙伴比较疑惑,global_step
怎么设置呢?global_step
到底是怎么完成自动加1的???关键就在这一句代码:
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_steps)
只要有这句代码,就可以实现自动加1。不信的话就运行上述代码,可以得到如下结果:
0.009895192
1
0.009791483
2
0.009688861
3
0.009587315
4
0.009486833
5
0.009387404
6
0.009289017
7
0.00919166
8
0.009095325
9
0.009
10
如果去掉其中的 global_step=global_steps
这一句,那么 global_step
的自动加1的操作就会失效,可以自己用上面的代码试一下,这里直接给出结果了:
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
0.01
0
省略号上面是神经网络的前面定义部分,省略号下面是最后的损失函数计算部分,省略号就是网络结构部分,只要对应各个位置进行代码的重构就可以实现学习率衰减。
博主注:要特别注意,学习率衰减这个功能虽然在一些情况下,比固定学习率要好用,但是同时也有几个参数需要微调,比如
learning_rate
、decay_steps
、decay_rate
、staircase
这四个参数,这都是需要自己根据网络结构进行调整的,对应于不同网络结构和数据集,相应的情况自然就复杂起来了,但是只要是能满足于最后的最优解就是好的,要相信阳光总在风雨后 😃
如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~
回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。
想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~