【TensorFlow】笔记3:MNIST数字识别问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012736685/article/details/88544173

一、MNIST数据处理

1、数据集概述

MNIST 数据集是 NIST 数据集的一个子集,它包含了 60000 张图片作为训练数据, 10000 张图片作为测试数据。在 MNIST 数据集中的每一张图片都代表了 0~9 中的一个数字。图片的大小都为 28 × 28 28\times28 , 且数字都会出现在图片的正中间。

2、数据获取

from tensorflow.examples.tutorials.mnist import input_data

path = './datasets/MNIST_DATA'
mnist = input_data.read_data_sets(path, one_hot=True)

print("Training data size: ", mnist.train.num_examples)
print("Validating data size: ", mnist.validation.num_examples)
print("Testing data size: ", mnist.test.num_examples)

# minist.train.next_batch函数,
# 它可以从所有的训练数据中读取一小部分作为一个训练batch。
batch_size = 100
xs, ys = mnist.train.next_batch(batch_size)
print("X shape: ", xs.shape)
print("Y shape: ", ys.shape)

输出结果

Training data size:  55000
Validating data size:  5000
Testing data size:  10000
X shape:  (100, 784)
Y shape:  (100, 10)

像素矩阵中的元素的取值范围为[0, 1],0代表白色背景,1代表黑色前景。

通过 input_data.read_data_sets 函数生成的类提供 minist.train.next_batch函数,它可以从所有的训练数据中读取一小部分作为一个训练batch

二、神经网络模型训练及不同模型结果对比

1、TF训练神经网络

在神经网络的结构上,深度学习一方面需要使用激活函数实现神经网络模型的去线性化,另一方面需要使用一个或多个隐藏层使得神经网络的结构更深,以解决复杂问题。

在训练神经网络时,通常使用带指数衰减的学习率设置、使用正则化来避免过度拟合,以及使用滑动平均模型来使得最终模型更加健壮。

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

path = './datasets/MNIST_DATA'

# MNIST 数据集相关的常数
INPUT_NODE = 784     # 28*28
OUT_NODE = 10        # 0~9

# 配置神经网络的参数
LAYER1_NODE = 500  				# 一个隐藏层,节点数:500
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		# 滑动平均衰减率

