购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.2 利用TensorFlow识别MNIST

在第 1.1节中,我们已经对 MNIST数据集和 TensorFlow中 MNIST数据集的载入有了基本的了解。本节将真正以 TensorFlow 为工具,写一个手写体数字识别程序,使用的机器学习方法是Softmax回归。

1.2.1 Softmax回归

1.Softmax回归的原理

Softmax 回归是一个线性的多类分类模型,实际上它是直接从 Logistic回归模型转化而来的。区别在于 Logistic 回归模型为两类分类模型,而Softmax模型为多类分类模型。

在手写体识别问题中,一共有 10 个类别(0~9),我们希望对输入的图像计算它属于每个类别的概率。如属于 9 的概率为 70%,属于 1 的概率为10%等。最后模型预测的结果就是概率最大的那个类别。

先来了解什么是 Softmax函数。Softmax函数的主要功能是将各个类别的“打分”转化成合理的概率值。例如,一个样本可能属于三个类别:第一个类别的打分为a,第二个类别的打分为b,第三个类别的打分为c。打分越高代表属于这个类别的概率越高,但是打分本身不代表概率,因为打分的值可以是负数,也可以很大,但概率要求值必须在0~1,并且三类的概率加起来应该等于1。那么,如何将(a,b,c)转换成合理的概率值呢?方法就是使用Softmax 函数。例如,对(a,b,c)使用 Softmax 函数后,相应的值会变成 ,也就是说,第一类的概率可以用 表示,第二类的概率可以用 表示,第三类的概率可以用 表示。显然,这三个数值都在0~1之间,并且加起来正好等于1,是合理的概率表示。

假设x是单个样本的特征,W、b是Softmax模型的参数。在MNIST数据集中,x就代表输入图片,它是一个784维的向量,而W是一个矩阵,它的形状为(784,10),b是一个10维的向量,10代表的是类别数。Softmax模型的第一步是通过下面的公式计算各个类别的Logit:

Logit=W T x+b

Logit同样是一个10维的向量,它实际上可以看成样本对应于各个类别的“打分”。接下来使用Softmax函数将它转换成各个类别的概率值:

y=Softmax(Logit)

Softmax模型输出的y代表各个类别的概率,还可以直接用下面的式子来表示整个Softmax模型:

y=Softmax(W T x+b)

2.Softmax回归在TensorFlow中的实现

本节对应的程序为softmax_regression.py,在该程序中,使用TensorFlow定义了一个 Softmax 模型,实现了 MNIST 数据集的分类。首先导入TensorFlow模块:

导入TensorFlow的语句一般写作:import tensorflow as tf。这是一种约定俗成的写法。请记住这条语句,它将在后面的每一章中重复出现。

接下来和之前一样,导入MNIST数据库:

下面的步骤是非常关键的几步,先来看代码:

这里定义了一些 占位符和变量( Variable 。在 TensorFlow 中,无论是占位符还是变量,它们实际上都是“Tensor”。从 TensorFlow 的名字中,就可以看出 Tensor在整个系统中处于核心地位。TensorFlow中的Tensor并不是具体的数值,它只是一些我们“希望”TensorFlow系统计算的“节点”。

这里的占位符和变量是不同类型的Tensor。先来讲解占位符。占位符不依赖于其他的Tensor,它的值由用户自行传递给TensorFlow,通常用来存储样本数据和标签。如在这里定义了x=tf.placeholder(tf.float32,[None,784]),它是用来存储训练图片数据的占位符。它的形状为[None,784],None 表示这一维的大小可以是任意的,也就是说可以传递任意张训练图片给这个占位符,每张图片用一个784维的向量表示。同样的,y_=tf.placeholder(tf.float32,[None,10])也是一个占位符,它存储训练图片的实际标签。

再来看什么是变量。变量是指在计算过程中可以改变的值,每次计算后变量的值会被保存下来,通常用变量来存储模型的参数。如这里创建了两个变量:W=tf.Variable(tf.zeros([784,10]))、b=tf.Variable(tf.zeros([10]))。它们都是 Softmax 模型的参数。创建变量时通常需要指定某些初始值。这里 W的初始值是一个784×10的全零矩阵,b的初始值是一个10维的0向量。

除了变量和占位符之外,还创建了一个y=tf.nn.softmax(tf.matmul(x,W)+b)。这个y就是一个依赖x、W、b的Tensor。如果要求TensorFlow计算y的值,那么系统首先会获取x、W、b的值,再去计算y的值。

y实际上定义了一个Softmax回归模型,在此可以尝试写出y的形状。假设输入x的形状为(N,784),其中N表示输入的训练图像的数目。W的形状为(784,10),b的形状为(10,) 。那么,Wx+b的形状是(N,10)。Softmax函数不改变结果的形状,所以得到y的形状为(N,10)。也就是说,y的每一行是一个10维的向量,表示模型预测的样本对应到各个类别的概率。

模型的输出是y,而实际的标签为y_,它们应当越相似越好。在Softmax回归模型中,通常使用“交叉熵”损失来衡量这种相似性。损失越小,模型的输出就和实际标签越接近,模型的预测也就越准确。

在TensorFlow中,这样定义交叉熵损失:

构造完损失之后,下面一步是如何优化损失,让损失减小。这里使用梯度下降法优化损失,定义为

TensorFlow 默认会对所有变量计算梯度。在这里只定义了两个变量 W和 b,因此程序将使用梯度下降法对 W、b 计算梯度并更新它们的值。tf.train.GradientDescentOptimizer(0.01)中的 0.01 是梯度下降优化器使用的学习率(Learning Rate)。

在优化前,必须要创建一个会话(Session),并在会话中对变量进行初始化操作:

会话是 TensorFlow 的又一个核心概念。前面提到 Tensor 是“希望”TensorFlow进行计算的结点。而会话就可以看成对这些结点进行计算的上下文。之前还提到过,变量是在计算过程中可以改变值的Tensor,同时变量的值会被保存下来。事实上,变量的值就是被保存在会话中的。在对变量进行操作前必须对变量进行初始化,实际上是在会话中保存变量的初始值。初始化所有变量的语句是tf.global_variables_initializer().run()。

有了会话,就可以对变量W、b进行优化了,优化的程序如下:

每次不使用全部训练数据,而是每次提取100个数据进行训练,共训练1000次。batch_xs,batch_ys分别是100个训练图像及其对应的标签。在训练时,需要把它们放入对应的占位符 x,y_中,对应的语句是 feed_dict={x:batch_xs,y_:batch_ys}。

在会话中,不需要系统计算占位符的值,而是直接把占位符的值传递给会话。与变量不同的是,占位符的值不会被保存,每次可以给占位符传递不同的值。

运行完梯度下降后,可以检测模型训练的结果,对应的代码如下:

模型预测y的形状是(N,10),而实际标签y_的形状是(N,10),其中N为输入模型的样本个数。tf.argmax(y,1)、tf.argmax(y_,1)的功能是取出数组中最大值的下标,可以用来将独热表示以及模型输出转换为数字标签。假设传入四个样本,它们的独热表示y_为(需要通过sess.run(y_)才能获取此Tensor的值,下同):

tf.argmax(y_,1)就是:

也就是说,取出每一行最大值对应的下标位置,它们是输入样本的实际标签。假设此时模型的预测输出y为:

tf.argmax(y_,1)就是:

得到了预测的标签和实际标签,接下来通过 tf.equal 函数来比较它们是否相等,并将结果保存到 correct_prediction 中。在上述例子中,correct_prediction就是:

即第一个样本和最后一个样本预测是正确的,另外两个样本预测错误。可以用tf.cast(correct_prediction,tf.float32)将比较值转换成float32型的变量,此时 True 会被转换成 1,False 会被转换成 0。在上述例子中,tf.cast(correct_prediction,tf.float32)的结果为:

最后,用tf.reduce_mean可以计算数组中的所有元素的平均值,相当于得到了模型的预测准确率,如[1.,0.,0.,1.]的平均值为0.5,即50%的分类准确率。

在程序 softmax_regression.py 中,传入占位符的值是 feed_dict={x:mnist.test.images,y_:mnist.test.labels}。也就是说,使用全体测试样本进行测试。测试图片一共有10000张,运行的结果为0.9185,即91.85%的准确率。因为Softmax回归是一个比较简单的模型,这里预测的准确率并不高,在下一节将学习如何使用卷积神经网络将预测的准确率提高到99%。

1.2.2 两层卷积网络分类

本节对应的程序文件是convolutional.py,将建立一个卷积神经网络,它可以把MNIST手写字符的识别准确率提高到99%,读者可能需要一些卷积神经网络的基础知识才能更好地理解本节的内容。

程序的开头依旧是导入TensorFlow:

接下来载入MNIST数据,并建立占位符。占位符x的含义为训练图像,y_为对应训练图像的标签,这与上文是一样的。

由于使用的是卷积网络对图像进行分类,所以不能再使用784维的向量表示输入的x,而是将其还原为28×28的图片形式。[-1,28,28,1]中的-1表示形状第一维的大小是根据x自动确定的。

x_image就是输入的训练图像,接下来,我们对训练图像进行卷积计算,第一层卷积的代码如下:

先定义了四个函数,函数weight_variable可以返回一个给定形状的变量并自动以截断正态分布初始化,bias_variabale同样返回一个给定形状的变量,初始化时所有值是 0.1,可分别用这两个函数创建卷积的核(kernel)与偏置(bias)。h_conv1=tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)是真正进行卷积计算,卷积计算后选用 ReLU 作为激活函数。h_pool1=max_pool_2x2(h_conv1)是调用函数max_pool_2x2进行一次池化操作。卷积、激活函数、池化,可以说是一个卷积层的“标配”,通常一个卷积层都会包含这三个步骤,有时也会去掉最后的池化操作。

