Tensorflow学习--最佳的深度学习实践案例

2018-1-26Task——Chapter5吃透MNIST识别例子

目的:验证神经网络的优化方法,使用MNIST手写数字识别神经网络进行验证。
目录:
·5.1 Tensorflow处理MNIST手写数字识别数据集
·5.2 对比神经网络设计和参数优化的不同方法
·5.3 介绍Tensorflow的变量重用问题和变量的命名空间问题
·5.4 介绍神经网络模型的持久化问题—直接使用训练好的模型
·5.5 完整的Tensorflow解决MNIST问题代码


5.1MNIST数据处理

MNIST数据集包含了60000张图片作为训练数据(55000张作为训练数据,5000张作为cross-validation的数据),10000张作为测试数据。每一张图片的大小均为28×28,且数字都会出现在图片的正中间。以下是Tensorflow处理MNIST数据的代码:

import tensorflow as  tf 
from tensorflow.examples.tutorials.mnist import input_data

#载入MNIST数据集,如果指定地址/path/to/MNIST_data 下没有已经下好的数据,
#那么Tensorflow会自动从表5-1的地址下载数据
mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)

#打印Training data size:55000
print("Training data size:",mnist.train.num_examples)

#打印Validation data siz:5000
print("Validation data size:",mnist.validation.num_examples)

#打印Testing data size. 10000
print("Testing data size:",mnist.test.num_examples)

#打印Example training data:
print("Example training data:\n",mnist.train.images[0])

#打印Example Training data label:
print("Example training data label:\n", mnist.train.labels[0])

input_data.read_data_sets函数生成的类会自动将MNIST的数据集划分为train、validation、test三个数据集。对应的数据集分别有5.5w,5k,1w张图片。处理后的每一张图片是一个长度为28*28=784的一维数组。数组中的每一个元素对应像素点中的每一个位置的数字(转换为[0,1]之间的灰度值)。外,input_data.read_data_sets

函数生成的类还提供了mnist.train.next_batch函数可以从训练数据中读取一小部分作为一个训练batch,便于使用批量(batch)梯度下降法。以下代码显示了此功能:

batch_size = 100

xs,ys = mnist.train.next_batch(batch_size)
#从train集合中选取batch_size个数据
#其中X为测试数据集,Y为标签集
print("X shape:",xs.shape)
#输出 X shape:(100,784)  

print("Y shape:",ys.shape)
#输出 Y Shape:(100,10)

5.2.1 Tensorflow训练神经网络模型

主要运用到以下优化算法:

①激活函数实现神经网络模型的去线性化

②使用一个或多个隐藏层使得神经网络变得更深

③使用指数衰减法设置学习率

④使用L2正则化来避免过拟合

⑤使用滑动平均模型使得最终的模型更加健壮

以下是直接将以上算法进行整合的代码(暂时不考虑模型存储,命名空间,变量重用问题)

Tensorflow的常用函数可以看这里

import tensorflow as  tf 
from tensorflow.examples.tutorials.mnist import input_data

#载入MNIST数据集,如果指定地址/path/to/MNIST_data 下没有已经下好的数据,
#那么Tensorflow会自动从表5-1的地址下载数据
# mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)

#打印Training data size:55000
# print("Training data size:",mnist.train.num_examples)

# #打印Validation data siz:5000
# print("Validation data size:",mnist.validation.num_examples)

# #打印Testing data size. 10000
# print("Testing data size:",mnist.test.num_examples)

# #打印Example training data:
# print("Example training data:\n",mnist.train.images[0])

# #打印Example Training data label:
# print("Example training data label:\n", mnist.train.labels[0])

# batch_size = 100
# xs,ys = mnist.train.next_batch(batch_size)
#从train集合中选取batch_size个数据
# print("X shape:",xs.shape)
# print("Y shape:",ys.shape)
#其中X为测试数据集,Y为标签集

#MNIST数据集相关常数
INPUT_NODE = 784    #输入层的节点数。对于MNIST数据集,这个就等于图片的像素
OUTPUT_NODE = 10   #输出层的节点数。这个等于类别的数目,因为MNIST数据集中需要区分0~9这十个数字,所以这里输出层的节点数为10.

#配置神经网络的参数
LAYER1_NODE = 500   #隐藏层的节点数。这里使用只有一个隐藏层的网络作为样例,这个隐藏层有500个结点

BATCH_SIZE = 100    #一个训练batch中的训练数据个数。数字越小时,训练过程越接近随机梯度下降;数字越大,训练越接近梯度下降

LEARNING_RATE_BASE = 0.8    #基础的学习率
LEARNING_RATE_DECAY =0.99   #学习率的衰减率
REGULARIZATION_RATE =0.0001 #描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS =30000       #训练的轮数
MOVING_AVERAGE_DECAY = 0.99 #滑动平均衰减率

