Tensorflow系列之(二):详解CNN识别MNIST手写数字集

在前面的文章中,我们介绍了怎么用Python自己实现一个简单的神经网络,来实现MNIST的手写数字集。这能帮我们更好的理解神经网络是怎样工作的,但是毕竟在实际应用中,自己实现容易出错,而且如果我们要实现比较复杂的结构比如说卷积神经网络,从底层实现就不太合适了。如果用Tensorflow的话,更加的方便快捷,合理高效

我们之前实现了用Python做一个简单的神经网络,但是这是有问题的,问题主要存在于这几个方面

  1. 全连接的结构不合适:对于一个32 * 32 * 3大小的图片,单单一个神经元就要有32 * 32 * 3个连接的权重,那么对于更多的神经元,更多的层数,更大的图片,这个计算量的增加是无法接受的
  2. 全连接的神经网络没有利用到图片的一些特点

在Convoluntional Layer当中,一个神经元只连接一个小区域内的像素点,这个区域就叫做这个神经元的receptive field,这个区域的大小就叫做filter size。这个地方需要注意的一点是,receptive field在图像的前两维里面是只取一个小区域,但是在第三个维度上,也就是深度上,是接收所有的数据的

输出向量的深度也是我们关心的一个量,这里深度就代表着这个维度上有多少个神经元。每一个深度上所有的神经元当然都是卷积同一个receptive field的,但是它们连接的权重也都不一样,它们提取的特征也不一样,有的是提取边特征,有的是提取颜色特征,这些看着同一个receptive field的神经元,我们叫作一个depth column(或者一个fibre)

我们还关心卷积的步长(step),也就是每当我做完一次卷积之后,我在每个维度上将我的窗口移动多大的距离。

如果我们输入层的大小是W,窗口的大小是F,步长的大小是S,Padding的大小是P,那么我们得到的输出层的大小是

Size_{output}=\frac{W-F+2P}{S} + 1

这个式子也很好理解,输入的大小减去窗口的大小,就是可以移动一个步长,然后再加一。处理的时候还要考虑padding添加的大小

这里值得注意的地方有两点
1. 输出层的一个neuron,看的区域,是receptive的大小,乘以输入层的所有深度,这么大,比如说输入的量是X,某一个神经元的权重W看的区域就是X[:5,:5,:],其中W[:,:,0]对应深度的第一层,W[:,:,1]对应深度的第二层
2. 输出层有很多层,每一层的权重系数都是一样的,比如说在depth_column[0]上,对于不同平面上的区域,权重都是一样的

也就是说,每一个神经元连接的输入层的量X和连接的权重之间,都是elementwise的运算

现在我们举个例子

如果我们有一个输入图片,大小是227 * 227 * 3,我们卷积的窗口大小是 11 * 11 * 3,步长是4,没有padding,设置输出层的层数是96,现在我们来简单进行一下计算

每一组输入的大小,是 11 * 11 * 3 = 363

在这个窗口的大小下,我们得到的数据的组数是
( 227 - 11 ) / 4 + 1 = 55,就是55 * 55 = 3025组

输入的矩阵X是3025 * 363

对于每一个receptive field当中的值,我们都需要给他一个权重,这个权重是每层共享的,总共有96层,所以我们的权重矩阵W1的大小是 363 * 96的

OutputLayer = np.matmul( X, W1 )

我们得到的输出矩阵就是3025 * 96的,我们需要把这个矩阵重新reshape成55 * 55 * 96的,然后输入下一个卷积层进行计算

1. 准备数据集

准备数据集的工作基本上和之前是一样的,我们把训练的60000组数据和训练用的10000组数据按每个图片成一个一维的向量(1,28*28),在进行计算的时候我们再把它展开

2. 搭建前向计算的网络

这个我们工作最重要的部分,如何对输入的向量进行正向计算

我们首先需要构建两个初始化权重矩阵的函数

def GetWeights(self,shape):
    weights = tf.truncated_normal( shape, stddev= 0.1 )
    return tf.Variable( weights )

def GetBias(self,shape):
    bias = tf.constant( 0.1, shape = shape )
    return tf.Variable( bias )

