TensorFlow的MNIST手写数字分类问题

一、简介MNIST

      TensorFlow编程学习的入门一般都是基于MNIST手写数字数据集和Cifar(包括cifar-10和cifar-100)数据集,因为它们都比较小,一般的设备即可进行训练和测试。而相比之下虽然基于ImageNet分类数据集的实验更具有意义,更加权威,但由于ImageNet数据集实在太大,不便于入门的童靴直接上手操作。所以接下来,我将只讲一下如何用TensorFlow导入MNIST数据集。
1、MNIST数据集简介

MNIST 数据集的官网是 Yann LeCun's website。下载下来的数据集被分成两部分:60000行的训练数据集(mnist.train)和10000行的测试数据集(mnist.test).这样的切分很重要,在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能,从而更加容易把设计的模型推广到其他数据集上(泛化)。

       我们把这个数组展开成一个向量,长度是 28x28 = 784.如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开.从这个角度来看,MNIST数据集的图片就是在784维向量空间里面的点, 并且拥有比较复杂的结构 (提醒: 此类数据的可视化是计算密集型的)。每一张图片包含28像素X28像素.我们可以用一个数字数组来表示这张图片:

TensorFlow的MNIST手写数字分类问题_第1张图片

     因此,在MNIST训练数据集中,mnist.train.images 是一个形状为 [60000, 784] 的张量,第一个维度数字用来索引图片,第二个维度数字用来索引每张图片中的像素点.在此张量里的每一个元素,都表示某张图片里的某个像素的强度值,值介于0和1之间.

TensorFlow的MNIST手写数字分类问题_第2张图片

      相对应的 MNIST 数据集的标签是介于0到9的数字,用来描述给定图片里表示的数字.为了用于这个教程,我们使标签数据是"one-hot vectors". 一个 one-hot 向量除了某一位的数字是1以外其余各维度数字都是0.所以在此教程中,数字n将表示成一个只有在第n维度(从0开始)数字为1的10维向量.比如,标签0将表示成([1,0,0,0,0,0,0,0,0,0,0]).因此, mnist.train.labels 是一个 [60000, 10] 的数字矩阵.

TensorFlow的MNIST手写数字分类问题_第3张图片

2、使用tensorflow导入已经下载好的mnist数据集

导入相应的库,并且导入数据(当前文件夹下没有MNIST库的话会自动下载);

from tensorflow.examples.tutorials.mnist import input_data
#读取数据
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)

运行结果:

Extracting MNIST_data\train-images-idx3-ubyte.gz
Extracting MNIST_data\train-labels-idx1-ubyte.gz
Extracting MNIST_data\t10k-images-idx3-ubyte.gz
Extracting MNIST_data\t10k-labels-idx1-ubyte.gz

让我们看一下数据集里面的数据情况:

print("MNIST的数据类型是:",type(mnist))
print("训练数据集的个数是:",mnist.train.num_examples)
print("测试数据集的个数是:",mnist.test.num_examples)

运行结果:

MNIST的数据类型是:
训练数据集的个数是: 55000
测试数据集的个数是: 10000

trainimg   = mnist.train.images
trainlabel = mnist.train.labels
testimg    = mnist.test.images
testlabel  = mnist.test.labels
print ("训练数据的类型:",type(trainimg))
print ("训练数据标签的类型:",type(trainlabel))
print ("测试数据的类型:",type(testimg))
print ("测试数据标签的类型:",type(testlabel))
print ("训练数据的形状:",trainimg.shape)
print ("训练数据标签的形状:",trainlabel.shape)
print ("测试数据的形状:",testimg.shape)
print ("测试数据标签的形状:",testlabel.shape)
batch=mnist.train.next_batch(50)
print("batch的数据类型:",type(batch))
print("batch[0]的形状:",batch[0].shape)
print("batch[1]的形状:",batch[1].shape)

       运行结果:

训练数据的类型:
训练数据标签的类型:
测试数据的类型:
测试数据标签的类型:
训练数据的形状: (55000, 784)
训练数据标签的形状: (55000, 10)
测试数据的形状: (10000, 784)
测试数据标签的形状: (10000, 10)

batch的数据类型:
batch[0]的形状: (50, 784)
batch[1]的形状: (50, 10)

二、Tensorflow实现MNIST的手写数字识别

CNN.py

import tensorflow as tf