# 一个辅助函数,给定神经网络的输入和所有参数,计算神经网络的前向传播结果。
# 在这里定义了一个使用 ReLU 激活函数的三层全连接神经网络。
# 通过加入隐藏层实现了多层网络结构,通过 ReLU 激活函数实现了去线性化。
# 在这个函数中也支持传入用于计算参数平均值的类,这样方便在测试时使用滑动平均模型。
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
	# 当没有提供滑动平均类时,直接使用参数当前的取值
	if avg_class == None:
		# 计算隐藏层的前向传播结果,这里使用了 ReLU i版活函数
		layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
		# 计算输出层的前向传播结果。
		# 因为在计算损失函数时会一并计算 softmax 函数,所以这里不需要加入激活函数。
		# 而且不加入 softmax 不会影响预测结果。
		# 因为预测时使用的是不同类别对应节点输出值的相对大小,有没有 softmax 层对最后分类结果的
		# 计算没有影响。于是在计算整个神经网络的前向传播时可以不加入最后的 softmax 层 。
		return tf.matmul(layer1, weights2) + biases2
	else:
		# 首先使用 avg_class.average 函数来计算得出变量的滑动平均值,
		# 然后再计算相应的神经网络前向传播结果。
		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(tf.float32, [None, INPUT_NODE], name='x-input')
	y_ = tf.placeholder(tf.float32, [None, OUT_NODE], name='y-input')

	# 生成隐藏层的参数。
	weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))
	biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))

	# 生成输出层的参数。
	weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUT_NODE], stddev=0.1))
	biases2 = tf.Variable(tf.constant(0.1, shape=[OUT_NODE]))

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

	# 定义在储训练轮数的变量。这个变量不需要计算滑动平均值,所以这里指定这个变量为
	# 不可训练的变量(trainable=Fasle)。在使用 TensorFlow 训练神经网络时,
	# -般会将代表训练轮数的变量指定为不可训练的参数。
	global_step = tf.Variable(0, trainable=False)

	# 给定消动平均哀减率和训练轮数的变量,初始化滑动平均类。
	# 给定训练轮数的变量可以加快训练早期变量的更新速度。
	variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

	# 在所有代表神经网络参数的变量上使用滑动平均。
	# tf.trainable_variables 返回的就是图上集合GraphKeys.TRAINABLE_VARIABLES 中的元索。
	# 也就是这个集合的元索就是所有没有指定 trainable=False 的参数。
	variable_avergaes_op = variable_averages.apply(tf.trainable_variables())

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

	# 计算交叉熵损失函数:spare_softmax_cross_entropy_with_logits()
	# 第一个参数是神经网络不包括 softmax 层的前向传播结果,第二个是训练数据的正确答案。
	# tf.argmax() 得到正确答案对应的类别编号
	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 正则化损失的损失函数
	regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
	# 计算模型的正则化损失。一般只计算神经网络边上权重的正则化损失,而不使用bias。
	regularization = regularizer(weights1) + regularizer(weights2)
	# loss funtions
	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 优化算法来优化损失函数
	train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step = global_step)

	# 在训练、神经网络模型时,每过一遍数据既需要通过反向传播来更新神经网络中的参数,
	# 又要更新每一个参数的滑动平均值。
	# 为了一次完成多个操作,TF提供了tf.control_dependencies和tf.group两种机制。
	# train_op = tf.group(train_step, variable_avergaes_op)  # 等价
	with tf.control_dependencies([train_step, variable_avergaes_op]):
		train_op = tf.no_op(name='train') 

	# 检验使用了滑动平均模型的神经网络前向传播结果是否正确。
	# tf.argmax(average_y , 1)计算每一个样例的预测答案。
	# 其中 average_y 是一个 batch_size*10 的二维数组,每一行表示一个样例的前向传播结果。
	# tf.argmax 的第二个参数"1”表示选取最大值的操作仅在第一个维度中进行,
	# 也就是说,只在每一行选取最大值对应的下标。
	# 得到长度为batch一维数组,数组中的值表示每个样例对应的数字识别结果。
	# tf.equal判断两个张量的每一维是否相等,如果相等返回 True ,否则返回 False 。
	correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
	# 这个运算先将布尔型的数值转换为实数型,然后计算平均值。表示模型在这一组数据上的正确率。
	accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

	# 初始化会话并开始训练过程。
	with tf.Session() as sess:
		tf.global_variables_initializer().run()
		# 准备验证数据。 
		# 一般在神经网络的训练过程中会通过验证数据来大致判断停止的条件和评判训练的效果。
		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):
			# 每 1000 轮输出一次在验证数据集上的测试结果
			if i % 1000 == 0:
			# 计算滑动平均模型在验证数据上的结果。因为 MNIST 数据集比较小,所以一次
			# 可以处理所有的验证数据。为了计算方便,本样例程序没有将验证数据划分为更
			# 小的 batch. 当神经网络棋型比较复杂或者验证数据比较大时,太大的 batch
			# 会导致计算时间过长甚至发生内存溢出的错误。
				validate_acc = sess.run(accuracy, feed_dict=validate_feed)
				print("After %d training step(s), validation accuracy "
						"using average model is %g " % (i, validate_acc))

				# 产生这一轮使用的一个 batch 的训练数据,并运行训练过程。
			xs, ys = mnist.train.next_batch(BATCH_SIZE)
			sess.run(train_op, feed_dict={x: xs, y_: ys})

		# 在训练结束之后,在测试数据上检测神经网络模型的最终正确率。
		test_acc = sess.run(accuracy, feed_dict=test_feed)
		print("After %d training step(s), test accuracy "
				"using average model is %g" % (TRAINING_STEPS, test_acc))

def main(argv=None):
	mnist = input_data.read_data_sets(path, one_hot=True)
	train(mnist)

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

输出结果

