深层神经网络之四:神经网络进一步优化

1.学习率的设置

学习率设置过大时参数将不会收敛,设定过小时虽然能保证收敛性,但是会大大降低优化速度。为解决设定学习率的问题,TensorFlow提供了一种更加灵活的学习率设置方法——指数衰减法。tf.train.exponential_decay函数实现了指数衰减学习率。通过这个函数,可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减小学习率,使得模型在训练后期更加稳定。exponential_decay函数会指数级地减小学习率,它实现了以下代码的功能:

decayed_learning_rate=\
learning_rate*decay_rate^(global_step/decay_step)

其中decayed_learning_rate为每一轮优化时使用的学习率,learning_rate为事先设定的初始学习率,decay_rate为衰减系数,decay_steps为衰减速度。下图显示了随着迭代轮数的增加,学习率逐步降低的过程。
在这里插入图片描述
tf.train.exponential_decay函数可以通过设置参数staircase选择不同的衰减方式。staircase的默认值为False,这时学习率随迭代轮数变化的趋势如上图灰色曲线所示。当staircase的值为True时,global_step / decay_steps会被转化为整数。使得学习率成为一个阶梯函数(staircase function)。上图中黑色曲线显示了阶梯状的学习率。在这样的设置下,decay_steps通常代表了完整地使用一遍训练数据所需要的迭代轮数。这个迭代轮数也就是总训练样本数除以每一个batch中的训练样本数。这种设置的常用场景是每次完整地过完一遍训练数据,学习率就减小一次。这样可以使得训练数据集中的所有数据对模型训练有相等的作用。当使用连续的指数衰减学习率时,不同的训练数据有不同的学习率,而当学习率减小时,对应的训练数据对模型训练结果的影响也就小了。下面代码示范了如何在TensorFlow中使用tf.train.exponential_decay函数。

global_step=tf.Variable(0)

#通过exponential_decay函数生成学习率
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)

上面这段代码中设定了初始学习率为0.1,因为指定了staircase=True,所以每训练100轮后学习率乘以0.96.一般来说学习率、衰减系数和衰减速度都是根据经验设置的。而且损失函数下降的速度和迭代后总损失的大小没有必然的联系。也就是说并不能通过前几轮损失函数下降的速度来比较不同神经网络的效果。

2.过拟合问题

所谓过拟合,指的是一个模型过为复杂之后,它可以很好地“记忆”每一个训练数据中随机噪音的部分而忘记了要去“学习”训练数据中的通用趋势。
在这里插入图片描述
为避免过拟合问题,一个非常常用的方法是正则化。正则化的思想就是在损失函数中加入刻画模型复杂程度的指标。这里不再对正则化的数学概念赘述。
TensorFlow可以优化任意形式的损失函数,所以TensorFlow自然也可以优化待正则化的损失函数。以下代码给出了一个简单的带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为定义的损失函数,它由两部分组成:第一部分是均方误差损失函数,它刻画了模型在训练数据上的表现;第二个部分是正则化,它防止模型过度模拟训练数据中的随机噪音。w为需要计算正则化损失的参数。Tensorflow提供了tf.contrib.layers.l2_regularizer函数,它可以返回一个函数,这个函数可以计算一个给定参数的L2正则化项的值。类似的,tf.contrib.layers.l1_regularizer可以计算L1正则化项的值。以下代码给出了使用这两个函数的样例:

weights=tf.constant([[1.0,-2.0],[-3.0,4.0]]) 
with tf.Session() as sess:
	print(sess.run(tf.contrib.layers.l1_regularizer(.5)(weights)))
	print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights)))

在简单的神经网络中,这样的方式可以很好地计算带正则化的损失函数。但当神经网络的参数增多后,这样的方式首先可能导致损失函数loss的定义很长,可读性差而且容易出错。但更主要的是,当网络结构复杂之后定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,这样通过变量这种方式计算损失函数就不方便了。为解决这个问题,可以使用TensorFlow提供的集合(collection),它可以在一个计算图(tf.Graph)中保存一组实体(比如张量)。以下代码给出了通过集合计算一个5层神经网络带L2正则化的损失函数的计算方法。

#coding=utf-8
import tensorflow as tf 

#获取一层神经网络边上的权重,并将这个权重的L2正则化损失加入名称为‘losses’的集合中
def get_weight(shape,lamda):
	#生成一个变量
	var=tf.Variable(tf.random_normal(shape),dtype=tf.float32)
	#add_to_collection函数将这个新生成变量的L2正则化损失项加入集合
	#这个函数的第一个参数'loaaes'是集合的名字,第二个参数是要加入这个集合的内容
	tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(lamda)(var))
	#返回生成的变量
	return var

x=tf.placeholder(tf.float32,shape=(None,2))
y_=tf.placeholder(tf.float32,shape=(None,1))
batch_size=8
#定义了每一层网络中节点的个数
layer_dimension=[2,10,10,10,1]
#神经网络的层数
n_layers=len(layer_dimension)