#定义CNN网络结构
def CNN(input_tensor,keep_prob):
    #C1是卷积层,Input=[batch,28,28,1],output=[batch,28,28,32],W=[3,3,1,32],S=[1,1,1,1]
    conv1_w=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))#1
    conv1_b=tf.Variable(tf.constant(0,1,shape=[32]))               #1
    conv1=tf.nn.conv2d(input_tensor,conv1_w,strides=[1,1,1,1],padding='SAME')+conv1_b #2
    conv1=tf.nn.relu(conv1)
    
    #S2是池化层,Input=[batch,28,28,32],output=[batch,14,14,32],Ksize=[1,2,2,1],S=[1,2,2,1]
    pool_1=tf.nn.max_pool(conv1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')   #2
    
    #C3是卷积层,Input=[batch,14,14,32],output=[batch,14,14,50],W=[3,3,32,50],S=[1,1,1,1]
    conv2_w=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1))
    conv2_b=tf.Variable(tf.constant(0.1,shape=[50]))
    conv2=tf.nn.conv2d(pool_1,conv2_w,strides=[1,1,1,1],padding='SAME')+conv2_b
    conv2=tf.nn.relu(conv2)
    
    #S4是池化层,Input=[batch,14,14,32],output=[batch,7,7,50],Ksize=[1,2,2,1],S=[1,2,2,1]
    pool_2=tf.nn.max_pool(conv2,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
    
    #F5是全连接层,Input=[batch,7,7,50],output=[batch,1024]
    fc1_w=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1))
    fc1_b=tf.Variable(tf.constant(0.1,shape=[1024]))
    pool_2_flat=tf.reshape(pool_2,[-1,7*7*50])
    fc1=tf.nn.relu(tf.matmul(pool_2_flat,fc1_w)+fc1_b)
    #dropout(随机权重失活)
    fc1_drop=tf.nn.dropout(fc1,keep_prob=keep_prob)     #3
    
    #F6是全连接层,Input=[batch,1024],output=[batch,10]
    fc2_w=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1))
    fc2_b=tf.Variable(tf.constant(0.1,shape=[10]))
    y_out=tf.nn.softmax(tf.matmul(fc1_drop,fc2_w)+fc2_b)  #4
 
    return y_out    

CNN_main.py

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

#读取数据
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
sess=tf.InteractiveSession()    #5

#设置占位符,尺寸为样本输入和输出的尺寸
x=tf.placeholder(tf.float32,[None,784]) #6
y_=tf.placeholder(tf.float32,[None,10])
x_img=tf.reshape(x,[-1,28,28,1])
keep_prob=tf.placeholder(tf.float32)
#用自己构建的神经网络得到预测结果
y_out=CNN.CNN(x_img,keep_prob)

#建立loss function,为交叉熵
cross_entropy = -tf.reduce_sum(y_*tf.log(y_out))#损失函数#7
loss=tf.reduce_mean(cross_entropy ,reduction_indices=[1]))
#配置Adam优化器,学习速率为0.0001
train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)  #8

#建立正确率计算表达式
correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1)) #9
accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))

#开始喂数据,训练
tf.global_variables_initializer().run()

for i in range(2000):
    batch=mnist.train.next_batch(50)
    if i%100==0:
        train_accurcy=accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1})
        print("step %d,train_accurcy=%g"%(i,train_accurcy))
        
    train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})
    
#训练结束后,使用测试集进行测试,输出最终结果
print( "test_accuracy= %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1}))#0.9707#10
  1. tf.Variable
conv1_w=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1))#1
conv1_b=tf.Variable(tf.constant(0,1,shape=[32]))               #1

我们赋予tf.Variable不同的初值来创建不同的Variable。因为我们要学习W和b的值,它们的初值可以随意设置.

2TensorFlow中CNN的两种padding方式“SAME”和“VALID”