WARNING:tensorflow:From /home/jie/Jie/codes/tf/mnist.py:166: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.
Instructions for updating:
Please write your own downloading logic.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.data to implement this functionality.
Extracting ./datasets/MNIST_DATA/train-images-idx3-ubyte.gz
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.data to implement this functionality.
Extracting ./datasets/MNIST_DATA/train-labels-idx1-ubyte.gz
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:110: dense_to_one_hot (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use tf.one_hot on tensors.
Extracting ./datasets/MNIST_DATA/t10k-images-idx3-ubyte.gz
Extracting ./datasets/MNIST_DATA/t10k-labels-idx1-ubyte.gz
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/contrib/learn/python/learn/datasets/mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.
Instructions for updating:
Please use alternatives such as official/mnist/dataset.py from tensorflow/models.
2019-03-14 11:30:04.019232: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-03-14 11:30:04.104098: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:897] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-03-14 11:30:04.105001: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1405] Found device 0 with properties: 
name: GeForce GTX 1060 major: 6 minor: 1 memoryClockRate(GHz): 1.6705
pciBusID: 0000:01:00.0
totalMemory: 5.94GiB freeMemory: 5.50GiB
2019-03-14 11:30:04.105016: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1484] Adding visible gpu devices: 0
2019-03-14 11:30:04.324445: I tensorflow/core/common_runtime/gpu/gpu_device.cc:965] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-03-14 11:30:04.324486: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971]      0 
2019-03-14 11:30:04.324493: I tensorflow/core/common_runtime/gpu/gpu_device.cc:984] 0:   N 
2019-03-14 11:30:04.324817: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1097] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 5264 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1060, pci bus id: 0000:01:00.0, compute capability: 6.1)
After 0 training step(s), validation accuracy using average model is 0.1332 
After 1000 training step(s), validation accuracy using average model is 0.9774 
After 2000 training step(s), validation accuracy using average model is 0.9806 
After 3000 training step(s), validation accuracy using average model is 0.982 
After 4000 training step(s), validation accuracy using average model is 0.983 
After 5000 training step(s), validation accuracy using average model is 0.9836 
After 6000 training step(s), validation accuracy using average model is 0.9834 
After 7000 training step(s), validation accuracy using average model is 0.9828 
After 8000 training step(s), validation accuracy using average model is 0.9832 
After 9000 training step(s), validation accuracy using average model is 0.9826 
After 10000 training step(s), validation accuracy using average model is 0.9824 
After 11000 training step(s), validation accuracy using average model is 0.9822 
After 12000 training step(s), validation accuracy using average model is 0.9842 
After 13000 training step(s), validation accuracy using average model is 0.9836 
After 14000 training step(s), validation accuracy using average model is 0.9842 
After 15000 training step(s), validation accuracy using average model is 0.9834 
After 16000 training step(s), validation accuracy using average model is 0.9838 
After 17000 training step(s), validation accuracy using average model is 0.9844 
After 18000 training step(s), validation accuracy using average model is 0.984 
After 19000 training step(s), validation accuracy using average model is 0.9834 
After 20000 training step(s), validation accuracy using average model is 0.9838 
After 21000 training step(s), validation accuracy using average model is 0.9844 
After 22000 training step(s), validation accuracy using average model is 0.984 
After 23000 training step(s), validation accuracy using average model is 0.984 
After 24000 training step(s), validation accuracy using average model is 0.9844 
After 25000 training step(s), validation accuracy using average model is 0.9848 
After 26000 training step(s), validation accuracy using average model is 0.9838 
After 27000 training step(s), validation accuracy using average model is 0.9844 
After 28000 training step(s), validation accuracy using average model is 0.984 
After 29000 training step(s), validation accuracy using average model is 0.9842 
After 30000 training step(s), test accuracy using average model is 0.9842

在训练初期,模型在验证数据集上表现越来越好,后来出现波动,说明模型已经接近极小值,迭代结束。

2、使用验证数据判断模型效果

所需初始的超参数:上述程序的开始,设置了初始学习率、学习率衰减率、隐藏层节点数、迭代次数、batch_size、正则项系数、滑动平均衰减数等7个不同的参数。

如何设置初始的超参数:一般情况需要实验来调整

难点:虽然模型的最终效果是在测试数据上进行判定的,但不能直接使用测试数据,否则会过拟合,从而丢失对未知数据的判断能力,所以要保证测试数据在训练过程中是不可见的。

解决方法

  • 从训练数据中抽取一部分作为验证数据,来评判不同参数取值下模型的表现。在海量数据的情况下,一般会更多地采用验证数据集的形式来评测模型的效果。
  • 使用“交叉验证(cross validation)”,但是神经网络的训练时间本身就比较长,所以采用该方法会花费大量的时间,故一般不会选用。

不同迭代轮次下,模型在验证数据和测试数据上的正确率。
==》每1000轮的输出滑动平均的模型在验证数据和测试数据上的正确率。

