Tensorflow基础:神经网络进一步优化
本文介绍神经网络优化过程中可能遇到的一些问题,以及解决这些问题的常用方法。共分为三部分:
- 学习率的设置
- 过拟合问题
- 滑动平均模型
学习率的设置
学习率决定了参数每次更新的幅度。如果幅度过大,那么可能导致参数在极优值的两侧来回移动;如果学习率过小,虽然能保证收敛性,但是这会大大降低优化速度。
Tensorflow提供了一种更加灵活的学习率设置方法—指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率。通过这个函数,可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。exponential_decay函数会指数级地减小学习率,它实现了一下代码的功能:
decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
tf.train.exponential_decay函数可以通过设置参数staircase选择不同的衰减方式。staircase的默认值为False,这时学习率随迭代轮数变化的趋势如下图红色线所示。当staircase的值被设置为True时,global / decay_steps会被转化为整数,这使得学习率成为了一个阶梯函数(staircase function),如下图蓝色线所示。
下面代码示范如何在Tensorflow中使用tf.train.exponential_decay函数:
global_step = tf.Variable(0)
learning_rate = tf.train.exponential_decay(
0.1, global_step, 100, 0.96, staircase=True)
#在minimize函数中传入global_step将自动更新global_step参数,从而使得学习率也得到相应更新
learning_step = tf.train.GradientDescentOptimizer(learning_rate)\
.minimize(...my loss..., global_step=global_step)
过拟合问题
所谓过拟合,指的是当一个模型过于复杂之后,它可以很好地“记忆”每一个训练数据中随机噪声的部分而忘记了要去“学习”训练数据中通用的趋势。下图展示了“过拟合”情形:
为了避免过拟合问题,一个非常常用的方法是正则化(regularization)。正则化的思想就是在损失函数中加入刻画模型复杂程度的指标 R(ω) 。常用的刻画模型复杂程度的函数 R(ω) 有两种:
- L1正则化:
R(ω)=||ω||1=∑i|ωi| - L2正则化:
R(ω)=||ω||22=∑i|ω2i|
无论是哪一种正则化方式,基本的思想都是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪声。在实践中,也可以将L1和L2正则化同时使用:
以下代码给出了一个简单的带L2正则化的损失函数定义:
w = tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w)
loss = tf.reduce_mean(tf.square(y_ - y)) +
tf.contrib.layers.l2_regularizer(lambda )(w)
在上面的程序中,loss为定义的损失函数,它由两部分组成。第一部分是均方误差函数,刻画了模型在训练数据上的表现。第二部分就是正则化,它防止模型过度模拟训练数据中的随机噪声。lambda参数表示了正则化项的权重,w为需要计算正则化损失的参数。即上面代码的loss为:
collection解决网络模型复杂情况下正则化的计算
当网络结构复杂之后,定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,这样通过变量这种方法计算损失函数就不方便了。为了解决这个问题,可以使用Tensorflow中提供的集合(collection)。
一下代码给出了通过集合计算L2正则化的损失函数的计算方法:
import tensorflow as tf
def get_weight(shape, lambda):
var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
tf.add_to_collection("losses", tf.contrib.layers.l2_regularizer(lambda )(var))
return var
mse_loss = tf.reduce_mean(tf.square(y_ - y))
tf.add_to_collection("losses", mse_loss)
loss = tf.add_n(tf.get_collection("losses"))
滑动平均模型
可以使模型在测试数据上更robust。在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度上提高最终模型在测试数据上的表现。
Tensorflow中提供了tf.ExponentialMovingAverage来实现滑动平均模型。在初始化ExponentialMovingAverage时,需要提供一个衰减率(decay)。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每一个变量会维护一个影子变量(shadow variable),这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:
shadow_variable = decay * shadow_variable + (1-decay) * variable
variable是待更新的变量,decay为衰减率。从公式可以看到,decay决定了模型更新的速度,decay越大模型越趋于稳定。
为了使得模型在训练前期可以更新得更快,ExponentialMovingAverage还提供了num_updates参数来动态设置decay的大小。如果在ExponentialMovingAverage初始化时提供了num_updates参数,那么每次使用的衰减率将是:
下面通过一段代码来解释ExponentialMovingAverage是如何被使用的:
import tensorflow as tf
v1 = tf.Variable(0, dtype=tf.float32)
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(0.99, step)
maintain_average_op = ema.apply([v1])
with tf.Session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
print (sess.run([v1, ema.average(v1)]))
sess.run(tf.assign(v1, 5))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
sess.run(tf.assign(step, 10000))
sess.run(tf.assign(v1, 10))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
sess.run(maintain_average_op)
print(sess.run([v1, ema.average(v1)]))
运行结果:
[0.0, 0.0]
[5.0, 4.5]
[10.0, 4.5549998]
[10.0, 4.6094499]