您的位置:首页 > 其它

【从零开始学习Tensorflow】(三)第5章 MNIST数字识别问题

2018-04-09 17:39 666 查看
转载请注明作者和出处: https://blog.csdn.net/weixin_37392582

代码平台https://gitee.com/wuweijun

开发平台: Win10 + Python3.6 + Anaconda3

编  者: 无尾

文章性质:【从零开始学习Tensorflow】系列博客为 《TensorFlow+实战Google深度学习框架》一书的学习笔记。

5.1、MNIST数据处理

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

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

5.2.3、不同模型效果比较

5.3、变量管理
5.4 TensorFlow 模型持久化
5.4.1 持久化代码实现

四、TensorFlow 模型持久化

五、TensorFlow 最佳实践样例程序

小结

5.1、MNIST数据处理

MNIST数据集,包含60000张图片作为训练数据(28*28),10000张图片作为测试数据,标签集用 ont_hot 编码表示手写数字(例:表示数字3→[0,0,0,1,0,0,0,0,0,0])。

TensorFlow提供了一个类来处理 MNIST 数据。这个类会自动下载并转换MNIST数据的格式,将数据从原始的数据包中解析成训练和测试神经网络时使用的格式。下面给出使用这个函数的样例程序。

from tensorflow.examples.tutorials.mnist import input_data

#载入 MNIST 数据集,若指定地址下没有,则自动从网上下载
mnist = input_data.read_data_sets("F:\MNIST_data",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)
print('example training data label:',mnist.train.labels[0])


train 集合内有 55000 张图片,validation集合内有 5000 张图片,test集合内有 10000 张图片。每一张图片是一个长度为784的一维数组,对应了图片像素矩阵中的每一个数字(28*28 = 784)。

为了方便使用随机梯度下降,input_data.read_data_setsinput_data.read_data_sets 函数生成的类还提供了 mnist.train.next_batchmnist.train.next_batch 函数,它可以从所有的训练数据中读取一小部分作为一个训练 batchbatch。代码如下:

batch_size = 100
#该函数每次执行都会从数据集中顺序读取
xs, ys = mnist.train.next_batch(batch_size)

# 从train集合中选取 batch_size 个训练数据
print("X shape:",xs.shape)
print("Y shape:",ys.shape)

OUT:
X shape: (100, 784)
Y shape: (100, 10)


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

5.2.1、TensorFlow 训练神经网络

给出一个完整的 Tensorflow 程序来解决 MNIST 手写体数字识别问题。

回顾第4章提到的主要概念。在神经网络的结构上,深度学习一方面需要使用激活函数实现神经网络模型的去线性化,另一方面需要使用一个或多个隐藏层使得神经网络的结构更深,以解决复杂问题。在训练神经网络时,第4章介绍了使用带指数衰减的学习率设置、使用正则化来避免过拟合,以及使用滑动平均模型来使得最终模型更加健壮。以下代码给出了一个在 MNIST 数据集上实现这些功能的完整的 TensorFlow 程序。

Click here to get the code:

【神经网络实现 MNIST 手写体识别】

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

将上一节中的代码做略微调整,使 validate_set 和 test_set 的正确率同时显示,发现 validate dataset 分布接近 test dataset 分布,可验证模型在验证数据上的表现可以很好的体现模型在测试数据上的表现。因此,对于验证数据的选择很重要。

#迭代地训练神经网络
for i in range(0,TRAINING_STEPS):
if i % 1000 == 0:
validate_acc = sess.run(accuracy, feed_dict = validate_feed)
test_acc = sess.run(accuracy, feed_dict = test_feed)
print("在 %d 次迭代后,验证数据集的正确率为 : %g , 测试数据集的正确率为 : %g" % (i, validate_acc,test_acc))


在 0 次迭代后,验证数据集的正确率为 : 0.093 , 测试数据集的正确率为 : 0.0965
在 1000 次迭代后,验证数据集的正确率为 : 0.9772 , 测试数据集的正确率为 : 0.9764
在 2000 次迭代后,验证数据集的正确率为 : 0.9828 , 测试数据集的正确率为 : 0.9804




5.2.3、不同模型效果比较

在第4章中,提到了设计神经网络时的 5 种优化方法。在神经网络结构的设计上,需要使用激活函数和多层隐藏层。在神经网络优化时,可以使用指数衰减的学习律、加入正则化的损失函数以及滑动平均模型。在图 5-3 中,给出了在相同神经网络参数下,使用不同优化方法没经过 30000 轮训练迭代后,得到的最终模型的正确率。



