上一次我们介绍了如何使用 tf 写一个简单的神经网络。 这次我们把难度升级,直接写卷积神经网络。
cnn(卷积神经网络) 模型要解决的问题是计算机视觉, 所以我们要准备一些图片数据。 为了简单,我们就使用 tf 官网提供的 mnist 手写体数字图片。 这份数据的好处是 tf 提供了专门的库来帮助我们读取这份数据,还帮我们实现了按 batch size 的读取。同时连训练集,验证集和测试集也都帮我们分好了。下面是代码:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
# 加载数据
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
这是一份 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 层,而它的输出是一个概率分布,从而要求输入的标签也以概率分布的形式出现,进而算交叉熵。
我们可以打印一下这份数据的一些内容。
print(mnist.train.num_examples) # 打印训练集样本数据量:55000
print(mnist.validation.num_examples) # 打印验证集样本数量:5000
print(mnist.test.num_examples) # 打印测试集样本数量:10000
# 打印一个样本的label,因为是10分类问题,所以是一个有10个数的数组。
print(mnist.train.labels[0]) # 输出为 [0. 0. 0. 0. 0. 0. 0. 0. 1. 0]
我们的训练集有 55000 个图片,每一张图片都是一个 28*28*1 的=784 的图片,之所以最后是 *1 的是因为这些图片是灰度的,而不是 RGB 3 个色彩通道的。所以经过程序处理,每一张图片都是一个 28*28 的一维数组。 同时 input_data 帮我们实现了根据 mini batch 读取数据的方式,如下:
batch_size = 64
xs, ys = mnist.train.next_batch(batch_size)
print(xs.shape) # 这是一个100*784的矩阵
print(ys.shape) # 这是一个100*10的矩阵
对卷积层还不太了解的小伙伴可以回头看一看前几章关于介绍卷积神经网络的帖子。 tf 中有一个很方便的方法实现了卷积层的前向传播算法。就是 tf.nn.conv2d。示例如下:
tf.nn.conv2d(x, [5,5,1,32], strides=[1, 1, 1, 1], padding='SAME')
x_image = tf.reshape(x, [-1,28,28,1])
为什么要对样本做 reshape 改变维度呢, 因为卷积层需要的输入格式是 [batch, height, width, channels] 这样的,而我们在读取图片数据的时候,它是一个 28*28 的一维数组,也就是一个向量,我们读取数据的时候是把所有的像素值变成了这么一个向量。而卷积操作是一个矩阵做操作的,所以同样需要转换成一个 4 维数组。 上面第一个维度为-1, 是缺省值,代表重新组织结构的时候优先其他维度的计算,就是先以你们合适,到时总数除以你们几个的乘积,我该是几就是几,当然这个维度也可以写一个具体的值,比如 batch_size。 第二个和第三个维度是图片的长和宽。28*28. 最后一个是颜色通道,也就是 1. 这样我们就能对样本进行卷积操作了。
池化层也与卷积层类似。示例如下:
tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME')
根据上面讲的,我们可以模拟一个卷积和池化的操作。
# 创建卷积层,步长为1,周围补0,输入与输出的数据大小一样(可得到补全的圈数)
# 设置占位符
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
# 创建w参数
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
# 创建b参数
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')
# 创建池化层,kernel大小为2,步长为2,周围补0,输入与输出的数据大小一样(可得到补全的圈数)
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME')
# 第一层卷积,这里使用5*5的过滤器,因为是灰度图片,所以只有一个颜色通道,使用32个过滤器来建立卷积层,所以
# 我们一共是有5*5*32个参数
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
# 数据加载出来以后是一个n*784的矩阵,每一行是一个样本。784是灰度图片的所有的像素点。实际上应该是28*28的矩阵
# 平铺开之后的结果,但在cnn中我们需要把他还原成28*28的矩阵,所以要reshape
x_image = tf.reshape(x, [-1,28,28,1])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
今天先写这么多,下一次会写一个完整的卷积神经网络的代码, 同时解释如何使用 tf 在测试集上计算正确率。