对第一次卷积操作后产生的h_pool1再做一次卷积计算,使用的代码与上面类似。

两层卷积层之后是全连接层:

在全连接层中加入了 Dropout,它是防止神经网络过拟合的一种手段。在每一步训练时,以一定概率“去掉”网络中的某些连接,但这种去除不是永久性的,只是在当前步骤中去除,并且每一步去除的连接都是随机选择的。在这个程序中,选择的 Dropout 概率是 0.5,也就是说训练时每一个连接都有50%的概率被去除。在测试时保留所有连接。

最后,再加入一层全连接,把上一步得到的h_fc1_drop转换为10个类别的打分。

y_conv相当于 Softmax模型中的 Logit,当然可以使用 Softmax函数将其转换为10个类别的概率,再定义交叉熵损失。但其实TensorFlow提供了一个更直接的 tf.nn.softmax_cross_entropy_with_logits 函数,它可以直接对Logit定义交叉熵损失,写法为

定义测试的准确率(和第1.2.1节类似):

训练过程同样与第 1.2.1 节类似,不同点在于这次会额外在验证集上计算模型的准确度并输出,方便监控训练的进度,也可以据此来调整模型的参数。

训练结束后,打印在全体测试集上的准确率:

得到的准确率结果应该在99%左右。与Softmax回归模型相比,使用两层卷积的神经网络模型借助了卷积的威力,准确率有非常大的提升。本节的程序同第1.2.1节在流程上非常相似,都是先读入MNIST数据集,再定义训练数据的占位符(x和 y_),以 x为输入定义模型,最后定义损失,进行训练。 TmHLeZuc9vqSDBacffFm2pmWLddBY1ubjXqsicyDs0RcpjZ2zpttyo4i0Ossc7y5

点击中间区域
呼出菜单
上一章
目录
下一章
×