#一个辅助函数,给定神经网络的输入和所有参数,计算神经网络的前向传播结果。在这里
#定义了一个使用ReLU激活函数的三层全连接神经网络。通过加入隐藏层实现了多层网络的结构
#通过ReLU激活函数实现了去线性化。在这个函数中也支持传入用于计算参数平均值的类,
#这样方便在测试时使用滑动平均模型
def  inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
	#当没有提供滑动平均类时,直接使用参数当前的取值
	if avg_class == None:
	#计算隐藏层的前向传播结果
		layer1 = tf.nn.relu(tf.matmul(input_tensor,weights1)+biases1)

	#计算输出层的前向传播结果。因为在计算损失时会一并计算softmax函数
	#所以这里不需要加入激活函数。而且不加入softmax不影响预测结果。
	#因为预测时使用的是不同类别对应结点输出值的不相对同大小,有没有softmax层
	#对最后分类结果的计算没有影响。于是在计算整个神经网络的前向传播时可以
	#不加入最后的softmax层。
		return tf.matmul(layer1,weights2)+biases2

	else:
		#首先使用avg_class.average函数来计算得出变量W和b的滑动平均值,
		#然后在计算相应的神经网络前向传播结果
		layer1 = tf.nn.relu(tf.matmul(input_tensor,avg_class.average(weights1)) + 
			avg_class.average(biases1))
		return tf.matmul(layer1,avg_class.average(weights2)) +avg_class.average(biases2)