第一个函数,生成一个给定shape形状的符合正态分布的权重矩阵

第二个函数,生成一个初始值为0.1给定形状的矩阵

下一步的工作就是搭建神经网络

卷积神经网络的简单介绍在这篇文章中,有兴趣的同学可以看一下

这个网络的结构是这样的

INPUT-> CONV1 ->RELU -> MAXPOOL ->CONV2 -> RELU -> MAXPOOL -> FULL_CONNECTED1 -> FULL_CONNECTED2 ->OUTPUT

基本的卷积,非线性单元,池化层的代码是这样的

首先我们来声明权重系数

W1 = self.get_weights( [ 5,5,1,32 ] )
b1 = self.get_bias( [ 32 ] )

这里[5,5,1,32]的意思是,receptive field的大小是5 * 5,输入层的深度是1,输出层的深度是32(关于每一层的深度的意思,详见这篇文章)

h1 = tf.nn.relu(
            tf.nn.conv2d( self.input, W1, strides= [ 1,1,1,1 ], padding= 'SAME' )
            + b1)

然后我们通过这个函数来计算卷积,和非线性单元,通过取stride=1和padding=’SAME’,可以保证输入和输出的大小是一样的

然后进行一个池化的操作

 p1 = tf.nn.max_pool( h1, [ 1,2,2,1 ],strides= [1,2,2,1],padding= 'SAME' )

通过设定池化的filter size = 2, stride = 2,使得输出的图片大小长宽各是输入的一半

这就是一层卷积,然后我们再进行一次卷积,然后就进行全连接操作,全连接就是指,我们把输入reshape成一维向量,然后像普通的神经网络那样进行矩阵乘

reshape的方法有一点点特别

p2_flatten = tf.reshape( p2, [ -1, 7 * 7 * 64 ] )

这里-1的意思是,我们把每7 * 7 * 64个值组成一个向量,但是我们不关心组成了多少组向量(其实设计网络的时候我们也不知道,因为这跟每次输入的数据有关),我们只对每一维的向量做相应的操作

在两个卷积层之后我们添加两个全连接层,就可以得到每个分类的分数了

3. 计算误差

误差的计算仍然用和之间神经网络一样的计算Softmax的算法,不过这次我们直接用Tensorfow的softmax的函数就可以了,非常的方便

self.output = tf.nn.softmax( tf.matmul( conv1, W_fc2 ) + b_fc2 )
cross_entropy = -tf.reduce_sum( self.label * tf.log(self.output))

这里需要说明一下,self.output是之前通过卷积网络计算的每个分类的分数,再通过softmax函数计算得到的值,也就是我们之前说的

L_{i} = -\log(\frac{e^{f_{y_{i}}}}{\sum_{j}e^{f_{j}}})

但是我们得到的是所有分类的值,所以这里通过one_hot的标签来进行选择,并且对所有样本的值求和,来计算交叉熵,cross_entropy,来得到Loss,正确样本的得分越高,Loss也就越小

4. 调用optimizer进行训练

我们现在要对Loss进行梯度下降,调整参数,使得Loss越来越小,我们可以使用优化器,就像这样

self.optimizer = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

在优化器当中我们可以指定使用怎样的梯度下降方式,来优化哪个量

5. 进行predict

在进行训练之后,我们希望能进行在测试集上进行预测,测试集和训练集是完全分开的,这样得到的预测结果才比较具有代表性

batch = mnist.train.next_batch(50)
prediction_scores = self.output.eval( feed_dict = { self.input_layer : batch[0] } )

我们首先从训练集当中取一定大小的batch,然后将这个batch提供给input_layer,做输入,得到我们的output层,也就是通过这个神经网络预测的输入样本属于每个类的评分

prediction = tf.argmax(prediction_scores,1)

对于每个样本,我们认为评分最大的那个分类就是它的预测分类

correct_prediction = tf.equal( prediction, tf.argmax(batch[1],1) )

我们将这个分类和真是的分类标签进行比对,得到一个1*M的向量,每一个分量表示有没有正确分类,是True或者False

