DL之Example1--MNIST

DL之Example1–MNIST

以MNIST数据集,用Tensorflow读取数据集中的数据,并建立一个简单的图像识别模型,同时介绍Tensorflow的几个核心概念

一.MNIST数据集

1.简介

MNIST数据集主要有一些手写数字的图片和相应的标签组成,图片一共有10类.分别对应从0-9.共10个阿拉伯数字

原始的MNIST数据库一共包含下面4个文件,如下表:

文件名 大小 用途
train-images-idx3-ubyte.gz 9.7MB 训练图像数据
train-labels-idx1-ubyte.gz 29KB 训练图像的标签
t10k-images-idx3-ubyte.gz 1.6MB 测试图像数据
t10k-labels-idx1-ubyte.gz 5KB 测试图像的标签

图像数据是指很多张手写字符的图像,图像的标签是每一张图像实际对应的数字是几,也就是说,在MNIST数据集中的每一张图像都事先标明了对应的数字

在Tensorflow中,可以使用下面的代码下载MNIST数据:

# -*- coding:utf-8 -*-

# 从 tensorflow.examples.tutorials.mnist 引入模块
from tensorflow.examples.tutorials.mnist import input_data
# data/mnist中读取MNIST数据,这条语句在数据不存在时,会自动执行下载
mnist=input_data.read_data_sets("./data/mnist",one_hot=True)
Extracting ./data/mnist\train-images-idx3-ubyte.gz
Extracting ./data/mnist\train-labels-idx1-ubyte.gz
Extracting ./data/mnist\t10k-images-idx3-ubyte.gz
Extracting ./data/mnist\t10k-labels-idx1-ubyte.gz

成功加载MNIST数据集后,得到了一个mnist对象,可以通过mnist对象的属性访问到MNIST数据集

属性名 内容 大小
mnist.train.images 训练图像 (55000,784)
mnist.train.labels 训练标签 (55000,10)
mnist.validation.images 验证图像 (5000,784)
mnist.validation.labels 验证标签 (5000,10)
mnist.test.images 测试图像 (10000,784)
mnist.test.labels 测试标签 (10000,10)
# 查看训练数据的大小
print(mnist.train.images.shape)
print(mnist.train.labels.shape)
(55000, 784)
(55000, 10)
# 查看验证数据的大小
print(mnist.validation.images.shape)
print(mnist.validation.labels.shape)
(5000, 784)
(5000, 10)
# 查看测试数据的大小
print(mnist.test.images.shape)
print(mnist.test.labels.shape)
(10000, 784)
(10000, 10)

原始的MNIST数据集中包含了60000张训练图片和10000张测试图片.而在Tensorflow中,又将原先的60000张训练图片重新划分成了新的55000张训练图片和5000张验证图片.所以在mnist对象中,数据一共分为三部分:mnist.train是训练图片的数据,mnist.validation是验证图片数据,mnist.test是测试图片的数据,这正好对应了机器学习中的训练集,验证集和测试集.一般来说,会在训练集上训练模型,通过模型在验证集上的表现调整canshu,最后通过测试集确定模型的性能

2.实验:将MNIST数据集保存为图片

在原始的MNIST数据集中,每张图片都由一个28*28的矩阵表示

DL之Example1--MNIST

在Tensorflow中,变量mnist.train.images是训练样本,它的形状为(55000,784).其中,55000是训练图像的个数,而784实际上为单个样本的维数,即每张图片都由一个784维的向量表示(784正好等于28*28).可以使用以下代码打印出第0张训练图片对应的向量表示:

# 打印出第0张图片的向量表示
print(mnist.train.images[0,:])
[0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.3803922  0.37647063 0.3019608
 0.46274513 0.2392157  0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.3529412
 0.5411765  0.9215687  0.9215687  0.9215687  0.9215687  0.9215687
 0.9215687  0.9843138  0.9843138  0.9725491  0.9960785  0.9607844
 0.9215687  0.74509805 0.08235294 0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.54901963 0.9843138  0.9960785  0.9960785
 0.9960785  0.9960785  0.9960785  0.9960785  0.9960785  0.9960785
 0.9960785  0.9960785  0.9960785  0.9960785  0.9960785  0.9960785
 0.7411765  0.09019608 0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.8862746  0.9960785  0.81568635 0.7803922  0.7803922  0.7803922
 0.7803922  0.54509807 0.2392157  0.2392157  0.2392157  0.2392157
 0.2392157  0.5019608  0.8705883  0.9960785  0.9960785  0.7411765
 0.08235294 0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.14901961 0.32156864
 0.0509804  0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.13333334 0.8352942  0.9960785  0.9960785  0.45098042 0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.32941177
 0.9960785  0.9960785  0.9176471  0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.32941177 0.9960785  0.9960785
 0.9176471  0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.4156863  0.6156863  0.9960785  0.9960785  0.95294124 0.20000002
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.09803922
 0.45882356 0.8941177  0.8941177  0.8941177  0.9921569  0.9960785
 0.9960785  0.9960785  0.9960785  0.94117653 0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.26666668 0.4666667  0.86274517 0.9960785  0.9960785
 0.9960785  0.9960785  0.9960785  0.9960785  0.9960785  0.9960785
 0.9960785  0.5568628  0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.14509805 0.73333335 0.9921569
 0.9960785  0.9960785  0.9960785  0.8745099  0.8078432  0.8078432
 0.29411766 0.26666668 0.8431373  0.9960785  0.9960785  0.45882356
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.4431373  0.8588236  0.9960785  0.9490197  0.89019614 0.45098042
 0.34901962 0.12156864 0.         0.         0.         0.
 0.7843138  0.9960785  0.9450981  0.16078432 0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.6627451  0.9960785
 0.6901961  0.24313727 0.         0.         0.         0.
 0.         0.         0.         0.18823531 0.9058824  0.9960785
 0.9176471  0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.07058824 0.48627454 0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.32941177 0.9960785  0.9960785  0.6509804  0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.54509807
 0.9960785  0.9333334  0.22352943 0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.8235295  0.9803922  0.9960785  0.65882355
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.9490197  0.9960785  0.93725497 0.22352943 0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.34901962 0.9843138  0.9450981
 0.3372549  0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.01960784 0.8078432  0.96470594 0.6156863  0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.01568628 0.45882356
 0.27058825 0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.        ]

为了加深这种表示的理解,下面完成一个简单的程序:将MNIST数据集读取出来,并保存为图片文件

from PIL import Image
import numpy as np
import os

# 将原始图片保存在mnist/raw文件夹下
save_dir="./data/mnist/raw/"

if os.path.exists(save_dir) is False:
    os.makedirs(save_dir)
    
# 保存前20张图片
for i in range(20):
    # 第i张图片(序号从0开始)
    image_array=mnist.train.images[i,:]
    
    # 重新把它还原为28*28维的图像
    image_array=image_array.reshape(28,28)
    
    filename=save_dir+'mnist_train_%d.jpg'%i
    
    Image.fromarray((image_array*256).astype(np.uint8)).save(filename)

DL之Example1--MNIST

3.图像标签的独热表示

变量mnist.train.labels表示训练图像的标签,它的形状是(55000,10).原始的图像标签是数字0-9,我们完全可以用一个数字来存储图像的标签,但为什么这里每个训练标签是一个10维的向量呢?其实,这个10维的向量是原先类别号的独热(one-hot)表示

所谓独热表示,就是"一位有效编码".我们用N维的向量来表示N个类别,每个类别占据独立的一位,任何时候独热表示中只有一位是1,其他都为0.直接从下表理解独热表示:

原始表示(0~9,共10个类别) 独热表示
0 [1,0,0,0,0,0,0,0,0,0]
1 [0,1,0,0,0,0,0,0,0,0]
2 [0,0,1,0,0,0,0,0,0,0]
9 [0,0,0,0,0,0,0,0,0,1]

运行下面的代码可以打印出第0张训练图片的标签:

print(mnist.train.labels[0,:])
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]

代码运行的结果是[0. 0. 0. 0. 0. 0. 0. 1. 0. 0.],也就是说第0张图片对应的标签为数字"7"

此外,我们可以打印前20张图片的标签,我们可以尝试与前面保存在/data/mnist/raw的照片对照,查看图像与图像的标签是否正确地对应上了

# -*- coding:utf-8 -*-

from tensorflow.examples.tutorials.mnist import input_data
import numpy as np

mnist=input_data.read_data_sets("./data/mnist/",one_hot=True)

# 看前20张训练图片的label
for i in range(20):
    # 得到独热表示,形如(0,1,0,0,0,0,0,0,0,0)
    ont_hot_label=mnist.train.labels[i,:]
    # 通过np.argmax,可以直接获得原始的label
    label=np.argmax(ont_hot_label)
    print('mnist_train_%d.jpg label:%d'%(i,label))
