Tensorflow- 卷积网络的MNIST源码详尽解析

前言


鉴于国内使用卷积网络实现MNIST数据集识别的源码(基于Tensorflow实现)解析资料很少,本人对最新的代码进行学习,现将笔记心得分享。由于自身水平有限,理解不当之处请大家批评指正。本文不会对算法进行详细介绍,主要针对代码中所使用的一些函数定义与用法进行解释,并给出最终运行代码。本教程假设读者已经了解MNIST和卷积网络,若未了解,可先看这两篇我写的文章:
Tensorflow- MNIST机器学习入门
Tensorflow- CNN卷积神经网络的MNIST手写数字识别

数据集


数据集是MNIST,一个入门级的计算机视觉数据集,它包含各种手写数字图片:

每张图片包含28X28个像素点,标签即为图片中的数字。

问题


使用MNIST数据集进行训练,识别图片中的手写数字(0到9,共10类)。

思路


使用一个简单的CNN网络结构如下,括号里边表示tensor经过本层后的输出shape:

输入层(28 * 28 * 1)-->卷积层1(28 * 28 * 32)-->pooling层1(14 * 14 * 32)-->卷积层2(14 * 14 * 64)-->池化层2(7 * 7 * 64)-->全连接层(1 * 1024)-->softmax层(10)

函数说明


在给出完整代码前,先对几个的主要函数中的主要参数进行说明。也可以先运行代码,不懂的部分在看这里的函数解释。

tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)

随机产生一个形状为shape的服从截断正态分布(均值为mean,标准差为stddev)的tensor。截断的方法根据官方API的定义为,如果单次随机生成的值偏离均值2倍标准差之外,就丢弃并重新随机生成一个新的数。

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, data_format=None, name=None)
  • input:一个形状为[batch, in_height, in_width, in_channels]的tensor:

    • batch:每次batch数据的数量。
    • in_height,in_width:输入矩阵的高和宽,如输入层的图片是28*28,则in_height和in_width就都为28。
    • in_channels:输入通道数量。如输入层的图片经过了二值化,则通道为1,如果输入层的图片是RGB彩色的,则通道为3;再如卷积层1有32个通道,则pooling层1的输入(卷积层1的输出)即为32通道。
  • filterfilter:一个形状为[filter_height, filter_width, in_channels, out_channels]的tensor:

    • filter_height, filter_width:卷积核的高与宽。如卷积层1中的卷积核,filter_height, filter_width都为28。
    • in_channels:输入通道数量。
    • out_channels:输出通道的数量。如输入数据经过卷积层1后,通道数量从1变为32。
  • strides:滑动窗口(卷积核)的滑动规则,包含4个维度,分别对应input的4个维度,即每次在input tensor上滑动时的步长。其中batch和in_channels维度一般都设置为1,所以形状为[1, stride, stride, 1]

tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
  • value:以tf.nn.conv2d()函数的参数input理解即可。
  • ksize:滑动窗口(pool)的大小尺寸,这里注意这个大小尺寸并不仅仅指2维上的高和宽,ksize的每个维度同样对应input的各个维度(只是大小,不是滑动步长),同样的,batch和in_channels维度多设置为1。如pooling层1的ksize即为[1, 2, 2, 1],即用一个2*2的窗口做pooling。
  • strides:同tf.nn.conv2d()函数的参数strides。
tf.nn.dropout(x, keep_prob, noise_shape=None, seed=None, name=None)
  • x:输入tensor。
  • keep_probx:每个元素的输出概率,输出为原值或0。

代码


'''
Created on 2017年10月11日

@author: zhoucheng
'''
from tensorflow.examples.tutorials.mnist import input_data
"""自动创建一个'MNIST_data'的目录来存储数据,将下载的文件解压,图像转化为4D向量[index, y, x, depth],
        分别存储在'train', 'validation', 'test'的Dataset中。将标签one-hot编码后,就可以跟对连续型特征的
        归一化方法一样,对每一维特征进行归一化
"""
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
import tensorflow as tf

#Weight Initialization
def weight_variable(shape):
    '''tf.truncated_normal(shape, mean, stddev) :shape表示生成张量的维度,mean是均值,stddev是标
                准差。这个函数产生正太分布,均值和标准差自己设定。这是一个截断的产生正太分布的函数,就是说产生
                正太分布的值如果与均值的差值大于两倍的标准差,那就重新生成。
    '''
    initial = tf.truncated_normal(shape = shape, stddev=0.1)
    return tf.Variable(initial)

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

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