validate_acc = sess.run(accuracy, feed_dict=validate_feed)
test_acc = sess.run(accuracy, feed_dict=test_feed)

print("After %d training step(s), validation accuracy using average "
	  "model is %g, test accuracy using average model is %g" % 
	  (i, validate_acc, test_acc))

输出结果

After 0 training step(s), validation accuracy using average model is 0.0592, test accuracy using average model is 0.058
After 1000 training step(s), validation accuracy using average model is 0.9754, test accuracy using average model is 0.9764
After 2000 training step(s), validation accuracy using average model is 0.981, test accuracy using average model is 0.981
After 3000 training step(s), validation accuracy using average model is 0.982, test accuracy using average model is 0.9824
After 4000 training step(s), validation accuracy using average model is 0.9842, test accuracy using average model is 0.9825
After 5000 training step(s), validation accuracy using average model is 0.984, test accuracy using average model is 0.9841
After 6000 training step(s), validation accuracy using average model is 0.9852, test accuracy using average model is 0.984
....
After 25000 training step(s), validation accuracy using average model is 0.9842, test accuracy using average model is 0.9839
After 26000 training step(s), validation accuracy using average model is 0.984, test accuracy using average model is 0.983
After 27000 training step(s), validation accuracy using average model is 0.9848, test accuracy using average model is 0.9841
After 28000 training step(s), validation accuracy using average model is 0.9846, test accuracy using average model is 0.9837
After 29000 training step(s), validation accuracy using average model is 0.9846, test accuracy using average model is 0.9845

分析:
验证数据和测试数据上的准确率趋势基本一致,且他们的相关系数(correlation coefficient)大于0.9999。意味着可以通过模型在验证数据上的表现来判断一个模型的优劣。
==》前提:验证数据分布可以很好代表测试数据分布。

3、不同模型效果比较

不同的优化方法,需要使用多层和激活函数的网络结构。此外,可以使用指数衰减学习率、加入正则化的损失函数以及滑动平均模型。

在神经网络结构的设计上,需要使用激活函数和多层隐藏层。在神经网络优化时,可以使用指数衰减的学习率,加入正则化的损失函数以及滑动平均模型。

不同优化方法的影响:

  1. 本质影响:神经网络的结构
  2. 滑动平均模型、指数衰减的学习率、正则化对MNIST数据集影响看起来不是很大的原因:
  • 因为滑动平均模型、指数衰减的学习率都在限制神经网络的参数的更新速度,而该数据库模型收敛的速度很快,所以影响不大。
  • 但是当问题更复杂时,迭代不会很快收敛,所以滑动平均模型、指数衰减的学习率可以发挥更大的作用。
  • 正则化带来的效果更为显著

总结:优化方法可以对模型带来更好的效果(模型越复杂,效果越明显)

三、变量管理

当神经网络的结构复杂、参数很多时,需要更好的方式来传递和管理神经网络的参数。
TensorFlow提供了通过变量名称来创建或获取一个变量的机制,不同函数可以直接通过变量的名字来使用变量,不需要通过参数的形式到处传递。
==》实现:tf.get_variable()tf.variable_scope()

1、tf.get_variable()

tf.get_variable() 用于创建或获取变量。

通过变量名称来获取变量。创建变量时,基本等价于tf.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")

TensorFlow提供了7种不同的初始化函数:

初始化函数 功能 主要参数
tf.constant_initializer 将变量初始化为给定常量 常量的取值
tf.random_normal_initializer 将变量初始化为满足正太分布的随机值 正太分布的均值和标准差
tf.truncated_normal_initializer 将变量初始化为满足正太分布的随机值,但若随机出来的值偏离平均值超过两个标准差,那么这个数将会被重新随机 正太分布的均值和标准差
tf.random_uniform_initializer 将变量初始化为满足平均分布的随机值 最大,最小值
tf.uniform_unit_scaling_initializer 将变量初始化为满足平均分布但不影响输出数量级的随机值 factor(产生随机值时乘以的系数)
tf.zeros_initializer 将变量设置为全为0 变量维度
tf.ones_initializer 将变量设置为全为1 变量维度

tf.get_variable:

  • tf.get_variable变量名是一个必填参数,其首先会试图去创建一个名字为v的参数,如果创建失败(比如已经有同名的参数),那么这个程序就会报错。这是为了避免无意识的变量复用造成的错误。
  • 如果需要通过tf.get_variable获取一个已经创建的变量,需要通过tf.variable_scope函数来生成一个上下文管理器,并明确指定在这个上下文管理器中,tf.get_variable将直接获取已经生成的变量。

