Deep Learning-TensorFlow (4) CNN卷积神经网络_CIFAR-10进阶图像分类模型(上)

来源:互联网 发布:粒子群算法 编辑:程序博客网 时间:2024/06/05 05:26

环境:Win8.1 TensorFlow1.0.1

软件:Anaconda3 (集成Python3及开发环境)

TensorFlow安装:pip install tensorflow (CPU版) pip install tensorflow-gpu (GPU版)

代码参考:TensorFlow cifar-10 示例代码


完整代码可在 @DiamonJoy下载,针对旧版修正如下:

  • 修改 Loss(),改用 sparse_softmax_cross_entropy_with_logits();
  • 修正Summary API;
  • 修正部分 Image API 和 计算 Op;
  • 关键代码添加中文注释。


1. CIFAR-10

Cifar-10 是由 Hinton 的两个大弟子 Alex Krizhevsky、Ilya Sutskever 收集的一个用于普适物体识别的数据集。Cifar 是加拿大政府牵头投资的一个先进科学项目研究所。Hinton、Bengio和他的学生在2004年拿到了 Cifar 投资的少量资金,建立了神经计算和自适应感知项目。这个项目结集了不少计算机科学家、生物学家、电气工程师、神经科学家、物理学家、心理学家,加速推动了 Deep Learning  的进程。从这个阵容来看,DL 已经和 ML 系的数据挖掘分的很远了。Deep Learning 强调的是自适应感知和人工智能,是计算机与神经科学交叉;Data Mining 强调的是高速、大数据、统计数学分析,是计算机和数学的交叉。


Cifar-10 由60000张32*32的 RGB 彩色图片构成,共10个分类。50000张训练,10000张测试(交叉验证)。这个数据集最大的特点在于将识别迁移到了普适物体,而且应用于多分类(姊妹数据集Cifar-100达到100类,ILSVRC比赛则是1000类)。


可以看到,同已经成熟的人脸识别相比,普适物体识别挑战巨大,数据中含有大量特征、噪声,识别物体比例不一。因而,Cifar-10 相对于传统图像识别数据集,是相当有挑战的。想了解更多信息请参考CIFAR-10 page,以及 Alex Krizhevsky 的技术报告。


2. 模型

在前面的博文中,我们已经利用 TensorFlow 建立起一个简单的手写数字识别的 MNIST 模型,主要参考 Yann LeCun 在 1998 年发表的论文 Gradient-Based Learning Applied to Document Recognition 中所提出的经典的 LeNet5网络:


这个网络由卷积层、池化层、全连接层组成,通过梯度下降法对参数进行训练。但是面对复杂的普适物体分类问题,网络结构已经远远不能满足需求。


本篇博文将解析上篇 AlexNet 网络的针对普适物体分类问题采用的改良技术,防止模型过拟合和增强了范化能力:

  • 对图片进行了翻转、随机剪切等数据增广(Data Augmentation);
  • 在卷积-最大池化层后面使用局部响应归一化 (LRN);
  • 采用修正线性激活 (ReLu)、Dropout、重叠 Pooling

以及在 CIFAR-10 上的 TensorFlow 代码实现,加入了为输入数据设计预存取队列,网络行为的可视化,维护参数的滑动均值,设置学习率随着迭代递减,最后在 Losses 中加入对 weight 的 L2 正则训练,提高网络的训练速度和识别率。


本文将使用的代码结果和网络结构:


文件说明cifar10_input.py读取本地CIFAR-10的二进制文件格式的内容cifar10.py建立CIFAR-10的模型cifar10_train.py在CPU或GPU上训练CIFAR-10的模型cifar10_multi_gpu_train.py在多GPU上训练CIFAR-10的模型。cifar10_eval.py评估CIFAR-10模型的预测性能

参考代码中提供了模型的多GPU版本,但本文只使用 CPU 运行。

3. 网络结构

CIFAR-10 网络模型部分的代码位于 cifar10.py,完整的训练图中包含约765个操作。通过下面的模块来构造训练图可以最大限度的提高代码复用率:

  • 模型输入: 包括 inputs()、distorted_inputs() 等一些操作,分别用于读取 CIFAR-10 的图像并进行预处理,做为后续评估和训练的输入;
  • 模型预测: 包括 inference() 等一些操作,用于进行统计计算,比如在提供的图像进行分类;
  • 模型训练: 包括 loss() and train() 等一些操作,用于计算损失、计算梯度、进行变量更新以及呈现最终结果。

3.1 模型输入

输入模型是通过 cifar10_input.inputs() 和 cifar10_input.distorted_inputs() 函数建立起来的,这2个函数会从 CIFAR-10 二进制文件中读取图片文件,具体实现定义在 cifar10_input.py 中,使用的数据为 CIFAR-10 page 下的162M 的二进制文件,由于每个图片的存储字节数是固定的,因此可以使用 tf.FixedLengthRecordReader 函数。

载入图像数据后,通过以下流程进行数据增广:

  1. 统一裁剪到24x24像素大小,裁剪中央区域用于评估或随机裁剪用于训练;
  2. 对图像进行随机的左右翻转;
  3. 随机变换图像的亮度;
  4. 随机变换图像的对比度;
  5. 图片会进行近似的白化处理。

其中,白化(whitening)处理或者叫标准化(standardization)处理,是对图片数据减去均值,除以方差,保证数据零均值,方差为1,如此降低输入图像的冗余性,尽量去除输入特征间的相关性,使得网络对图片的动态范围变化不敏感。和 Caffe 中的均值文件一个原理。

