AI测试 深度学习基础 (十九)-- 使用 tensorflow 构建一个卷积神经网络 (上)

孙高飞 · 2018年04月16日 · 最后由 思寒_seveniruby 回复于 2018年04月16日 · 3830 次阅读

前言

上一次我们介绍了如何使用 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')
  • 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。如下:
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')
  • 第一个参数是经过卷积层处理的样本, 需要池化的输入,一般池化层接在卷积层后面,依然是 [batch, height, width, channels] 这样的 shape
  • 第二个参数是池化层过滤器的参数,一般是 [1, height, width, 1],因为我们不想在 batch 和 channels(颜色通道) 上做池化,所以这两个维度设为了 1。
  • 步长和 padding 与卷积一样的。

具体操作

根据上面讲的,我们可以模拟一个卷积和池化的操作。

# 创建卷积层,步长为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)
  • 我们一步一步说上面的过程, 首先设置占位符,在上上章中我们说到,样本数量过大,一次性读入内存是不行的。所以引入 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 在测试集上计算正确率。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 6 条回复 时间 点赞

比我年轻,比我还勤奋,我退下了。。

http://keras-cn.readthedocs.io/en/latest/
推荐你看下这个,新手用 keras 更容易上手,做迁移学习更容易。

magicyang 回复

先不看了,以后我们产品要是集成了 keras 再说吧。我现在用 tensorflow 是为了测试用的, 要测试我司的 SDK 和 GPU 能跟 tensorflow 完美交互。 我写这个卷积神经网络也是要测试 tf 运行在我们 GPU 上的性能。

孙高飞 回复

TF 比 KERAS 快 N 多,哈哈~

magicyang 回复

mnist 数据集,2000 轮训练的 cnn 使用 GPU 只用了 11 秒。 速度快的飞起

tf 是行业使用量最大的,还是跟着大平台混吧,不能图简单。

ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册