Extracting ./data/mnist/train-images-idx3-ubyte.gz
Extracting ./data/mnist/train-labels-idx1-ubyte.gz
Extracting ./data/mnist/t10k-images-idx3-ubyte.gz
Extracting ./data/mnist/t10k-labels-idx1-ubyte.gz
mnist_train_0.jpg label:7
mnist_train_1.jpg label:3
mnist_train_2.jpg label:4
mnist_train_3.jpg label:6
mnist_train_4.jpg label:1
mnist_train_5.jpg label:8
mnist_train_6.jpg label:1
mnist_train_7.jpg label:0
mnist_train_8.jpg label:9
mnist_train_9.jpg label:8
mnist_train_10.jpg label:0
mnist_train_11.jpg label:3
mnist_train_12.jpg label:1
mnist_train_13.jpg label:2
mnist_train_14.jpg label:7
mnist_train_15.jpg label:0
mnist_train_16.jpg label:2
mnist_train_17.jpg label:9
mnist_train_18.jpg label:6
mnist_train_19.jpg label:0

现在,我们应当对变量mnist.train.images和mnist.train.labels很熟悉了.对于剩下的mnist.validation.images,mnist.validation.labels,mnist.test.images和mnist.test.labels四个变量与它们非常类似,唯一的区别只是图像的个数不同

二.利用Tensorflow识别MNIST

我们将以Tensorflow为工具,写一个手写体数字识别程序,使用的机器学习方法是Softmax回归

1.Softmax回归

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函数后,相应的值会变成:
DL之Example1--MNIST

也就是说,第一类的概率可以用:
DL之Example1--MNIST

下面第二类,第三类类推.显然,这三个数值都在0-1之间,并且加起来正好等于1,是合理的概率表示

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

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

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

Softmax回归在Tensorflow中的实现

使用Tensorflow定义一个Softmax模型,实现了MNIST数据集的分类.首先导入Tensorflow模块

# -*- coding:utf-8 -*-

# 导入Tensorflow
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist=input_data.read_data_sets("./data/mnist/",one_hot=True)
Extracting ./data/mnist/train-images-idx3-ubyte.gz
Extracting ./data/mnist/train-labels-idx1-ubyte.gz
Extracting ./data/mnist/t10k-images-idx3-ubyte.gz
Extracting ./data/mnist/t10k-labels-idx1-ubyte.gz

下面的步骤非常关键,先看代码

# 创建x,x是一个占位符(placeholder),代表待识别的图片
x=tf.placeholder(tf.float32,[None,784])

# w是Softmax模型的参数,将一个784维的输入转换为一个10维的输出
W=tf.Variable(tf.zeros([784,10]))

# b是一个Softmax模型的参数,一般叫做"偏置项(bias)"
b=tf.Variable(tf.zeros([10]))

# y表示模型的输出
y=tf.nn.softmax(tf.matmul(x,W)+b)

# y_是实际图像标签,同样以占位符表示
y_=tf.placeholder(tf.float32,[None,10])

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

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

变量是指在计算过程可以改变的值,每次计算后变量的值会被保存下来,通常用变量来存储模型的参数.如这里创建了两个变量: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中,定义交叉熵损失:

# 至此,得到了两个重要的Tensor:y和y_
# y是模型的输出,y_是实际的图像标签,注意y_是独热表示的

# 根据y和y_构造交叉熵损失
cross_entropy=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y)))

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

train_step=tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

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

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

# 创建一个Session,只有在Session中才能运行优化步骤train_step
session=tf.InteractiveSession()


# 分配内存
tf.global_variables_initializer().run()

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

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

# 进行1000步梯度下降
for _ in range(1000):
    # 在mnist.train中取100个训练数据
    # batch_xs是形状为(100,784)的图像数据,batch_ys是形状(100,10)的实际标签
    # batch_xs,batch_ys对应着两个占位符x和y_
    batch_xs,batch_ys=mnist.train.next_batch(100)
    
    # 在Session中运行train_step,运行时要传入占位符的值
    session.run(train_step,feed_dict={x:batch_xs,y_:batch_ys})

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

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

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

# 正确的预测结果
correct_prediction=tf.equal(tf.argmax(y,1),tf.argmax(y_,1))

# 计算预测准确率
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

print()

# 在session中运行Tensor可以得到Tensor的值
print(session.run(accuracy,feed_dict={x:mnist.test.images,y_:mnist.test.labels}))
0.9177

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

[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]

tf.argmax(y_,1)就是:[0,2,9,0]

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

[[0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01., 0.01, 0.01],
[0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01., 0.01, 0.01],
[0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01., 0.01, 0.01],
[0.91, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01., 0.01, 0.01]]

tf.argmax(y_,1)就是:[0,0,0,0]

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

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

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

在上面程序中,我们使用测试图片一共有10000张,运行的结果是0.9177,即91.77%的准确率.因为Softmax回归是一个比较简单的模型,这里预测的准确率并不高,下面将学习如何使用卷积神经网络将预测的准确率提高到99%

2.两层卷积网络分类

# -*- coding:utf-8 -*-

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

mnist=input_data.read_data_sets("./data/mnist",one_hot=True)

