前言
上一次我们介绍了如何使用tf写一个简单的神经网络。 这次我们把难度升级,直接写卷积神经网络。
数据介绍
cnn(卷积神经网络)模型要解决的问题是计算机视觉, 所以我们要准备一些图片数据。 为了简单,我们就使用tf官网提供的mnist 手写体数字图片。 这份数据的好处是tf提供了专门的库来帮助我们读取这份数据,还帮我们实现了按batch size的读取。同时连训练集,验证集和测试集也都帮我们分好了。下面是代码:
这是一份10分类的问题,每一张图片都是手写的数字,从0到9,而我们的目的,就是识别出每一张图片是哪一个数字,图片样例如下:
所以这是一个10分类的问题。
input_data 就是tf为mnist封装的库,read_data_sets方法的第一个参数是读取数据的路径, 如果这个路径下没有数据,它会自动下载下来。 第二个参数one_hot=True, 是专门为多分类也就是softmax准备的。非onehot,标签是类似0 1 2 3...n这样。而onehot标签则是顾名思义,一个长度为n的数组,只有一个元素是1.0,其他元素是0.0。例如在n为4的情况下(也就是4分类问题),标签2对应的onehot标签就是 0.0 0.0 1.0 0.0。使用onehot的直接原因是现在多分类cnn网络的输出通常是softmax层,而它的输出是一个概率分布,从而要求输入的标签也以概率分布的形式出现,进而算交叉熵。
我们可以打印一下这份数据的一些内容。
我们的训练集有55000个图片,每一张图片都是一个28*28*1的=784的图片,之所以最后是*1的是因为这些图片是灰度的,而不是RGB 3个色彩通道的。所以经过程序处理,每一张图片都是一个28*28的一维数组。 同时input_data帮我们实现了根据mini batch读取数据的方式,如下:
卷积层
conv2d的第一个参数是我们的样本,一般是[batch, height, width, channels]这样的格式,batch是我们的样本数量,也是我们训练mini batch的时候的block size。 height和width是图片的长和宽,channels是颜色通道的数量。
第二个参数是参数矩阵(在dnn中也就是参数W),这是一个4维矩阵,前两个维度是过滤器的大小,上面我们使用5*5的过滤器,第三个维度是我们的样本数据一共有多少个颜色通道,因为我们用的mnist数据是灰度图片而非是RGB, 所以只有一个颜色通道,第三个维度是1。 最后一个维度是我们要使用多少个过滤器,这里我们使用的是32个过滤器。 所以在这里我们的参数是5*5*1*32个参数。
conv2d的第三个参数是卷积步长, 同样是个4维数组,但是第一个维度和最后一个维度必须是1,因为我们只对图片的长和宽进行卷积操作。在上面的中间两个维度是真正起作用的,都是1,代表步长为1。
conv2d的最后一个参数是padding,可以取两个值,一个是SAME,代表使用0来补全图片像素,这样配合都是1的步长,图片大小就不会发生改变。 另外一个值是VALID,代表不使用padding,这样图片会变小。 这就是conv2d的用法。 当然了为了能够让我们的样本能够在卷积中使用,我们需要将样本进行reshape。如下:
为什么要对样本做reshape改变维度呢, 因为卷积层需要的输入格式是[batch, height, width, channels]这样的,而我们在读取图片数据的时候,它是一个28*28的一维数组,也就是一个向量,我们读取数据的时候是把所有的像素值变成了这么一个向量。而卷积操作是一个矩阵做操作的,所以同样需要转换成一个4维数组。 上面第一个维度为-1, 是缺省值,代表重新组织结构的时候优先其他维度的计算,就是先以你们合适,到时总数除以你们几个的乘积,我该是几就是几,当然这个维度也可以写一个具体的值,比如batch_size。 第二个和第三个维度是图片的长和宽。28*28. 最后一个是颜色通道,也就是1. 这样我们就能对样本进行卷积操作了。
池化层
池化层也与卷积层类似。示例如下:
第一个参数是经过卷积层处理的样本, 需要池化的输入,一般池化层接在卷积层后面,依然是[batch, height, width, channels]这样的shape
第二个参数是池化层过滤器的参数,一般是[1, height, width, 1],因为我们不想在batch和channels(颜色通道)上做池化,所以这两个维度设为了1。
步长和padding与卷积一样的。
具体操作
根据上面讲的,我们可以模拟一个卷积和池化的操作。
我们一步一步说上面的过程, 首先设置占位符,在上上章中我们说到,样本数量过大,一次性读入内存是不行的。所以引入mini batch,一次只训练batch size个样本,但如果每次都重新初始化样本(x)和label(y),会在计算图中产生大量的节点,这样是不行的。 所以使用占位符的方式在训练的时候动态的注入数据,这样由于占位符的作用,我们就只有一个节点存在于计算图中
上面我们封装一个卷积操作的通用方法,因为我们不只有一个卷积层,所以我们用一个函数来封装这个操作。 同样的池化层也封装了一个方法。
在做卷积操作的时候我们先初始化参数,我们用5*5的过滤器,由于是灰度图片所以channel是1, 同时使用32个过滤器来做卷积计算,所以是[5,5,1,32]
接着是针对输入的样本做reshape,具体原因上面讲过了
再然后是加入激活函数的前向传播算法,这里我们使用relu作为激活函数。这里注意的是我在上面直接用加法做的偏置项。 这样是只对经过卷积计算之后的结果加了偏置项,如果想对过滤器对原始图片的每一次卷积计算都加入偏置项,可以使用tf.nn.bias_add(conv, biases) 这个函数
最后我们把经过卷积计算的结果传递给池化层
结尾
今天先写这么多,下一次会写一个完整的卷积神经网络的代码, 同时解释如何使用tf在测试集上计算正确率。
领取专属 10元无门槛券
私享最新 技术干货