从图中的反映可以说明,神经网络的结构对最终模型的效果有本质性的影响。

我们看到,滑动平均模型和指数衰减的学习率对正确率的结果似乎没有什么影响,这是因为滑动平均模型和指数衰减的学习率在一定程度上都是限制神经网络中参数更新的速度,在 MNIST 数据集中,迭代在4000轮的时候就已经接近收敛,收敛速度很快,所以这两种优化对最终模型的影响不大。但是当问题更加复杂时,迭代不会这么快接近收敛,这时滑动平均模型和指数衰减的学习率可以发挥出更大的作用。比如 Cifar-10 图像分类数据集,使用滑动平均模型可以将错误率降低 11%,而使用指数衰减的学习率可以将错误率降低 7%。

正则化损失对于模型的影响较大。



5.3、变量管理

前面,将前向传播过程抽象成了一个函数,在训练和测试的过程中可以统一调用同一个函数来得到模型的前向传播结果。这个函数的定义为:

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2)


从定义中可以看到,这个函数的参数中包括了神经网络中的所有参数。然而,当网络结构更加复杂、参数更多时,就需要一个更好的方式来传递和管理神经网络中的参数了。TensorFlow 提供了通过变量名称来创建或获取一个变量的机制。通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式到处传递。 TensorFlow 中通过变量名称获取变量的机制主要是通过 tf.get_variabletf.get_variable 和 tf.variable_scopetf.variable_scope函数实现的。

tf.get_variabletf.get_variable 和 tf.Variabletf.Variable 的功能时基本等价的。

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


两个函数创建变量的过程基本上是一样的。tf.get_variabletf.get_variable 函数调用时提供的维度(shape)信息以及初始化方法(initializer)的参数和 tf.Variabletf.Variable 函数调用时提供的初始化过程中的参数也类似。 TensorFlow 中提供的 initializerinitializer 函数 同 3.4.3 小节中介绍地随机数以及常量生成函数大部分是一一对应的。 比如常数初始化函数 tf.constantinitializertf.constantinitializer 和常数生成函数 tf.constanttf.constant 功能上就是一致地。 TensorFlow 提供了 7 种不同地初始化函数,如下:



tf.get_variabletf.get_variable 函数与 tf.Variabletf.Variable 函数最大的区别在于指定变量名称的参数。对于 tf.Variabletf.Variable 函数,变量名称是一个可选的参数,通过 name=”v” 的形式给出。但是对于 tf.get_variabletf.get_variable 函数,变量名称是一个必填的参数, tf.get_variabletf.get_variable 首先会试图取创建一个名字为 v 的参数,如果创建失败(比如已经有同名的参数),那么这个程序就会报错。这是为了避免无意识的变量复用造成的错误。比如在定义神经网络参数时,第一层网络权重已经叫 weights 了,那么在创建第二层神经网络时,如果参数名仍叫 weights,就会触发变量重用的错误。否则两层神经网络共用一个权重会出现一些比较难以发现的错误。

如果通过 tf.get_variabletf.get_variable 获取一个已经创建的变量,需要通过 tf.variable_scope函数tf.variable_scope函数 来生成一个上下文管理器,并明确指定在这个上下文管理器中, tf.get_variabletf.get_variable 将直接获取已经声称的变量。下面给出了一段代码说明如何通过 tf.variablescopetf.variablescope 函数来控制 tf.get_variabletf.get_variable 函数获取已经创建过的变量。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 12 16:28:38 2018

@author: 无尾君
"""
import tensorflow as tf

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

# 下面代码会报错,因为命名空间 foo 中已经存在名字为 v 的变量
# ValueError: Variable foo/v already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope? Originally defined at:
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('a', [1])
print(v == v1) #输出为 True, 代表v, v1 代表的是相同的 TensorFlow 中变量

# 当参数 reuse 设置为 True 时, tf.variable_scope 将只能获取已经创建过的变量。
# 因为在命名空间 bar 中还咩有创建变量 v,所以下面的代码会报错
# ValueError: Variable bar/v does not exist, or was not created with tf.get_variable(). Did you mean to set reuse=tf.AUTO_REUSE in VarScope?
with tf.variable_scope('bar', reuse = True):
v = tf.get_variable("v", [1])


样例简单说明了通过 tf.variablescopetf.variablescope 函数可以空值 tf.get_variabletf.get_variable 函数的语义。 当参数 reuse 为 False 或 None 时创建上下文管理器,将创建新的变量。如果同名变量已经存在则报错。 tf.variable_scopetf.variable_scope 是可以嵌套的。下面程序说明了当 tf.variable_scopetf.variable_scope 函数嵌套时, resuse 参数的取值是如何确定的。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 12 16:28:38 2018

@author: 无尾君
"""
import tensorflow as tf