#训练模型的过程
def train(mnist):
	x = tf.placeholder(dtype=tf.float32,shape=[None,INPUT_NODE],name = 'x-input')
	y_ = tf.placeholder(dtype=tf.float32,shape=[None,OUTPUT_NODE],name = 'y-input')

	#生成隐藏层的参数
	#tf.truncated_mormal生成的随机值服从正态分布,且每个随机值偏离均值不超过2个标           #准差,stddev=0.1,即标准差为0.1
	weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE,LAYER1_NODE],stddev=0.1))
	biases1 = tf.Variable(tf.constant(0.1,shape=[1,LAYER1_NODE]))
	#注意此处的shape也设为[1,LAYER1_NODE]
	#生成输出层的参数
	weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE,OUTPUT_NODE],stddev = 0.1))
	biases2 = tf.Variable(tf.constant(0.1,shape=[1,OUTPUT_NODE]))

	#计算在当前参数下神经网络前向传播的结果,这里给出的用于计算滑动平均的类为None,
	#所以函数不会使用参数的滑动平均值
	y = inference(x,None,weights1,biases1,weights2,biases2)

	#定义存储训练轮数的变量。这个变量不需要计算滑动平均值,所以这里指定这个变量为#   #不可训练的变量(trainable=False)
	#Tensorflow训练神经网络时,一般会将代表训轮数的变脸指定为不可训练的参数。

	#将代表训练轮数的变量为不可变的参数
	global_step = tf.Variable(0,trainable=False)

	#给定_滑动平均衰减率_和_训练轮数_的变量,初始化滑动平均类(见第4章)
	#给定训练轮数的变量可以训练早期变量的更新速度

	#variable_averages是一个滑动平均类的实例,global_step用于更新影子变量
	variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY,global_step)

	#在所有代表神经网络参数的变量上使用滑动平均,其他辅助变量就不需要了。
	#tf.trainable.variable返回的就是图上存储可训练变量的集合collection
	variables_averages_op = variable_averages.apply(tf.trainable_variables())

	#计算使用滑动平均之后前向传播的结果。滑动平均不会改变变量本身的取值,而会维护    #一个影子变量来记录
	#其滑动平均值。所以当需要使用这个滑动平均值时,需要明确调用average()函数。
	average_y = inference(x,variable_averages,weights1,biases1,weights2,biases2)

	#计算交叉熵作为刻画预测值和真实值之间差距的损失函数,这里使用Tensorflow中提供
	#sparse_softmax_cross_entropy_with_logits函数来计算交叉熵。当分类问题只有一个   #正确答案时,可以使用这个函数来加速交叉熵的计算。MNIST问题的图片中只包含了0~9   #中的一个数字,所以可以使用这一个函数来计算交叉损失。这个函数的第一个参数是神   #经网络不包括softmax的前向传播结果第二个是训练数据的正确答案。因为标准答案是一   #个长度为10的一维数组,而该函数需要提供的是
	#一个正确答案的数字,所以需要使用tf.argmax(y_,1)函数来得到正确答案的编号
	cross_entropy = \ tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))
	 
	#计算在当前batch中所有样例的交叉熵平均值。
	cross_entropy_mean = tf.reduce_mean(cross_entropy)

	#定义计算L2正则化损失函数,REGULARIZATION_RATE表示模型复杂损失在总损失中的比例
	#正则化的思想,在损失函数中加入刻画模型复杂程度的指标
	regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)


	#计算模型的正则化损失一般只计算神经网路权上的正则化损失,而不需要计算偏置顶
	regularization = regularizer(weights1) + regularizer(weights2)

	#总损失等于交叉熵损失和正则化损失之和
	loss = cross_entropy_mean + regularization

	#设置指数衰减的学习率
	learning_rate = tf.train.exponential_decay(
		LEARNING_RATE_BASE,     #基础的学习率,随着迭代的进行,更新变量时使用          #的学习率在这个基础上递减
		global_step,		#当前迭代的轮数
		mnist.train.num_examples/BATCH_SIZE, #过完所有训练数据需要迭代的次数
		LEARNING_RATE_DECAY		     #学习率的衰减速度
		)

	#使用tf.train.GradientDescentOptimizer()函数优化算法来优化损失函数。
	#这里的损失函数包含了交叉熵损失和L2正则化损失
	train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
	#参数global_step代表全局步数,比如在多少步该进行什么操作,现在神经网络训练到多   #少轮等等,类似于一个钟表。
	#损失函数优化器的minimize()中global_step=global_steps能够提供global_step自动加   #一的操作。
	#从0开始,系统会自动更新这个值,逐次加一累加

	#在训练神经网络模型时,每过一遍数据既需要通过反向传播来更新神经网络中的参数,
	#又要更新每一个参数的滑动平均值。为了一次完成多个操作,Tensorflow提供了
	#tf.control_dependencies和tf.group两种机制,下面两行代码和
	#tf.control_dependencies([train_step,variable_averages_op])是等价的
	#tf.control_dependencies()控制计算流图的,给图中的某些计算指定顺序
	with tf.control_dependencies([train_step,variables_averages_op]):
		train_op = tf.no_op(name='train')

	#检测使用了滑动平静模型的神经网络前向传播是否正确。tf.argmax(average_y,1)
	#计算每一个样例的预测答案。其中average_y是一个batch_size*10的二维数组,每
	#一行表示案例向前传播的结果。tf.argmax的第二个参数为1,表示选取最大值的
	#操作只在第一个维度上进行(x轴上),也就是说只在每一行选取最大值对应的下标
	#于是得到的结果是一个长度为batch的一维数组,这个一维数组中的值就表示了每
	#一个数字对应的样例识别的结果.tf.equal()判断每个Tensor的每一维度是否相同
	#如果相等返回True,否则返回False.
	correct_prrediction = tf.equal(tf.argmax(average_y,1),tf.argmax(y_,1))

	#这个运算首先将一个布尔型的值转换为实数型,然后计算平均值。这一个平均值
	#就代表模型在这一组数据上的正确率
	accuracy = tf.reduce_mean(tf.cast(correct_prrediction,tf.float32))

	#初始化会话,并开始训练过程
	with tf.Session() as sess:
		sess.run(tf.initialize_all_variables())

		#准备验证数据。一般在神经网络的训练过程中会通过验证数据大致判断停止的      #条件和训练的结果
		#验证集
		validate_feed = {x:mnist.validation.images,y_:mnist.validation.labels}

		#准备测试数据。在真实应用中,这部分数据在训练时是不可见的,这部分数据      #只是作为模型优劣的最后评价标准
		#测试集
		test_feed = {x:mnist.test.images,y_:mnist.test.labels}

		#迭代训练神经网络
		for i in range(TRAINING_STEPS):
			#每次取一个batch样本出来训练
			xs, ys = mnist.train.next_batch(BATCH_SIZE)
			#train_op相当于train_step优化器和variables_averages_op的集合,    #将两个关系依赖于train_op使得
			#反向传播和平均滑动的参数一起在sess.run()的函数中一起更新。
			sess.run(train_op,feed_dict={x: xs,y_:ys})
			#每1000轮输出一次在训练集上的测试结果
			if i % 1000 == 0:
	#计算滑动平均模型在验证数据上的结果。因为MNIST数据集比较小,所以一次
	#可以处理所有的验证数据。为了计算方便。本程序没有将数据划分为更小的batch.
	#当神经网络模型比比较复杂或者验证数据比较大时,太大的batch会导致计算时间过长
	#甚至导致内容溢出的错误。
				validate_acc =\  sess.run(accuracy,feed_dict=validate_feed)
				test_acc = sess.run(accuracy,feed_dict=test_feed)
				print("Aftrr %d training step(s),validation accuancy"\
					  " model is %g,test accuracy model is %g."%\(i,validate_acc,test_acc))

		#产生这一轮使用的一个batch训练集,并运行训练过程训练结束之后,在测试数      #据上检验神经网络模型的最终正确率
		test_acc = sess.run(accuracy,feed_dict=test_feed)
	   print("After %d training strep(s),test accuracy using average model is %g"%(TRAINING_STEPS,test_acc))
		 