conv1=tf.nn.conv2d(input_tensor,conv1_w,strides=[1,1,1,1],padding='SAME')+conv1_b 
conv1=tf.nn.relu(conv1)
#S2是池化层,Input=[batch,28,28,32],output=[batch,14,14,32],Ksize=[1,2,2,1],S=[1,2,2,1]
pool_1=tf.nn.max_pool(conv1,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

TensorFlow的MNIST手写数字分类问题_第4张图片

3、tf.dropout(随机权重失活)

fc1_drop=tf.nn.dropout(fc1,keep_prob=keep_prob)     #2

   在这一层使用dropout(权值随机失活),对一些神经元突触连接进行强制的置零,这个trick可以防止神经网络过拟合。这里的dropout的保留比例是0.5,即随机地保留一半权值,删除另外一半(不要觉得可惜,为了保证在测试集上的效果,这是必须的)。Dropout比例通过placeholder来设置,因为训练过程中需要dropout,但是在最后的测试过程中,我们又希望使用全部的权值,所以dropout的比例要能够改变,所以这里使用placeholder。

4、类别预测tf.nn.softmax

    现在我们可以实现我们的回归模型了。这只需要一行!我们把向量化后的图片x和权重矩阵W相乘,加上偏置b,然后计算每个分类的softmax概率值。

y_out=tf.nn.softmax(tf.matmul(fc1_drop,fc2_w)+fc2_b)

一分钟理解softmax函数(超简单)

Softmax函数与交叉熵

Softmax回归

交叉熵代价函数(作用及公式推导)

5、tf.InteractiveSession

sess=tf.InteractiveSession()

tf.InteractiveSession():它能让你在运行图的时候,插入一些计算图,这些计算图是由某些操作(operations)构成的。这对于工作在交互式环境中的人们来说非常便利,比如使用IPython。
tf.Session():需要在启动session之前构建整个计算图,然后启动该计算图。

      意思就是在我们使用tf.InteractiveSession()来构建会话的时候,我们可以先构建一个session然后再定义操作(operation),如果我们使用tf.Session()来构建会话我们需要在会话构建之前定义好全部的操作(operation)然后再构建会话。
6、占位符tf.placeholder

我们通过为输入图像和目标输出类别创建节点,来开始构建计算图。

x=tf.placeholder(tf.float32,[None,784]) 
y_=tf.placeholder(tf.float32,[None,10])

这里的xy并不是特定的值,相反,他们都只是一个占位符,可以在TensorFlow运行某一计算时根据该占位符输入具体的值。

输入图片x是一个2维的浮点数张量。这里,分配给它的shape[None, 784],其中784是一张展平的MNIST图片的维度。None表示其值大小不定,在这里作为第一个维度值,用以指代batch的大小,意即x的数量不定。输出类别值y_也是一个2维张量,其中每一行为一个10维的one-hot向量,用于代表对应某一MNIST图片的类别。

虽然placeholdershape参数是可选的,但有了它,TensorFlow能够自动捕捉因数据维度不一致导致的错误。

训练模型

7、损失函数 -tf.reduce_sum(y_*tf.log(y_out))

cross_entropy = -tf.reduce_sum(y_*tf.log(y_out))

为了训练我们的模型,我们首先需要定义一个指标来评估这个模型是好的.其实,在机器学习,我们通常定义指标来表示一个模型是坏的,这个指标称为成本(cost)或损失(loss),然后尽量最小化这个指标.但是,这两种方式是相同的.

一个非常常见的,非常漂亮的成本函数是“交叉熵”(cross-entropy).交叉熵产生于信息论里面的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的重要技术手段.它的定义如下:

mnist10

y 是我们预测的概率分布, y' 是实际的分布(我们输入的 one-hot vector).比较粗糙的理解是,交叉熵是用来衡量我们的预测用于描述真相的低效性.更详细的关于交叉熵的解释超出本教程的范畴,但是你很有必要好好理解它.

为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值:

y_ = tf.placeholder("float", [None,10])

然后我们可以用  计算交叉熵:

cross_entropy = -tf.reduce_sum(y_*tf.log(y))

首先,用 tf.log 计算 y 的每个元素的对数.接下来,我们把 y_ 的每一个元素和 tf.log(y_) 的对应元素相乘.最后,用 tf.reduce_sum 计算张量的所有元素的总和.(注意,这里的交叉熵不仅仅用来衡量单一的一对预测和真实值,而是所有100幅图片的交叉熵的总和.对于100个数据点的预测表现比单一数据点的表现能更好地描述我们的模型的性能.

8、tf.train.AdamOptimizer(1e-4).minimize(loss)

train_step=tf.train.AdamOptimizer(1e-4).minimize(loss)

在这里,我们要求 TensorFlow 用梯度下降算法(gradient descent algorithm)以0.01的学习速率最小化交叉熵.梯度下降算法(gradient descent algorithm)是一个简单的学习过程,TensorFlow 只需将每个变量一点点地往使成本不断降低的方向移动.当然 TensorFlow 也提供了其他许多优化算法:只要简单地调整一行代码就可以使用其他的算法。

9、评估我们的模型

那么我们的模型性能如何呢?

首先让我们找出那些预测正确的标签.tf.argmax 是一个非常有用的函数,它能给出某个 tensor 对象在某一维上的其数据最大值所在的索引值。用 tf.equal 来检测我们的预测是否真实标签匹配(索引位置一样表示匹配)。

tf.argmax(prediction, 1) 返回每行最大值的下标

tf.argmax(prediction, 0) 返回每列最大值的下标

correct_prediction = tf.equal(tf.argmax(y_out,1), tf.argmax(y_,1))

这行代码会给我们一组布尔值.为了确定正确预测项的比例,我们可以把布尔值转换成浮点数,然后取平均值.例如,[True, False, True, True] 会变成 [1,0,1,1] ,取平均值后得到 0.75.

accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

10、最后,我们计算所学习到的模型在测试数据集上面的正确率.

print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})

 

 

 

 

 

 

 

 

 

 

 

TensorFlow中CNN的两种padding方式“SAME”和“VALID”

 

你可能感兴趣的:(tensorflow学习笔记)