with tf.variable_scope("root"):
#可以通过 tf.get_variable_scope().reuse 函数来获得当前上下文管理器中 reuse 参数的值
print(tf.get_variable_scope().reuse) # 输出 False,即最外层 reuse 是 False

with tf.variable_scope('foo', reuse = True):
print(tf.get_variable_scope().reuse) #输出True

with tf.variable_scope('bar'): # 新建一个嵌套的上下文管理器但不指定 reuse,#这时reuse 的取值会和外面一层保持一致
print (tf.get_variable_scope().reuse) # 输出 True
print(tf.get_variable_scope().reuse) # 退出 reuse设置为 True的上下文之后, reuse的值又回到了False


tf.variable_scopetf.variable_scope 函数声称的上下文管理器也会创建一个 TensorFlow 中的命名空间,在命名空间内创建的变量名称都会带上这个命名空间名作为前缀。所以 tf.variable_scopetf.variable_scope 函数除了可以控制 tf.get_variabletf.get_variable 执行的功能之外, 也提供了一个管理变量命名空间的方式。代码如下,

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 12 16:28:38 2018

@author: 无尾君
"""
import tensorflow as tf

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

with tf.variable_scope('foo'):
v2 = tf.get_variable('v', [1])
print(v2.name) # 输出 foo/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, 调用了 该name的变量,v5输出该name下的value

v6 = tf.get_variable('foo/v1', [1])
print(v6 == v4) # 输出 True


通过 tf.variable_scope 和 tf.get_variable 函数,以下代码对 5.2.1 小节中定义的计算前向传播结果的函数做了一些改进。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 12 16:28:38 2018

@author: 无尾君
"""
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_trnsor, weights) + biases)

#类似的定义第二层神经网络的变量和前向传播过程。
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.nn.relu(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)、
# 如果需要使用滑动平均模型可以参考 5.2.1 小节中使用的代码,把滑动平均的类传到 inference 函数中即可。获取或创建变量的部分不需要改变。

new_x = ...
new_y = inference(new_x, True)


使用上面这段代码所示的方式,就不需要再将所有变量都作为参数传递到不同的函数中了。当神经网络结构更加复杂、参数更多时,使用这种变量管理的方式将大大提高程序的可读性。

5.4 TensorFlow 模型持久化

5.2.1小节中给出的样例代码在训练完成之后就直接退出了,并没有将训练得到的模型保存下来方便下次直接使用。为了让训练结果可以复用,需要将训练得到的神经网络模型持久化。

5.4.1 持久化代码实现

TensorFLow 提供了一个非常简单的 API 来保存和还原一个神经网络模型。这个 API 就是 tf.train.Saver 类。以下代码给出了保存 TensorFlow 计算图的方法。

# -*- coding: utf-8 -*-
"""
Created on Thu Apr 12 16:28:38 2018

@author: 无尾君
"""
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')

#声明 tf.train.Saver() 用于保存模型
saver = tf.train.Saver()

with tf.Session() as sess:
tf.global_variables_initializer().run()
# 将模型保存到/MODEL/model.ckpt 文件
save_path = saver.save(sess,"./MODEL/5.4.1_model.ckpt")
print("Model saved in file: %s" % save_path)




在指定的保存路径下,生成了三个文件。这是因为 TensorFlow 会将计算图的结构和图上参数取值分开保存。

第一个文件为 5.4.1_model.ckpt.meta,它保存了 TensorFlow 计算图的结构,即神经网络的网络结构。

第二个文件为 5.4.1_model.ckpt.index,I DON’T KNOW

第三个文件为 5.4.1_model.ckpt.data-00000-of-00001,保存了每一个变量的取值

最后一个文件为 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, 'my_model/model.ckpt') 
print('\n输出在这里看啊看啊',sess.run(result)) # 输出3

#无论是保存还是输出,操作前 restart kernel 一下


加载模型的代码中没有运行变量的初始化过程,而是将变量的值通过已经保存的模型加载进来。如果不希望重复定义图上的运算,也可以直接加在已经持久化的图。代码如下:

四、TensorFlow 模型持久化

五、TensorFlow 最佳实践样例程序

小结

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  TensorFlow
相关文章推荐