2、tf.variable_scope()管理

下面给出一段代码说明如何通过tf.variable_scope函数来控制tf.get_variable函数获取已经创建过的变量。

#在名字为foo的命名空间内创建名字为v的变量
with tf.variable_scope("foo"):
    v = tf.get_variable("v",[1],initializer=tf.constant_initializer(1.0))

#因为在命名空间foo已经存在名字为v的变量,所有下面的代码将会报错:
with tf.variable_scope("foo"):
    v = tf.get_variable("v",[1])

#在生成上下文管理器时,将参数reuse设置为True。这样tf.get_variable函数将直接获取已经生成的变量
with tf.variable_scope("foo",reuse=True):
    v1 = tf.get_variable("v",[1])
    print v == v1    #输出为True,代表v,v1是相同的Tensorflow中的变量

#将参数reuse设置为True时,tf.variable_scope将只能获取已经创建的变量,因为在命名空间bar中还没有创建变量v,所以下面的代码将会报错:
with tf.variable_scope("bar",reuse=True):
  v = tf.get_variable("v",[1])

通过 tf.variable_scope 控制 tf.get_variable 的语义:

  • 如果tf.variable_scope函数使用参数 reuse=None 或者reuse=False创建上下文管理器,tf.get_variable操作将创建新的变量,如果同名的变量已经存在,则tf.get_variable函数将报错。另外,Tensorflow中tf.variable_scope函数是可以嵌套的。
  • 如果tf.variable_scope函数使用参数 reuse=True 生成上下文管理器时,该上下文管理器中的所有 tf.get_variable 函数会直接获取已经创建的变量,如果变量不存在,将会报错。
  • 使用变量管理后,就不再需要将所有变量都作为参数传递到不同的函数中了,当神经网络结构更加复杂,参数更多时,使用这种变量管理的方式将大大提高程序的可读性。

嵌套示例代码:

with tf.variable_scope("root"):
	# 获取当前上下文管理器中 reuse 的取值。
	print(tf.get_variable_scope().reuse)

	with tf.variable_scope("foo", reuse=True):
		print(tf.get_variable_scope().reuse)

		with tf.variable_scope("bar"):
			print(tf.get_variable_scope().reuse)

			with tf.variable_scope("bar1"):
				print(tf.get_variable_scope().reuse)
	print(tf.get_variable_scope().reuse) 

输出结果

False
True
True
True
False

结论:在嵌套中,若指定reuse参数为True则输出为True当未指定reuse参数时,这时的reuse取值与外面一层保持一致,若为最后层,则为False

通过 tf.variable_scope 来管理变量命名空间:

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

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

with tf.variable_scope("foo"):
	with tf.variable_scope("bar"):
		v3 = tf.get_variable("v", [1])
		print(v3.name)
		# output: foo/bar/v:0
		# 名称会加入命名空间的名称

	v4 = tf.get_variable("v1", [1])
	print(v4.name)
	# output: foo/v1:0

with tf.variable_scope("", reuse=True):
	v5 = tf.get_variable("foo/bar/v", [1])
	print(v5 == v3)
	# output: True

	v6 = tf.get_variable("foo/v1", [1])
	print(v6 == v4)
	# output: True

3、前向传播的改进

def inference(input_tensor, reuse=False):
	# 定义第一层神经网络的变量和前向传播过程。
	with tf.variable_scope('layer1', reuse=reuse):
		# 根据传进来的reuse来判断是创建新变量还是使用已经创建好的,
        # 第一次构造网络时需要创建新的变量,
        # 之后每次调用该函数都直接使用reuse=True就不需要每次将变量传进来了
		weights = tf.get_variable("weights", [INPUT_NODE, LAYER1_NODE], 
						initializer=tf.truncated_normal_initializer(stddev=0.1))
		biases = tf.get_variable("biases", [LAYER1_NODE],
						initializer=tf.constant_initializer(0.0))
		layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

	# 定义第 2 层神经网络的变量和前向传播过程。
	with tf.variable_scope('layer2', reuse=reuse):
		weights = tf.get_variable("weights", [LAYER1_NODE, OUT_NODE],
						initializer=tf.truncated_normal_initializer(stddev=0.1))
		biases = tf.get_variable("biases", [OUT_NODE],
						initializer=tf.constant_initializer(0.0))
		layer2 = tf.matmul(layer1, weights) + biases

	return layer2

