tensorflow 基础学习四:神经网络优化算法
指数衰减法:
公式代码如下:
decayed_learning_rate=learning_rate*decay_rate^(global_step/decay_steps)
变量含义:
decayed_learning_rate:每一轮优化时使用的学习率
learning_rate:初始学习率
decay_rate:衰减系数
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(loss, global_step=global_step)
上面代码指定初始学习率为0.1,每训练100轮后学习率乘以0.96。
过拟合问题:
引入正则化(regularization)项,常用的有L1和L2正则化,具体公式如下:
L1正则化:$R(w)=\left \| w \right \|_{1}=\sum_{i}\left | w_{i} \right |$
L2正则化:$R(w)=\left \| w \right \|_{2}^{2}=\sum_{i}\left | w_{i}^{2} \right |$
两种正则化之间的区别,L1正则化会使参数变得更稀疏,而L2正则化不会。所谓参数变得更稀疏是指会有更多的参数变为0,这样可以达到类似特征提取的功能。L2正则化之所以不会让参数变稀疏,是因为,当参数变得很小时,比如为0.01时,这个参数的平方基本可以忽略不计,于是模型不会进一步将这个参数调整为0。其次,L1正则化的公式不可导,而L2可导。实践中,可以同时使用L1和L2正则化:
$R(w)=\sum_{i}\alpha\left | w_{i} \right |+(1-\alpha)w_{i}^{2}$
以下代码给出了一个简单的带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)
类似的,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))) # 输出5.0 (|1.0|+|-2.0|+|-3.0|+|4.0|)*0.5=5 print(sess.run(tf.contrib.layers.l2_regularizer(.5)(weights))) # 输出7.5 (12+(-2)2+(-3)2+42)/2*0.5=7.5
tensorflow会将L2的正则化损失值除以2使得求导过程更加简洁。
在简单的神经网络中,这种方式可以很好的计算带正则化的损失函数。但当神经网络参数增多后,这种方式首先会导致损失函数loss的定义很长,可读性差且容易出错。更重要的是,当网络结构变复杂之后定义网络结构的部分和计算损失函数的部分可能不在同一个函数中,因此通过这种方式计算损失函数就不方便了。为了解决这个问题,可以使用tensorflow中提供的集合(collection)。以下代码给出了通过集合计算一个5层神经网络带L2正则化的损失函数的计算方法。
import tensorflow as tf # 获取一层神经网络边上的权重,并将这个权重的L2正则化损失加入到名称为'losses'的集合中 def get_weight(shape,lambda): # 生成一个变量 var=tf.Variable(tf.random_normal(shape),dtype=tf.float32) # add_to_collection函数将这个新生成变量的L2正则化损失项加入集合 # 这个函数的第一个参数'losses'是集合的名字,第二个参数是要加入这个集合的内容。 tf.add_to_collection('losses',tf.contrib.layers.l2_regularizer(lambda)(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'))
滑动平均模型
在采用随机梯度下降算法训练神经网络时,使用滑动平均模型在很多应用中都可以在一定程度提高最终模型在测试数据上的表现。
在tensorflow中提供了tf.train.ExponentialMovingAverage函数来实现滑动平均模型。在初始化ExponentialMovingAverage时,需要提供一个衰减率(decay)。这个衰减率将用于控制模型更新的速度。ExponentialMovingAverage对每一个变量会维护一个影子变量(shadow variable),这个影子变量的初始值就是相应变量的初始值,而每次运行变量更新时,影子变量的值会更新为:
shadow_variable=decay*shadow_variable+(1-decay)*variable
其中shadow_variable为影子变量,variable为待更新的变量,decay为衰减率。从公式中可以看出,decay决定了模型更新的速度,decay越大模型越趋于稳定。在实际应用中,decay一般会设置成非常接近1的数(比如0.999或0.9999)。为了使得模型在训练前期可以更新得更快,ExponentialMovingAverage还提供了num_updates参数来动态设置decay的大小。如果在ExponentialMovingAverage初始化时提供了num_updates参数,那么每次使用的衰减率将为:
$min\left \{ decay,\frac{1+num\_updates}{10+num\_updates} \right \}$
以下代码详细讲解了ExponentialMovingAverage的使用方法:
import tensorflow as tf # 定义一个变量用于计算滑动平均,这个变量的初始值为0。 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.global_variables_initializer() 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.1*0+0.9*5=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.99*4.5+0.01*10=4.555 sess.run(maintain_averages_op) print(sess.run([v1,ema.average(v1)])) # 输出[10.0, 4.5549998] # 再次更新滑动平均值,得到的新滑动平均值为0.99*4.555+0.01*10=4.60945 sess.run(maintain_averages_op) print(sess.run([v1,ema.average(v1)])) # 输出[10.0, 4.6094499]