在 Images 页的列表中查看所有可用的变换,对于每个原始图还添加 tf.summary.image,以便于在 TensorBoard中 查看:




从磁盘上加载图像并进行变换需要花费不少的处理时间。为了避免这些操作减慢训练过程,使用16个独立的线程中并行进行这些操作,这16个线程被连续的安排在一个 TensorFlow 队列中,最后返回预处理后封装好的tensor,每次执行都会生成一个 batch_size 数量的样本 [images,labels]。测试数据使用cifar10_input.inputs() 函数生成,测试数据不需要对图片进行翻转或修改亮度、对比度,需要裁剪图片正中间的24*24大小的区块,并进行数据标准化操作。  


以上用到主要函数:

maybe_download_and_extract():              # 下载并解压数据distorted_inputs(data_dir, batch_size):    # 读入数据并数据增广read_cifar10(filename_queue):      # 读取二进制数据tf.random_crop();                  # 随机裁剪,旧版为tf.image.random_croptf.image.random_flip_left_right(); # 左右翻转tf.image.random_brightness();      # 变换图像的亮度tf.image.random_contrast();        #  变换图像的对比度tf.image.per_image_standardization(); # 对图像进行标准化,旧版为 tf.image.per_image_whitening_generate_image_and_label_batch();    # 使用 tf.train.shuffle_batch() 创建多个线程从 tensor 队列中构建 batch

3.2 模型预测

模型的预测流程由 inference() 构造,输入为 images,输出为最后一层的 logits。

在建立模型之前,我们构造 weight 的构造函数 _variable_with_weight_decay(name, shape, stddev, wd),其中 wd 用于向 losses 添加L2正则化,可以防止过拟合,提高泛化能力:

def _variable_with_weight_decay(name, shape, stddev, wd):  var = _variable_on_cpu(name, shape,                         tf.truncated_normal_initializer(stddev=stddev))  if wd:    weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss')    tf.add_to_collection('losses', weight_decay)  return var


然后我们开始建立网络,第一层卷积层的 weight 不进行 L2正则,因此 kernel(wd) 这一项设为0,建立值为0的 biases,conv1的结果由 ReLu 激活,由 _activation_summary() 进行汇总;然后建立第一层池化层,最大池化尺寸和步长不一致可以增加数据的丰富性;最后建立 LRN 层(详细介绍参考上篇博文),LRN层模仿了生物神经系统的"侧抑制"机制,对局部神经元的活动创建竞争环境,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力,LRN 对 Relu 这种没有上限边界的激活函数会比较有用,因为它会从附近的多个卷积核的响应中挑选比较大的反馈,但不适合 sigmoid 这种有固定边界并且能抑制过大的激活函数。

# conv1  with tf.variable_scope('conv1') as scope:    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 3, 64],                                         stddev=1e-4, wd=0.0)    conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME')    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0))    bias = tf.nn.bias_add(conv, biases)    conv1 = tf.nn.relu(bias, name=scope.name)    _activation_summary(conv1)  # pool1  pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],                         padding='SAME', name='pool1')  # norm1  norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,                    name='norm1')

第二层卷积层与第一层,除了输入参数的改变之外,将 biases 值全部初始化为0.1,调换最大池化和 LRN 层的顺序,先进行LRN,再使用最大池化层。

# conv2  with tf.variable_scope('conv2') as scope:    kernel = _variable_with_weight_decay('weights', shape=[5, 5, 64, 64],                                         stddev=1e-4, wd=0.0)    conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME')    biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1))    bias = tf.nn.bias_add(conv, biases)    conv2 = tf.nn.relu(bias, name=scope.name)    _activation_summary(conv2)  # norm2  norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75,                    name='norm2')  # pool2  pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1],                         strides=[1, 2, 2, 1], padding='SAME', name='pool2')


第三层全连接层 ,需要先把前面的卷积层的输出结果全部 flatten,使用 tf.reshape 函数将每个样本都变为一维向量,使用 get_shape 函数获取数据扁平化之后的长度;然后对全连接层的 weights 和 biases 进行初始化,为了防止全连接层过拟合,设置一个非零的 wd 值0.004,让这一层的所有参数都被 L2正则所约束,最后依然使用 Relu 激活函数进行非线性化。同理,可以建立第四层全连接层。
# local3  with tf.variable_scope('local3') as scope:    # Move everything into depth so we can perform a single matrix multiply.        reshape = tf.reshape(pool2, [FLAGS.batch_size, -1])    dim = reshape.get_shape()[1].value         weights = _variable_with_weight_decay('weights', shape=[dim, 384],                                          stddev=0.04, wd=0.004)    biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1))    local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)    _activation_summary(local3)


最后的 softmax_linear 层,先创建这一层的 weights 和 biases,不添加L2正则化。在这个模型中,不像之前的例子使用 sotfmax 输出最后的结果,因为将 softmax 的操作放在来计算 loss 的部分,将 softmax_linear 的线性返回值 logits 与 labels 计算 loss,下一篇博文将会讲述。

# softmax, i.e. softmax(WX + b)  with tf.variable_scope('softmax_linear') as scope:    weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES],                                          stddev=1/192.0, wd=0.0)    biases = _variable_on_cpu('biases', [NUM_CLASSES],                              tf.constant_initializer(0.0))    softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name)    _activation_summary(softmax_linear)  return softmax_linear

至此,整个网络的 inference 构建完毕,用 TensorBoard 可查看此结构:




1 0
原创粉丝点击