x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
y = inference(x)

# 在程序中如果需要使用训练好的神经网络进行推导时,可以直接调用inference(new_x,True)
new_x = ...
new_y = inference(new_x, True)

四、TF模型持久化

为了将训练得到的模型保存下来方便下次使用,即结果可以复用,需要将神经网络模型持久化。

1、持久化代码实现

实现:tf.train.Saver

(1)ckpt文件的保存

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.global_variables_initializer()
# 声明 tf.train.Saver 类用于保存模型。
saver = tf.train.Saver()

with tf.Session() as sess:
	sess.run(init_op)
	# 将模型保存到model.ckpt 文件。
	saver.save(sess, "./model.ckpt")

Tensorflow模型一般会存在后缀为 .ckpt 文件中,虽然上面的程序只指定了一个文件路径,但是在这个文件目录下会出现三个文件,这是因为Tensorflow会将计算图的结构和图上的参数取值分来保存。
在这里插入图片描述

  • 第一个文件为model.ckpt.meta,它保存了Tensorflow计算图的结构,
  • 第二个文件为model.ckpt,这个文件保存了Tensorflow程序中每一个变量的取值,
  • 最后一个文件为checkpoint文件,这个文件保存了一个目录下所有的模型文件列表。

(2)加载已经保存的TF模型

import tensorflow as tf

# load the model
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, "./model.ckpt")
	print(sess.run(result))
	# 输出:[ 3.]

加载模型的代码中,没有运行变量的初始化过程,而是将变量的值通过已经保存的模型加载进来.

如果不希望重复定义图上的运算,也可以直接加载已经持久化的图。

import tensorflow as tf

# 加载计算图
saver = tf.train.import_meta_graph("./model.ckpt.meta")
with tf.Session() as sess:
	# 加载全部变量
	saver.restore(sess, "./model.ckpt")
	# 通过张量名称来获取张量
	print(sess.run(tf.get_default_graph().get_tensor_by_name("add:0")))
	# 输出:[ 3.]

(3)保存或加载部分变量

情景:可能有一个之前训练好的五层神经网络模型,但现在想尝试一个六层的神经网络,那么可以将前面五层神经网络中的参数直接加载到新的模型,而仅仅将最后一层神经网络重新训练。

实现:在声明 tf.train.Saver 类时可以提供一个列表来指定需要保存或者加载的变量。比如在加载模型的代码中使用saver=tf.train.Saver([v1])命令来构建tf.train.Saver类,那么只有变量v1会被加载进来,如果运行修改后之家在v1的代码会得到变量未初始化的错误:

tensorflow.python.framework.errors_impl.FailedPreconditionError: Attempting to use uninitialized value v2

(4)在保存或者加载时给变量重命名

v11 = tf.Variable(tf.constant(1.0, shape=[1]), name="other-v1")
v22 = tf.Variable(tf.constant(2.0, shape=[1]), name="other-v2")

# 如果直接使用tf.train.Saver类来加载模型会报变量找不到的错误。


# 使用字典来重命名变量即可加载原来的模型
# 字典指定了原来名称为v1的变量现在加载到变量v11中(名称为other-v1)
saver = tf.train.Saver({"v1":v11, "v2":v22})

主要目的:方便使用变量的滑动平均值,在Tensorflow中,每一个变量的滑动平均值是通过影子变量维护的,所以要获取变量的滑动平均值实际上就是获取这个影子变量的取值。如果在加载模型时直接将影子变量映射到变量自身,那么在使用训练好的模型时就不需要再调用函数来获取变量的滑动平均值了。

(5)示例

一个保存滑动平均模型的样例。

import tensorflow as tf

# 1. 使用滑动平均
vv = tf.Variable(0, dtype=tf.float32, name="v")
# 在没有申明滑动平均模型时只有一个变量 v,所以以下语句只会输出“v:0”
for variables in tf.global_variables():
	print(variables.name)
	# v:0

ema = tf.train.ExponentialMovingAverage(0.99)
maintain_average_op = ema.apply(tf.global_variables())
# 在申明滑动平均模型之后, TensorFlow 会自动生成一个影子变量
for variables in tf.global_variables():
	print(variables.name)
	# v:0
	# v/ExponentialMovingAverage:0