#主程序入口
def main(argv=None):
	#声明处理MNIST数据集的类,这个类在初始化数据时会自动下载数据
	 mnist = input_data.read_data_sets("/path/to/MNIST_data/",one_hot=True)
	 train(mnist)

#Tensorflow提供一个主程序入口。tf.app.run()会调用上面定义的main函数
if __name__ == '__main__':
	#这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行。
	tf.app.run()
	#tf.app.run处理flag解析,然后执行main函数 
	#Runs the program with an optional ‘main’ function and ‘argv’ list 
	#用主函数和命令行参数列表,来跑程序

 运行结果如下图所示: 
 

【以上神经网络存在的不足之处:①训练的数据是由tensorflow封装的MNIST类进行处理,其过程过于简便,然而许多模型的训练中数据的处理并不会如此简便②训练完之后的模型没有保存起来,每次调用都得重新训练一遍③声明了太多的变量,变量过多,代码过于繁杂】

5.2.2 使用验证数据集判断模型的效果

采用cross-validation的方法需要的时间比较长,所以在海量数据的情况下,一般会更多的采用验证数据集的形式来评测模型的效果。

5.2.3

对比不同模型在MNIST验证集上的表现可以得出以下的结论:

①神经网络的结构(激活函数、隐藏层)对模型有本质性的影响

②滑动平均模型、指数衰减、正则化所带来的正确率的提升效果不是特别明显。因为滑动平均模型和指数衰减的学习率在一定程度上都是限制神经网络参数更新的速度,而该模型的收敛速度很快,因此这两种优化对模型最终的影响不大。迭代早期是否使用以上两种优化方法对训练结果的影响相对较小。然而,当问题更加复杂,迭代不会那么快收敛时,使用该两种优化算法可以发挥更大的作用。

③使用正则化对模型的提升效果要比滑动平均模型和指数衰减的学习率更加显著。

5.3变量管理

Tensorflow中提供了可以直接通过变量名称来创建或获取一个变量的机制。使得在不同函数中可以直接通过变量的名称来使用变量,而不需要变量通过参数的形式到处传递。Tensorflow中除了tf.Variable()用于创建变量,还可以通过函数tf.get_variable()函数来创建或者获取变量。以下是链各个

v = tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))

v = tf.Variable(tf.constant(1.0,shape=[1]),name="v")

 tf.get_variable 函数和tf.Variable函数最大的区别在于前者的变量名称是一个必填的参数,而后者的变量名称则是一个可选的参数,通过name="v"的形式给出。tf.get_variable()函数会根据这个名字去创建或者获取变量。为了避免变量的重复创建程序会报错。当需要通过tf.get_variable()来获取一个已经创建的变量时,需要通过tf.variable_scope函数来生成一个上下文管理器(已创建的变量只能从其所在命名空间内获取),以下为相应的例子:

with tf.variable_scope("foo"):
	v = tf.get_variable("v",shape=[1],initializer=tf.constant_initializer(1.0))
with tf.variable_scope("foo",reuse=True):
	v1 = tf.get_variable("v",[1])
	print(v == v1)
而且在Tensorflow中tf.variable_scope函数是可以嵌套的:

with tf.variable_scope("root"):
	#可以通过tf.get_variable_scope().reuse来获取当前上下文管理器中reuse参数的取值
	print (tf.get_variable_scope().reuse)
	#输出False
	with tf.variable_scope("foo",reuse=True):
		print(tf.get_variable_scope().reuse)
		#输出True
		with tf.variable_scope("bar"):
			print(tf.get_variable_scope().reuse)
			#输出True

	print (tf.get_variable_scope().reuse)
	#输出False
值得注意的是,在命名空间内创建的变量的名称都会带上命名空间名作为前缀。所以tf.variable_scope函数除了可以控制tf.get_variable函数执行功能之外还提供了一个变量管理命名空间的方式。

v1 = tf.get_variable("v",[1])
print(v1.name)#输出v:0."v"为变量的名称,“:0”表示这个变量是生成变量这个运算的第一个结果

with tf.variable_scope("foo"):
	v2 = tf.get_variable("v",[1])
	print(v2.name)#输出f00/v:0

with tf.variable_scope("foo"):
	with tf.variable_scope("bar"):
		v3 = tf.get_variable("v",[1])
		print(v3.name)#输出foo/bar/v:0
	v4 = tf.get_variable("v1",[1])
	print(v4.name)

