引入
随机梯度下降(SGD)算法是现如今使用较为广泛的优化算法(此处的SGD指的是小批量梯度下降)。具体执行方法是不断迭代直到满足停止准则,在每次的迭代中取小批量训练集,计算损失函数对于权重参数的梯度,并以一定学习率执行权重更新。
《深度学习》一书中指出学习率( ε \varepsilon ε)是SGD算法中的关键参数。并且在我之前所编写的深度学习程序中,学习率一直是一个固定的变量,但通过阅读才发现自己多么无知,原来超参数也应当是变化的并且在学习算法的迭代中以一定策略不断更新!该书还指出
学习率可通过实验和误差来选取,通常最好的选择方法是检测目标函数随时间变化的学习曲线。与其说是科学,这更像是一门艺术,我们应当谨慎考虑该问题!
真是长见识,原来超参数的选择是艺术!不是随随便便定个小数就可以的。并且该书给出了线性衰减更新策略,第 k k k步迭代的学习率记为 ε k \varepsilon_k εk,在实际执行中一般会线性衰减学习率直到第 η \eta η次迭代:
ε k = ( 1 − α ) ε 0 + α ε η = ( 1 − k η ) ε 0 + k η ε η \varepsilon_k=(1-\alpha)\varepsilon_0+\alpha\varepsilon_\eta=(1-\frac{k}{\eta})\varepsilon_0+\frac{k}{\eta}\varepsilon_\eta εk=(1−α)ε0+αεη=(1−ηk)ε0+ηkεη
其中 α = k η \alpha=\frac{k}{\eta} α=ηk,在 η \eta η步迭代以后,一般使 ε \varepsilon ε保持常数。
实践
理论就是这理论,为了进一步实践,我们利用tensorflow的内置api来进行学习率的衰减更新!
分段常数衰减
分段常数衰减是在事先定义好的训练次数区间上,设置不同的学习率常数。刚开始学习率大一些,之后越来越小,区间的设置需要根据样本量调整,一般样本量越大区间间隔应该越小。
tf中定义了train.piecewise_constant() 函数,实现了学习率的分段常数衰减功能。
tf.train.piecewise_constant(x, boundaries, values, name=None)
- x: 标量,指代训练次数
- boundaries: 学习率参数应用区间列表
- values: 学习率列表,values的长度比boundaries的长度多一个
- name: 操作的名称
实现如下:
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
boundaries = [10, 20, 30]
learing_rates = [0.1, 0.07, 0.025, 0.0125]
N = 40
rates = []
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(N):
learning = tf.train.piecewise_constant(epoch, boundaries=boundaries, values=learing_rates)
rate = sess.run([learning])
rates.append(rate)
plt.plot(np.linspace(0, 40, 40), rates)
显示的图像如下:
可以看出其衰减呈线性。
指数衰减
指数衰减是比较常用的衰减方法,学习率是跟当前的训练轮次指数相关的。
tf中实现指数衰减的函数是 train.exponential_decay()。
tf.train.exponential_decay(
learning_rate,
global_step,
decay_steps,
decay_rate,
staircase=False,
name=None
)
- learning_rate:初始学习率
- global_step:当前训练轮次,epoch
- decay_step:定义衰减周期,跟参数staircase配合,可以在decay_step个训练轮次内保持学习率不变
- decay_rate:衰减率系数
- staircase:定义是否是阶梯型衰减,还是连续衰减,默认是False,即连续衰减(标准的指数型衰减)
- name:操作名称
实现如下:
N = 200
y = []
z = []
epoch = tf.Variable(0, name='global_step', trainable=False)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(N):
# 阶梯型衰减
learning_rate1 = tf.train.exponential_decay(learning_rate=0.5,
global_step=epoch,
decay_steps=10,
decay_rate=0.9,
staircase=True)
# 标准指数型衰减
learning_rate2 = tf.train.exponential_decay(learning_rate=0.5,
global_step=epoch,
decay_steps=10,
decay_rate=0.9,
staircase=False)
lr1 = sess.run(learning_rate1)
lr2 = sess.run(learning_rate2)
y.append(lr1)
z.append(lr2)
plt.plot(range(N), y)
plt.plot(range(N), z)
结果如下图所示:
应用
我们利用上面的方法来跑一下MNIST数据集。
import tensorflow as tf
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
# 载入数据集
data = input_data.read_data_sets('MNIST_data/', one_hot=True)
def weight_variable(shape):
#权重的初始化
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
def bias_variable(shape):
#偏置项的初始化
initial = tf.constant(0.5, shape=shape)
return tf.Variable(initial)
# 定义神经网络结构
def bp_nn(x, keep_prob):
with tf.name_scope('hidden_layer'):
w1 = weight_variable([784, 500])
b1 = bias_variable([500])
h1 = tf.nn.relu(tf.matmul(x, w1) + b1)
L1 = tf.nn.dropout(h1, keep_prob=keep_prob)
with tf.name_scope('output_layer'):
w2 = weight_variable([500, 10])
b2 = bias_variable([10])
y = tf.nn.softmax(tf.matmul(L1, w2) + b2)
return y
x = tf.placeholder(dtype=tf.float32, shape=[None, 784])
# dropout随机断开的概率
keep_prob = tf.placeholder(dtype=tf.float32)
# 标签
y_ = tf.placeholder(dtype=tf.float32, shape=[None, 10])
# 最终预测值
y = bp_nn(x, keep_prob)
# 定义交叉熵损失函数
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
#学习率衰减相关参数
epoch = tf.Variable(0, name='global_step', trainable=False)
learning_rate = tf.train.exponential_decay(learning_rate=0.001, global_step=epoch,
decay_steps=10,
decay_rate=0.9,
staircase=True)
# 梯度下降优化目标函数
train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(cross_entropy)
# 这是我们算法运行过程中需要判断的模型准确率
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(10000):
x_batch, y_batch = data.train.next_batch(100)
sess.run(train_step, feed_dict={x: x_batch, y_: y_batch, keep_prob: 0.8,})
if epoch % 1000 == 0:
train_accuracy = sess.run(accuracy, feed_dict={x: x_batch, y_:y_batch, keep_prob:1.0})
print('精度:' + str(train_accuracy))
print('-----------------------载入测试集----------------------')
acc = []
for i in range(1000):
batch = data.test.next_batch(100)
test_accuracy = sess.run(accuracy, feed_dict={x: batch[0], y_:batch[1], keep_prob:1.0})
acc.append(test_accuracy)
mean_accuracy = np.mean(acc)
print(mean_accuracy)
精度0.2
精度0.97
精度1.0
精度0.98
精度0.99
精度0.99
精度0.98
精度1.0
精度1.0
精度0.99
-----------------------载入测试集----------------------
0.9776
最终测试集表现不错!