目录
第五章——MNIST数字识别问题
本章将使用的数据集是 MNIST手写体数字识别数据集。在很多深度学习教程中,这个数据集都会被当作第一个案例。在验证神经网络优化方法的同时,本章也会介绍使用 TensorFlow 训练神经网络的最佳实践。
5.1 MNIST数据处理
MNIST 是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。MNIST 数据集是 NIST 数据集的一个子集,它包含了 60000 张图片作为训练数据, 10000 张图片作为测试数据。在 MNIST 数据集中的每一张图片都代表了 0~9 中的一个数字,标签集用 ont_hot 编码表示手写数字(例:表示数字3→[0,0,0,1,0,0,0,0,0,0])。图片的大小都为 28x28, 且数字都会出现在图片的正中间。
TensorFlow提供了一个类来处理 MNIST 数据。这个类会自动下载并转换MNIST数据的格式,将数据从原始的数据包中解析成训练和测试神经网络时使用的格式。原数据集无验证集,但TensorFlow会从训练集中分出部分作为验证集,下面给出使用这个函数的样例程序。
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) #55000
print('Validating data size', mnist.validation.num_examples) #5000
print('Testing data size', mnist.test.num_examples) #10000
#分别输出示例图片数据及对应标签
print('example training data :',mnist.train.images[0]) #28*28=784 一维数组
print('example training data label:',mnist.train.labels[0])
为了方便使用随机梯度下降,input_data.read_data_sets函数生成的类还提供了 mnist.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 程序。
这里的程序是将所有流程写在一个py文件中,在本章最后,会进行优化划分得到一个最佳示例。
5.2.2使用验证数据集判断模型效果
一般两种方式来判断模型效果:
交叉验证(cross validation)是在机器学习建立模型和验证模型参数时常用的办法。交叉验证,顾名思义,就是重复的使用数据,把得到的样本数据进行切分,组合为不同的训练集和测试集,用训练集来训练模型,用测试集来评估模型预测的好坏。在此基础上可以得到多组不同的训练集和测试集,某次训练集中的某样本在下次可能成为测试集中的样本,即所谓“交叉”。常用于数据量少时。
划分为固定的训练集,验证集,测试集,用验证集来进行验证判断。 常用于数据量比较充足时。
将上一节中的代码做略微调整,使 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))
5.2.3不同模型效果的比较
这里是指使用不同的优化方法,网络结构不变,最终训练结果的不同。第四章共提到五种优化方法:在神经网络结构的设计上,需要使用激活函数与多层隐藏层,在神经网络优化时,可以使用指数衰减的学习率,加入正则化的损失函数,加入滑动平均模型。
从上图可以看出,神经网络结构对最终模型的效果具有本质性的影响。
我们看到,滑动平均模型和指数衰减的学习率对正确率的结果似乎没有什么影响,这是因为滑动平均模型和指数衰减的学习率在一定程度上都是限制神经网络中参数更新的速度,在 MNIST 数据集中,迭代在4000轮的时候就已经接近收敛,收敛速度很快,所以这两种优化对最终模型的影响不大。但是当问题更加复杂时,迭代不会这么快接近收敛,这时滑动平均模型和指数衰减的学习率可以发挥出更大的作用。比如 Cifar-10 图像分类数据集,使用滑动平均模型可以将错误率降低 11%,而使用指数衰减的学习率可以将错误率降低 7%。
正则化损失对于模型的影响较大。
在图 5-6 中灰色(淡色)和黑色(深色)的实线给出了两个模型正确率的变化趋势, 虚线给出了在当前训练batch 上的交叉熵损失。
从图5-6可以看出,不使用正则化损失的话,在训练集上只优化交叉熵(灰色虚线)确实比优化总损失(黑色虚线)的损失值要小,但是在测试数据的正确率上,反而是优化总损失(黑色实线)的正确率高。
总的来说,各种优化方式,首先使用激活函数和隐藏层是必要的;其次,指数衰减的学习率,加入正则化的损失函数,加入滑动平均模型这三种优化方法,对于不同问题的提升效果是不同的,需要具体问题具体验证分析。
5.3 变量管理
在5.2.1节中,将前向传播的过程抽象为一个函数,通过这种方式在训练和测试的过程中可以统一调用同一个函数来得到模型的前向传播结果。如下:
def inference(input_tensor,avg_class,weights1,biases1,weights2,biases2):
这个函数的参数包括了神经网络的所有参数(weights1,biases1,weights2,biases2),但是当网络结构复杂时,上述方式不再适用,需要更好的方式来传递和管理神经网络中的参数。
TensorFlow提供了通过变量名称来创建或者获取一个变量的机制,通过这个机制,在不同的函数中可以直接通过变量的名字来使用变量,而不需要将变量通过参数的形式到处传递。该机制主要通过下列两个函数实现。
重要函数:tf.get_variable()与tf.variable_scope()
#下列两个函数功能基本等价,都是创建变脸
#第四章所用
v=tf.Variable(tf.constant(1.0,shape=[1]),name='v')
#第五章所学重点函数
v=tf.get_variable('v',shape=[1],initializer=tf.constant_initializer(1.0))
两个函数创建变量的过程基本上是一样的。tf.get_variable函数调用时提供的维度(shape)信息以及初始化方法(initializer)的参数和 tf.Variable 函数调用时提供的初始化过程中的参数也类似。 TensorFlow 中提供的 initializer 函数同 3.4.3 小节中介绍地随机数以及常量生成函数大部分是一一对应的。 比如变量初始化函数 tf.constant_initializer 和常数生成函数 tf.constant 功能上就是一致地。 TensorFlow 提供了 7 种不同地初始化函数,如下:
tf.get_variable()与tf.Variable()函数最大的区别在于变量名参数,对于前者是必须的,对后者是可选的,通过name='v'的形式给出!
在上面的案例程序中,tf.get_variavle()会先试图去创建一个名字为v的参数,若创建失败(比如已经存在同名的参数),那么程序会报错,这是为了防止无意识的变量复用造成的错误。比如在定义神经网络参数时,第一层网络权重已经叫 weights 了,那么在创建第二层神经网络时,如果参数名仍叫 weights,就会触发变量