with tf.variable_scope("",reuse=True):
	v5 = tf.get_variable("foo/bar/v",[1])
	#可以直接通过带命名空间名称的变量名来获取其他命名空间下的变量

	print(v5 == v3)#True
	v6 = tf.get_variable("foo/v1",[1])
	print(v6 == v4)#True
以上的变量命名空间以及命名管理方法将会应用到在最后的实例中

5.4Tensorflow模型的持久化
还记得之前给到的5.2的代码中存在的一个明显的缺陷吗,就是训练完之后模型没有保存就直接退出程序了。为了让训练结果可以复用吗,需要将训练得到的神经网络的模型持久化。

5.4.1持久化代码实现

Tensorflow中提供了API tf.train.Saver类来保存和还原一个神经网路模型。以下给出了保存计算图的方法:

import tensorflow as tf

#声明两个变量并计算他们的和
v1 = tf.Variable(tf.constant(1.0,shape=[1]),name = "v1")
v2 = tf.Variable(tf.constant(2.0,shape=[1]),name = "v2")

result = v1 + v2

# init_op = tf.initialize_all_variables()
#声明tf.train().Saver类,用于保存模型
init_op = tf.initialize_all_variables()

saver = tf.train.Saver()

with tf.Session() as sess:
	sess.run(init_op)
	# saver.save(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow实战/path/model.ckpt")
	saver.save(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow实战/path/to/model/model.ckpt")

Tensorflow会将计算图的结构和参数取值分开存放,因此在对应的路径下会得到3个文件,如下图所示:


第一个文件 model.ckpt.meta:用于保存Tensorflow计算图的结构,及神经网路的网络结构

第二个文件 model.ckpt: 保存了Tensorflow中每一个变量的取值

第三个文件 checkpoint: 保存了一个目录下所有模型的文件列表。格式(类型)为CheckpointState Protocol Buffer,包含以下属性model_checkpoint_path:保存最新的tensorflow模型的文件名;all_model_checkpoint_paths:列出还没有被删除的所有模型的文件名,checkpoint内容如下图所示:


以下给出了加载加载以保存的Tensorflow模型的方法:

import tensorflow as tf 

#使用很保存模型中一样的方式来声明变量
v1 = tf.Variable(tf.constant(1.0,shape=[1],name="v1"))
v2 = tf.Variable(tf.constant(2.0,shape=[1],name="v2"))
result = v1 + v2

saver  = tf.train.Saver()

with tf.Session() as sess:
	#加载以保存的模型,并通过以保存的模型中变量的值来计算加法
	saver.restore(sess,"C:/Users/Jet Zhang/Desktop/Tensorflow实战/path/to/model/model.ckpt")
	print(result)   #输出3
上述加载模型的代码和保存的代码基本类似主要的区别为:加载模型的代码中不用运行初始化的过程,而是直接将变量的值通过以保存的模型加载进来。 如果不希望重复定义图上的运算,也可以直接加载已经持久化的图,样例代码如下:

import tensorflow as tf 
#直接加载持久化图
saver = tf.train.import_meta_graph(
	"path/to/model/model.ckpt.meta")
with tf.Session() as sess:
	saver.restore(sess,"path/to/model/model.ckpt")
	#通过Tensor的名称来获取张量
	print(sess.run(tf.get_default_graph().get_tensor_by_name("add:0")))
	#输出[3.]

/************************************************************************************************************************

【此处补充关于"add:0"的知识】

import tensorflow as tf 
#tf.constant()是一个计算,其结果为一个张量
a = tf.constant([1.0,2.0],name = "a")
b = tf.constant([2.0,3.0],name = "b")
result = tf.add(a,b,name="add")
print(result)
#输出Tensor("add:0", shape=(2,), dtype=float32)
Tensorflow中所有的数据都通过张量(Tensor)来表示,它只是对Tensorflow中运算结果的引用,并没有真正地保存数字,它保存的是如何计算得到这些数字的过程。以上面的代码运行结果为例:

Tensorflow计算得到的是一个张量的结构:

一个张量包括以下3个属性:名字(name)、维度(shape)、类型(type)

①名字是一个张量的唯一标识符,它也可以给出这个张量是如何计算出来的。张量的命名通过“node:src_output”的形式给出。其中node为结点的名称(计算图上的每一个节点代表一个计算),src_output表示当前张量来自节点的第几个输出。比如“add:0”就表示节点“add”输出的第一个结果(编号从0开始)。

②shape=(2,)说明了张量result是一个一维数组,长度为出2。

③每一个张量会有唯一的类型,Tensorflow会对参与运算的所有张量作类型的检查,当发现类型不匹配时就会报错。

 *************************************************************************************************************************/

为了保存或者加载部分变量,在声明tf.train.Saver类时可以提供一个列表来制定需要保存或加载的变量.比如通过saver=tf.train.Saver([v1])命令来构建tf.train.Saver类,那么只有变量v1会被加载进来。

除了可以选取需要被加载的变量,tf.train.Saver类也支持在保存或者加载时给变量重命名。下面给出示例代码:


v1 = tf.Variable(tf.constant(1.0,shape=[1],name="other-v1"))
v2 = tf.Variable(tf.constant(2.0,shape=[1],name="other-v2"))
#直接使用tf.train.Saver()类加载模型会报变量找不到的错误

#这时只需要使用一个字典来重命名变量就可以加载模型了,字典指定了原来名称为v1的变
#量现在加载到变量v1中(名称W为other-v1)
#名称为v2的变量加载到变量v2中(名称为other-v2)
savee = tf.train.Saver({"v1":v1,"v2":v2})

此处对变量v1和v2的名称进行了修改,如果直接通过tf.train.Saver类默认的构造函数来加载保存的模型则会报变量找不到的错误。Tensorflow中通过字典将保存时的变量名和需要加载时的变量名联系起来。这样做的主要目的之一是使用滑动平均值。使得神经网络模型变得更加健壮。在Tensorflow中,每一个变量的滑动平均值是通过一个影子变量来维护的,所以要获取变量的滑动平均值实际上就是通过获取这个影子变量的取值。如果加载模型时直接将影子变量映射到自身,那么训练好的模型就不在需要调用函数来获取变量的滑动平均值了。大大方便了滑动平均模型的使用。以下为保存滑动平均模型的样例:

#保存滑动平均模型的样例
import tensorflow as tf
 
v = tf.Variable(0,dtype=tf.float32,name="v")

#在没有声明滑动平均模型的时候只有一个变量v,所以下面的语句会输出"v:0"
for variables in tf.all_variables():
	print(variables.name)

ema = tf.train.ExponentialMovingAverage(0.99)
maintain_average_op = ema.apply(tf.all_variables())
#在申明滑动平均模型之后,Tensorflow会自动生成一个影子变量
#v/ExponentialMovaing Average.于是下面的语句会输出
#"v:0"和"v/ExponentialMovingAverage:0"。
for variables in tf.all_variables():
	print(variables.name)

saver = tf.train.Saver()

with tf.Session() as sess:
	init_op = tf.initialize_all_variables()
	sess.run(init_op)

	sess.run(tf.assign(v,10))

	sess.run(maintain_average_op)

	#保存时,Tensorflow会将v:0和v/ExponentialMovingAverage:0这两个变量都存下来
	saver.save(sess,"/path/to/model/model.ckpt")
	print(sess.run([v,ema.average(v)]))

以下代码给出了如何通过变量重命名直接读取变量的滑动平均值

	v = tf.Variable(0,dtype=tf.float32,name="v1")
        #通过变量重命名将要来变量v的滑动平均值直接赋给v
	saver = tf.train.Saver({"v/ExponentialMovingAverage":v})
	with tf.Session() as sess:
		saver.restore(sess,"/path/to/model/model.ckpt")
		print(sess.run(v1))
为了方便加载时重命名滑动平均模型,tf.train.ExponentialMovingAverage类提供了variables_to_restore函数来生成tf.train.Saver类所需要的变量重命名字典,以下代码为variables_to_restore函数的使用样例:


variables_to_restore函数
import tensorflow as tf 

v = tf.Variable(0,dtype=tf.float32,name="v")
ema = tf.train.ExponentialMovingAverage(0.99)

#通过使用variables_to_restore函数可以直接生成上面代码中提供的字典
#{"v/ExponentialMovingAverage":v}
#以下代码会输出
#{'v/ExponentialMovingAverage':<tensorflow.python.ops.variables.Variable object at 0x7ff6454ddc10>}
#其中后面的Variable类就代表了变量v
print(ema.variables_to_restore())

saver = tf.train.Saver(ema.variables_to_restore())
with tf.Session() as sess:
	saver.restore(sess,"path/to.model/model.ckpt")
	print(sess.run(v))

Tensorflow中还提供了convert_variables_to_constants函数,通过这个函数可以将计算图的变量及其取值通过常量的方式保存,将整个Tensorflow计算图(变量的取值以及计算图的结构)统一存在一个一个文件中。下面的程序提供了实例:

import tensorflow as tf 
from tensorflow.python.framework import graph_util
 
v1 = tf.Variable(tf.constant(1.0,shape=[1],name="v1"))
v2 = tf.Variable(tf.constant(2.0,shape=[1],name="v2"))
result = v1 + v2

with tf.Session() as sess:
	sess.run(tf.initialize_all_variables())
	#导出当前计算图的GraphDef部分,只需要这一部分就可以完成输入层到输出层的计算过程
	graph_def = tf.get_default_graph().as_graph_def()

	#将图中的变量及其取值转化为常量,同时将图中不必要的节点去掉。在下面一行代码中,最后一个参数'add'
	#给出了需要保存的计算节点名称.add节点是上面定义IDE两个变量相加的操作。这里的add是节点的名称
	output_graph_def = graph_util.convert_variables_to_constants(sess,graph_def,['add'])
	#将导出的模型存入文件,Gfile以字符串的形式将内容写入文件当中。
	with tf.gfile.GFile("/path/to/model/combined_model.pb","wb") as f:
		f.write(output_graph_def.SerializeToString())
通过下面的程序可以直接计算定义的加法运算的结果。当只需要得到计算图的某个结点的取值时,这里提供了一个更为简便的方法,此方法将在后面应用于迁移学习当中:

import tensorflow as tf 
from tensorflow.python.platform import gfile
with tf.Session() as sess:
	model_filename = "/path/to/model/combined_model.pb"
	#读取保存的模型文件,将文件解析成对应的GraphDef Protocol Buffer.
	with gfile.FastGFile(model_filename,'rb') as f:
		graph_def = tf.GraphDef()
		graph_def.ParseFromString(f.read())

	#将graph_def中保存的图加载到当前图中。return_elements=["add:0"]
	#给出了返回的张量名称.在保存的时候给出了计算图结点的名称,所以
	#为“add”.在加载的时候给出了张量的名称,所以是add:0
	result = tf.import_graph_def(graph_def,return_elements=["add:0"])

	print(sess.run(result))#输出[array([ 3.], dtype=float32)]

5.4.2持久化原理及数据格式

Tensorflow通过图的形式来表示计算的编程系统。通过元图(MetaGraphDef)来记录计算图中结点的信息以及运行计算图中结点所需的元数据。元图由MetaGraphDef Protocol Buffer来定义。

【procotol buffer是用于处理结构化数据的工具。当要将结构化的信息持久化或进行网络传输时,就需要先将它们序列化。序列化就是将结构化的数据变成数据流的格式,简单说就是变成一个字符串。Protocol Buffer解决的问题便是:将结构化的数据序列化并从序列化的数据流中还原出原来的结构化数据。】

MetaGraphDef中的内容就构成了Tensorflow持久化的第一个文件。


MetaGraphDef的属性详见以下总结:

Tensorflow中提供了tf.train.NewCheckpointReader类来查看model.ckpt文加中保存的变量信息。以下展示了该类的使用方法

import tensorflow as tf 
#tf.train.NewCheckpointReader可以读取model.ckpt文件中所保存的所有变量
reader = tf.train.NewCheckpointReader('path/to/model/model.ckpt')

#获取所有的变量列表。这个一个从变量名到变量维度的字典
all_variables = reader.get_variable_to_shape_map()
for variable_name in all_variables:
	#打印变量的名称及其对应的维度
	print(variable_name,all_variables[variable_name])

print(reader.get_tensor("v1"))

# 输出
# v1 [1]
# v2 [1]
# [ 1.]





5.5 神经网络基础最佳实践案例


mnist_inference.py

import tensorflow as tf

INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 512

def get_weight_variable(shape, regularizer):
    weights = tf.get_variable("weights", shape, initializer=tf.truncated_normal_initializer(stddev=0.1))
    #tf.get_variable(name,shape,initializer)
    if regularizer != None: tf.add_to_collection('losses', regularizer(weights))
    return weights

#定义神经网络的前向传播过程
def inference(input_tensor, regularizer):
	#声明第一层神经网络的变量并完成传播过程
    with tf.variable_scope('layer1'):

        weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], regularizer)
        biases = tf.get_variable("biases", [LAYER1_NODE], initializer=tf.constant_initializer(0.0))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    with tf.variable_scope('layer2'):
        weights = get_weight_variable([LAYER1_NODE, OUTPUT_NODE], regularizer)
        biases = tf.get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
        layer2 = tf.matmul(layer1, weights) + biases

    #返回前向传播的结果
    return layer2