# 2. 保存滑动平均模型
saver = tf.train.Saver()
with tf.Session() as sess:
	init_op = tf.global_variables_initializer()
	sess.run(init_op)

	sess.run(tf.assign(vv, 10))
	sess.run(maintain_average_op)
	# 保存的时候会将v:0  v/ExponentialMovingAverage:0这两个变量都存下来。
	saver.save(sess, "./model/model1.ckpt")
	print(sess.run([vv, ema.average(vv)]))
'''
[10.0, 0.099999905]
'''

# 3. 加载滑动平均模型
vv1 = tf.Variable(0, dtype=tf.float32, name="v")
# 通过变量命名将原来变量v的滑动平均值直接赋值给vv
saver = tf.train.Saver({"v/ExponentialMovingAverage":vv1})
with tf.Session() as sess:
	saver.restore(sess, "./model/model1.ckpt")
	print(sess.run(vv1))

在加载时重命名滑动平均变量。使用tf.train.ExponentialMovingAverage类提供的variables_to_restore函数生成tf.train.Saver类所需的变量命名字典

import tensorflow as tf

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

print(ema.variables_to_restore())
# {'v/ExponentialMovingAverage': <tf.Variable 'v:0' shape=() dtype=float32_ref>}

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

(6)统一保存

将变量取值和计算图结构统一保存:在TF提供了 convert_variables_to_constants 函数,可以将计算图中的变量及其取值通过变量的方式保存到一个文件中。

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

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

	# 导出当前计算图的GraphDef部分,只需此部分就可完成从输入层到输出层的计算过程
	graph_def = tf.get_default_graph().as_graph_def()

	# 将图中的变量及其取值转化为常量,同时将图中不必要的节点去掉
	output_graph_def = graph_util.convert_variables_to_constants(sess, graph_def, ["add"])

	# 将导出的模型出入文件
	with tf.gfile.GFile("./model/combined_model.pb", "wb") as f:
		f.write(output_graph_def.SerializeToString())

# load the model
import tensorflow as tf
from tensorflow.python.platform import gfile

with tf.Session() as sess:
	model_filename = "./model/combined_model.pb"
	# 保存的模型文件,并将文件解析成对应的GrapDef 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)]

2、持久化原理及数据格式

  • Tensorflow是一个通过图的形式来表达计算的编程系统,Tensorflow程序中的所有计算都会表达为计算图上的节点。
  • Tensorflow通过元图(MetGraph)来记录计算图中节点的信息以及运行计算图中节点所需要的元数据。
  • Tensorflow中元图是由MetaGraphDef Protocol Buffer定义的,MetaGraphDef中的内容就构成了Tensorflow持久化时的第一个文件。==》.meta文件

元图(MetGraph)主要记录5类信息

保存MetGraph信息的文件默认以.meta为后缀名,是一个二进制文件,无法直接查看,TensorFlow提供export_meta_graph函数来以json格式导出MetaGraphDef Protocol Buffer。

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

saver = tf.train.Saver()
saver.export_meta_graph("./model/model1.ckpt.meda.json", as_text=True)

下面分别介绍元图存储的信息 :

  • meta_info_def 属性:记录了计算图中的元数据(计算图版本号、标签等)及程序中所有用到的运算方法信息。
  • graph_def 属性:记录了计算图上的节点信息,因为在meta_info_def属性已经包含了所有运算的信息,所以graph_def只关注运算的连接结构。
  • saver_def 属性:记录了持久化模型时需要使用的一些参数,如保存到文件的文件名、保存操作和加载操作的名称,以及保存频率等。
  • collection_def 属性:计算图中维护集合的底层实现,该属性是一个从集合名称到集合内容的映射。

五、TF最佳实践样例

  • 将不同的功能模块分开:将训练和测试分成两个独立的程序,这可以使得每一个组件更加灵活。比如训练神经网络的程序可以持续输出训练好的模型,而测试程序可以每隔一段实践检验最新模型的正确率,如果模型效果更好,则将这个模型提供给产品使用。除了将不同的功能模块分开;
  • 本节还将前向传播的过程抽象成一个单独的库函数。因为神经网络的前向传播过程在训练和测试的过程中都会用到,所以通过库函数的方式使用起来既方便又可以保证训练和测试过程中使用的前向传播方法是一致的。