#这个变量维护前向传播时最深层的节点,开始的时候是输入层
cur_layer=x
#当前层的节点个数
in_dimension=layer_dimension[0]

#通过一个循环来生成5层全连接的神经网络结构
for i in range(1,n_layers):
	#layer_dimension[i]为下一层的节点个数
	out_dimension=layer_dimension[i]
	#生成当前层权重的变量,并将这个变量的L2正则化损失加入计算图上的集合
	weight=get_weight([in_dimension,out_dimension],0.001)
	bias=tf.Variable(tf.constant(0.1,shape=[out_dimension]))
	#使用ReLU激活函数
	cur_layer=tf.nn.relu(tf.matmul(cur_layer,weight)+bias)
	#进入下一层之前将下一层的节点个数更新为当前层节点个数
	in_dimension=layer_dimension[i]

#在定义神经网络前向传播的同时一斤将所有的L2正则化损失加入了图上的集合
#这里只需要计算刻画模型在训练数据上表现的损失函数
mse_loss=tf.reduce_mean(tf.square(y_-cur_layer))

#将均方误差损失函数加入损失集合
tf.add_to_collection('losses',mse_loss)

#get_collection返回一个列表,这个列表是所有这个集合中的元素
#在这个样例中,这些元素就是损失函数的不同部分,将它们加起来就可以得到最终的损失函数
loss=tf.add_n(tf.get_collection('losses'))

通过使用集合的方法,在网络结构比较复杂的情况下可以使代码可读性更高。

3.滑动平均模型

滑动平均模型可以使模型在测试数据上更健壮。在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最中国模型在测试数据上的表现。
在TensorFlow中提供了tf.ExponentialMovingAverage来实现滑动平均模型。在初始化ExponentialMovingAverage时,需提供一个衰减率(decay)。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每个变量会维护一个影子变量(shadow variable),这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:
shadow_variable = decay x shadow_variable + (1 - decay) x variable
其中shadow_variable为影子变量,variable为待更新的变量,decay为衰减率。decay决定了模型更新的速度,decay越大模型越趋于稳定。在实际应用中,decay一般会设置成非常接近1的数(比如0.999或0.9999)。为了使得模型在训练前期可以更新得更快,ExponentialMovingAverage还提供了num_updates参数来动态设置decay的大小。如果在ExponentialMovingAverage初始化时提供了num_updates参数,那么每次使用的衰减率将是:
在这里插入图片描述
下面通过一段代码来解释ExponentialMovingAverage是如何被使用的。

#coding=utf-8

import tensorflow as tf 

#定义一个变量用于计算滑动平均,这个变量的初始值是0
#注意这里手动指定了变量的类型为tf.float32
#因为所有需要计算滑动平均的变量必须是实数型
v1=tf.Variable(0,dtype=tf.float32)
#这里step变量模拟神经网络中迭代的轮数,可以用于动态控制衰减率
step=tf.Variable(0,trainable=False)

#定义一个滑动平均的类(class),初始化时给定了衰减率(0.99)和控制衰减率的变量step
ema=tf.train.ExponentialMovingAverage(0.99,step)
#定义一个更新变量滑动平均的操作,这里需要给定一个列表
#每次执行这个操作时,这个列表中的变量都会被更新
maintain_averages_op=ema.apply([v1])

with tf.Session() as sess:
	#初始化所有变量
	init_op=tf.initialize_all_variables()
	sess.run(init_op)

	#通过ema.average(v1)获取滑动平均之后变量的取值。
	#在初始化之后变量v1的值和v1的滑动平均都为0
	print(sess.run([v1,ema.average(v1)]))
	#输出[0.0, 0.0]

	#更新变量v1的值到5
	sess.run(tf.assign(v1,5))
	#更新v1的滑动平均值,衰减率为min{0.99,(1+step)/(10+step)=0.1}=0.1
	#所以v1的滑动平均会被更新为0.1x0.9x5=4.5
	sess.run(maintain_averages_op)
	print(sess.run([v1,ema.average(v1)]))
	#输出[5.0, 4.5]

	#更新step的值为10000
	sess.run(tf.assign(step,10000))
	#更新v1的值为10
	sess.run(tf.assign(v1,10))
	#更新v1的滑动平均值,衰减率为min{0.99,(1+step)/(10+step)0.999}=0.99
	#所以v1的滑动平均会被更新为0.99x4.5+0.01x10=4.555
	sess.run(maintain_averages_op)
	print(sess.run([v1,ema.average(v1)]))
	#输出[10.0, 4.5549998]

	#再次更新滑动平均值,得到新的滑动平均值为0.99x4.555+0.01x10=4.60945
	sess.run(maintain_averages_op)
	print(sess.run([v1,ema.average(v1)]))
	#输出[10.0, 4.6094499]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值