但是我们要计算分类的准确率,需要把这个向量转成float型,然后计算均值

accuracy = tf.reduce_mean( tf.cast( correct_prediction, "float") )
print 'accuracy: ', accuracy.eval()

至此,所有部分都已经介绍完了,下面给出完整的代码,有兴趣的同学可以自己运行一下

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


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


class Data:
    def __init__(self):

        self.K = 10
        self.N = 60000
        self.M = 10000
        self.BATCHSIZE = 100
        self.reg_factor = 1e-3
        self.stepsize = 1e-2
        self.train_img_list = np.zeros(( self.N, 28*28 ))
        self.train_label_list = np.zeros((self.N, 1))



        self.test_img_list = np.zeros(( self.M, 28*28 ))
        self.test_label_list = np.zeros((self.M, 1))

        self.sess = tf.InteractiveSession()

        self.loss_list = []
        self.init_network()
        self.sess.run(tf.initialize_all_variables())

        self.train_data = np.append( self.train_img_list, self.train_label_list, axis = 1 )







    def GetOneHot(self, transfer_list):
        const_zero = np.zeros( [ transfer_list.shape[0], 10 ] )

        for i in range( transfer_list.shape[0] ):
            const_zero[ i ][ int(transfer_list[ i ]) ] = 1

        return const_zero

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

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





    def init_network(self):
        self.label = tf.placeholder( "float", [None, self.K] )
        self.input_layer = tf.placeholder( "float", [ None, 28*28 ] )
        self.input = tf.reshape( self.input_layer, ( -1, 28, 28, 1 ) )
        W1 = self.get_weights( [ 5,5,1,32 ] )
        b1 = self.get_bias( [ 32 ] )

        W2 = self.get_weights( [ 5,5,32,64 ] )
        b2 = self.get_bias( [ 64 ] )

        W_fc1 = self.get_weights( [ 7 * 7 * 64, 1024 ] )
        b_fc1 = self.get_bias( [ 1024 ] )

        W_fc2 = self.get_weights( [ 1024, 10 ] )
        b_fc2 = self.get_bias([ 10 ] )

        h1 = tf.nn.relu(
            tf.nn.conv2d( self.input, W1, strides= [ 1,1,1,1 ], padding= 'SAME' )
            + b1)
        p1 = tf.nn.max_pool( h1, [ 1,2,2,1 ],strides= [1,2,2,1],padding= 'SAME' )


        h2 = tf.nn.relu(
            tf.nn.conv2d( p1, W2, strides = [ 1,1,1,1 ], padding = 'SAME')
            + b2
        )
        p2 = tf.nn.max_pool( h2, [1,2,2,1], strides=[1,2,2,1], padding= 'SAME' )

        p2_flatten = tf.reshape( p2, [ -1, 7 * 7 * 64 ] )

        conv1 = tf.nn.relu(tf.matmul( p2_flatten, W_fc1 ) + b_fc1)

        self.output = tf.nn.softmax( tf.matmul( conv1, W_fc2 ) + b_fc2 )

        cross_entropy = -tf.reduce_sum( self.label * tf.log(self.output))
        self.optimizer = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)


    def predict(self):
        batch = mnist.train.next_batch(50)
        prediction_scores = self.output.eval( feed_dict = { self.input_layer : batch[0] } )

        prediction = tf.argmax(prediction_scores,1)

        correct_prediction = tf.equal( prediction, tf.argmax(batch[1],1) )
        accuracy = tf.reduce_mean( tf.cast( correct_prediction, "float") )
        print 'accuracy: ', accuracy.eval()

    def train(self):
        for i in range(1000):


            batch = mnist.train.next_batch(50)

            self.optimizer.run(
                feed_dict = {
                    self.input_layer:batch[0],self.label:batch[1]
                }
            )

            print i
            self.predict()
        return


def main():
    data = Data()
    data.train()
    data.predict()


if __name__ == '__main__':
    main()

参考资料

http://wiki.jikexueyuan.com/project/tensorflow-zh/tutorials/mnist_pros.html

猜你喜欢

转载自blog.csdn.net/superCally/article/details/54601931