#Convolution and Pooling
'''TensorFlow在卷积和池化上有很强的灵活性。我们怎么处理边界?步长应该设多大?在这个实例里,我们会一直
        使用vanilla版本。我们的卷积使用1步长(stride size),0边距(padding size)的模板,通过填充“零”保
        证输出和输入是同一个大小。我们的池化用简单传统的2x2大小的模板做max pooling。为了代码更简洁,我们把
        这部分抽象成一个函数。
'''
def conv2d(x, W):
    return tf.nn.conv2d(input = x, filter = 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')
    
#First Convolutional Layer

'''现在我们可以开始实现第一层了。它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征
    (32个卷积核,可以学习32种特征)。卷积的权重张量形状是[5, 5, 1, 32],前两个维度是patch的大小,接着
        是输入的通道数目,最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量。
'''
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

'''为了用这一层,我们把x变成一个4d向量,其第2、第3维对应图片的宽、高,最后一维代表图片的颜色通道数(因为
        是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。将784X1的向量形式转化成28X28的矩阵形式进行卷积运算
'''
x_image = tf.reshape(x, [-1, 28, 28, 1])

'''我们把x_image和权值向量进行卷积,加上偏置项,然后应用ReLU激活函数,最后进行max pooling。同理,可以
        建立结构不变,输入32个通道,输出64个通道的第二层卷积层。
'''
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)#32 map:14X14
h_pool1 = max_pool_2x2(h_conv1)

#Second Convolutional Layer
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)#64 map:7X7
h_pool2 = max_pool_2x2(h_conv2)

'''现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,用于处理整个图片。我们把池化层输出的张
        量reshape成向量,乘上权重矩阵,加上偏置,然后对其使用ReLU。
'''
#Densely Connected Layer
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)#matrix 1X1024

'''为了减少过拟合,我们在输出层之前加入dropout。我们用一个placeholder来代表一个神经元的输出在dropout中
        保持不变的概率。这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。 TensorFlow的
    tf.nn.dropout操作除了可以屏蔽神经元的输出外,还会自动处理神经元输出值的scale。所以用dropout的时候
        可以不用考虑scale。
'''

#dropout
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)#matrix 1X1024

'''最后,我们添加一个softmax层,把向量化后的图片x和权重矩阵W相乘,加上偏置b,然后计算每个分类的softmax概率值。
'''
#Readout Layer
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])

y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2#matrix 1X10

'''可以很容易的为训练过程指定最小化误差用的损失函数,我们的损失函数是目标类别和预测类别之间的交叉熵。
'''
#loss function
"""
   softmax_cross_entropy_with_logits返回1X10的向量,代表每個維度预测越准确,结果的值越小(别忘了前面还有负号),
   最后reduce_mean求一个平均,得到我们想要的loss
"""
cross_entropy = tf.reduce_mean( #a number
    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))

'''我们已经定义好模型和训练用的损失函数,那么用TensorFlow进行训练就很简单了。因为TensorFlow知道整个计算图,它
        可以使用自动微分法找到对于各个变量的损失的梯度值。TensorFlow有大量内置的优化算法 这个例子中,我们用最速下降
        法让交叉熵下降,步长为0.0001.这一行代码实际上是用来往计算图上添加一个新操作,其中包括计算梯度,计算每个参数
        的步长变化,并且计算出新的参数值。
        返  回的train_step操作对象,在运行时会使用梯度下降来更新参数。整个模型的训练可以通过反复地运行train_step来完成。
'''

#Train the Model 
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

'''首先让我们找出那些预测正确的标签。tf.argmax 是一个非常有用的函数,它能给出某个tensor对象在某一维上的其数据最
        大值所在的索引值。由于标签向量是由0,1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1)返回的是
        模型对于任一输入x预测到的标签值,而 tf.argmax(y_,1) 代表正确的标签,我们可以用 tf.equal 来检测我们的预测是否
        真实标签匹配(索引位置一样表示匹配)。
    tf.argmax(input, axis=None, name=None, dimension=None)
            此函数是对矩阵按行或列计算最大值

参数
input:输入Tensor
axis:0表示按列,1表示按行
        这里返回一个布尔数组。为了计算我们分类的准确率,我们通過tf.cast将布尔值转换为浮点数来代表对、错,然后取平均值。
        例如:[True, False, True, True]变为[1,0,1,1],计算出平均值为0.75。
'''

#Evaluate the Model
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))#a list of boolean
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))#bool->float,然後求均值

with tf.Session() as sess:
  sess.run(tf.global_variables_initializer())
  for i in range(20000):
    batch = mnist.train.next_batch(50)
    if i % 100 == 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({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}))

你可能感兴趣的:(Tensorflow- 卷积网络的MNIST源码详尽解析)