1、mnist_inference.py

定义了前向传播的过程以及神经网络中的参数。

# -*- coding:utf-8 -*-
import tensorflow as tf

# 1. 定义神经网络结构相关参数
INPUT_NODE = 784 	# 28*28
OUTPUT_NODE = 10
LAYER1_NODE = 500

# 2. 通过tf.get_variable函数来获取变量
def get_weight_variable(shape, regularizer):
	weights = tf.get_variable(
		"weights", shape, initializer=tf.truncated_normal_initializer(stddev=0.1))
	# 当给出正则化生成函数,将正则化损失加入losses
	if regularizer != None:
		tf.add_to_collection('losses', regularizer(weights))
	return weights

# 3. 定义神经网络的前向传播过程
def inference(input_tensor, regularizer):
	with tf.variable_scope('layer1'):
		weights = get_weight_variable([INPUT_NODE, LAYER1_NODE], regularizer)
		biases = 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 = get_variable("biases", [OUTPUT_NODE], initializer=tf.constant_initializer(0.0))
		layer2 = tf.matmul(layer1, weights) + biases

	return layer2

2、mnist_train.py

定义了神经网络的训练过程。

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

# 加载 mnist inference.py 中定义的常量和前向传播的函数
import mnist_inference

# 1. 定义神经网络结构相关参数
BATCH_SIZE = 100
LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARAZTION_RATE = 0.0001
TRANING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99

MODEL_SAVE_PATH = "./model/"
MODEL_NAME = "model_mnist.ckpt"

# 2. 定义训练过程
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(REGULARAZTION_RATE)
	y = mnist_inference.inference(x, regularizer)
	global_step = tf.Variable(0, trainable=False)

	# 定义损失函数、学习率、滑动平均操作以及训练过程。
	variable_averages = tf.train.ExponentialMovingAverage(
		MOVING_AVERAGE_DECAY, global_step)
	variable_averages_op = variable_averages.apply(
		tf.trainable_variables())
	cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
		logits=y, labels=tf.argmax(y_, 1))
	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)
	train_step = tf.train.GradientDescentOptimizer(learning_rate)\
					.minimize(loss, global_step=global_step)
	with tf.control_dependencies([train_step, variable_averages_op]):
		train_op = tf.no_op(name='train')


	# 初始化TensorFlow持久化类。
	saver = tf.train.Saver()
	with tf.Session() as sess:
		tf.global_variables_initializer().run()

		# train
		for i in range(TRANING_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:
				print("After %d training step(s), loss on training batch is %g." % (step, loss_value))
				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("./datasets/MNIST_DATA", one_hot=True)
	train(mnist)

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

输出结果

5216 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1060, pci bus id: 0000:01:00.0, compute capability: 6.1)
After 1 training step(s), loss on training batch is 2.84809.
After 1001 training step(s), loss on training batch is 0.326219.
After 2001 training step(s), loss on training batch is 0.179183.
After 3001 training step(s), loss on training batch is 0.131504.
After 4001 training step(s), loss on training batch is 0.113809.
After 5001 training step(s), loss on training batch is 0.10388.
...
After 22001 training step(s), loss on training batch is 0.0408138.
After 23001 training step(s), loss on training batch is 0.0397483.
After 24001 training step(s), loss on training batch is 0.0349427.
After 25001 training step(s), loss on training batch is 0.0382821.
After 26001 training step(s), loss on training batch is 0.0368644.
After 27001 training step(s), loss on training batch is 0.0366197.
After 28001 training step(s), loss on training batch is 0.0426658.
After 29001 training step(s), loss on training batch is 0.0358222.

3、mnist_eval.py

定义了测试过程。

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

import mnist_inference
import mnist_train

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

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))

		# 通过变量重命名的方式来加载模型
		variable_averages = tf.train.ExponentialMovingAverage(mnist_train.MOVING_AVERAGE_DECAY)
		variable_to_restore = variable_averages.variables_to_restore()
		saver = tf.train.Saver(variable_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:
					# load the model
					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("./datasets/MNIST_DATA", one_hot=True)
	evaluate(mnist)

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

输出结果

After 29001 training step(s), validation accuracy = 0.985

猜你喜欢

转载自blog.csdn.net/u012736685/article/details/88544173
今日推荐