mnist_train.py

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
#加载mnist_inference中定义的常量和前行传播的函数
import mnist_inference
import os

#配置神经网络的参数
BATCH_SIZE = 128
LEARNING_RATE_BASE = 0.9
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99

#模型保存的文件名和路径
MODEL_SAVE_PATH="/path/to/model"
MODEL_NAME="mymodel.ckpt"


def train(mnist):
	#定义输入输出placeholder()
    x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')

    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    y = mnist_inference.inference(x, regularizer)
    #将代表轮数的变量指定为不可训练的参数
    global_step = tf.Variable(0, trainable=False)

    #定义损失函数、学习率、滑动平均操作以及训练过程

    #给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)
    #在所有代表神经网络的变量上使用滑动平均。tf.trainable_variables返回的是需要训练的变量列表
    #即GRAPHKets.TRAINABLE_VARIABLES中的元素,即没有指定trainable=False的参数
    variables_averages_op = variable_averages.apply(tf.trainable_variables())
    #计算交叉熵,分类问题只有一个答案时使用sparse_softmax_cross_entropy_with_logits函数
    #para1:神经网络前向传播的结果,para2:训练数据的正确答案
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))

    #计算在当前batch中所有交叉熵的平均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy)
    loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))
    learning_rate = tf.train.exponential_decay(
    #指定指数衰减法的学习率
        LEARNING_RATE_BASE,
        global_step,
        mnist.train.num_examples / BATCH_SIZE,
        LEARNING_RATE_DECAY,
        staircase=True)
    #优化损失函数
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)
    #定义上下文的依赖关系
	#只有在 variables_averages_op被执行以后,上下文管理器中的操作才会被执行
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')

    saver = tf.train.Saver()
    with tf.Session() as sess:
        tf.global_variables_initializer().run()

        for i in range(TRAINING_STEPS):
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})
            if i % 1000 == 0:
            	#每训练1000轮输出一次损失函数的大小
                print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
                #保存当前的模型。这里给出了global_step的参数,这样可以让每一个被保存的模型
                #文件名的末尾加上训练的轮数
                saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME), global_step=global_step)