x=tf.placeholder(tf.float32,[None,784])
y_=tf.placeholder(tf.float32,[None,10])
Extracting ./data/mnist\train-images-idx3-ubyte.gz
Extracting ./data/mnist\train-labels-idx1-ubyte.gz
Extracting ./data/mnist\t10k-images-idx3-ubyte.gz
Extracting ./data/mnist\t10k-labels-idx1-ubyte.gz

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

# 将单张图片从784维向量重新还原为28*28的矩阵图片
x_image=tf.reshape(x,[-1,28,28,1])

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

def weight_variable(shape):
    initial=tf.truncated_normal(shape,stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    initial=tf.constant(0.1,shape=shape)
    return tf.Variable(initial)

def conv2d(x,W):
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")

def max_pool_2x2(x):
    return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
# 第一层卷积层
W_conv1=weight_variable([5,5,1,32])
b_conv1=bias_variable([32])
h_conv1=tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1)
h_pool1=max_pool_2x2(h_conv1)

先定义了四个函数,函数weight_variable可以返回一个给定形状的变量并自动以截断正态分布初始化,bias_variable同样返回一个给定形状的变量,初始化时所有值是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再做一次卷积计算,使用的代码与上面类似

# 第二层卷积层
W_conv2=weight_variable([5,5,32,64])
b_conv2=bias_variable([64])
h_conv2=tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2)
h_pool2=max_pool_2x2(h_conv2)

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

y_conv 相当于 Softmax 模型中的 Logit ,当然可以使用 So max 数将
真转换为 10 个类别的概率 再定义交叉摘损失 但真实 TensorFlow 供了
一个更直接的 tf.nn.so max cross_ entropy_ with logit 函数 可以直接对
Lo git 定义交叉烟损失 写法为

# 全连接层,输出为1024维的向量
W_fc1=weight_variable([7*7*64,1024])
b_fc1=bias_variable([1024])
h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*64])
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)

# 使用Dropout,keep_prob是一个占位符,训练时为0.5,测试为1
keep_prob=tf.placeholder(tf.float32)
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)

在全连接层中加入了Dropout,它是防止神经网络过拟台的一种手段.在每一步训练时,以一定概率“去悼”网络中的某些连接,但这种去除不是永久性的,只是在当前步骤中去除.并且每一步去除的连接都是随机选择的.

在这个程序中,选择的Dropout概率是0.5.也就是说训练时每一个连接都50%的概率被去除 在测试时保留所有连接

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

# 把1024维的向量转换成10维,对应10个类别
W_fc2=weight_variable([1024,10])
b_fc2=bias_variable([10])
y_conv=tf.matmul(h_fc1_drop,W_fc2)+b_fc2

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

cross_entropy=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_,logits=y_conv))

train_step=tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction=tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
session=tf.InteractiveSession()
session.run(tf.global_variables_initializer())

# 训练10000步
for i in range(10000):
    batch=mnist.train.next_batch(50)
    
    # 每100步报告一次在验证集上的准确率
    if i%1000==0:
        train_accuracy=accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0})
        print("Step %d,Training accuracy %g"%(i,train_accuracy))
    train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})
Step 0,Training accuracy 0.12
Step 1000,Training accuracy 0.96
Step 2000,Training accuracy 0.98
Step 3000,Training accuracy 1
Step 4000,Training accuracy 0.98
Step 5000,Training accuracy 0.98
Step 6000,Training accuracy 1
Step 7000,Training accuracy 1
Step 8000,Training accuracy 1
Step 9000,Training accuracy 1
# 训练结束后报告在测试集上的准确率
print("Test accuracy:%g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))
Test accuracy:0.9891

三.总结

主要介绍MNIST数据集,以及如何使用Tensorflow把它读到内存中.接着通过一个简单的Softmax回归模型例子,学习如何使用Tensorflow建立简单的图像识别模型.最后我们使用Tensorflow建立了有两层卷积层的神经网络,将MNIST的识别率准确提高到1

扩展阅读:

  • MNIST数据集经常被用来检验机器学习模型的性能,在它的官网:链接地址,可以找到多达68种模型在该数据集上的准确率数据,包括相应的论文出处.这些模型包括线性分类器,K近邻方法,普通的神经网络,卷积神经网络等
  • 本节的两个MNIST程序实际上来自于Tensorflow官方的两个新手教程,地址为:例子1例子2.可以将例子与官方的教程对照起来进行阅读
  • 关于非常重要的tf.Tensor类.可以参考:链接
  • 使用tf.Variable类来存储模型的参数.可以参考:链接
  • 只有通过会话(Session)才能计算出tf.Tensor的值.阅读:链接,该文档描述了Tensorflow中计算图和会话的基本运行原理