def main(argv=None):
    mnist = input_data.read_data_sets("C:/path/to/MNIST_data", one_hot=True)
    train(mnist)

if __name__ == '__main__':
    tf.app.run()


mnist.eval.py

# -*- coding: utf-8 -*-
import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# 加载mnist_inference.py和mnist_train.py中定义的常量和函数。
import mnist_inference
import mnist_train

# 每10秒加载一次最新的模型,并在测试数据上测试最新模型的正确率。
EVAL_INTERVAL_SECS = 15

def evaluate(mnist):
	with tf.Graph().as_default() as g:
	# 定义输入输出的格式。
		x = tf.placeholder(
		tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')
		y_ = tf.placeholder(
		tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')
		validate_feed = {x: mnist.validation.images, 
		            y_:mnist.validation. labels}

		# 直接通过调用封装好的函数来计算前向传播的结果。因为测试时不关注正则化损失的值, 
		# 所以这里用于计算正则化损失的函数被设置为None。
		y = mnist_inference.inference(x, None)

		# 使用前向传播的结果计算正确率。如果需要对未知的样例进行分类,那么使用
		# tf.argmax(y, 1)就可以得到输入样例的预测类别了。
		correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
		accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

		# 通过变量重命名的方式来加载模型,这样在前向传播的过程中就不需要调用求滑动平均
		# 的函数来获取平均值了。这使得我们可以完全共用mnist_inference.py中定义的
		# 前向传播过程。
		variable_averages = tf.train.ExponentialMovingAverage(
				mnist_train.MOVING_AVERAGE_DECAY)
		variables_to_restore = variable_averages.variables_to_restore()
		saver = tf.train.Saver(variables_to_restore)

		# 每隔EVAL_INTERVAL_SECS秒调用一次计算正确率的过程以检测训练过程中正确率的# 变化。
		while True:
			with tf.Session() as sess:
			 # tf.train.get_checkpoint_state函数会通过checkpoint文件自动
			 # 找到目录中最新模型的文件名。
				ckpt = tf.train.get_checkpoint_state(
				     mnist_train.MODEL_SAVE_PATH)
				if ckpt and ckpt.model_checkpoint_path:
				     # 加载模型。
				  saver.restore(sess,
				  	ckpt.model_checkpoint_path)
				     # 通过文件名得到模型保存时迭代的轮数。
				  global_step = ckpt.model_checkpoint_path\
				                             .split('/')[-1].split('-')[-1]
				  accuracy_score = sess.run(accuracy,feed_dict=validate_feed)
				  print("After %s training step(s), validation "
				              "accuracy = %g" % (global_step, accuracy_score))
				else:
				   print('No checkpoint file found')
				   return
			time.sleep(EVAL_INTERVAL_SECS)
     
def main(argv=None): 
   mnist = input_data.read_data_sets("C:/path/to/MNIST_data", one_hot=True)
   evaluate(mnist)

if __name__ == '__main__':
   tf.app.run()




Experiment阶段(调参+记录)
Version-1


BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99


INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500


 


Version-2


BATCH_SIZE = 512
LEARNING_RATE_BASE = 0.9
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99


INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 512


随着batch的增大,反而使得训练的时间多了3倍多。
 
 


随后把batch调整为128,训练的速度立即提了上来,但准确率变化却不大。
 
 

猜你喜欢

转载自blog.csdn.net/qq_37053885/article/details/79177921