【实践】第04讲 使用卷积神经网络CNN实现CIFAR的分类和识别

作者:欧新宇(Xinyu OU)

本文档所展示的测试结果,均运行于:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti


【实验目的】

  1. 熟悉卷积神经网络的基本结构,特别是卷积层+激活函数+池化层的叠加结构
  2. 学会使用mini-batch方法实现卷积神经网络的训练并进行预测
  3. 学会保存模型,并使用保存的模型进行预测(即应用模型到生产环境)
  4. 学会使用函数化编程方法完成卷积神经网络的训练和测试
  5. 学会依照网络结构图构建网络模型

【实验要求】

  1. 按照给定的网络体系结构图设计卷积神经网络
  2. 使用CIFAR10的训练集训练设计好的卷积神经网络,并给出测试集上的测试精度
  3. 对给定的测试样本进行预测,输出每一个样本的预测结果该类别概率
  4. 尽力而为地在测试集上获得最优精度(除网络模型不能更改,其他参数均可修改)
  5. 此次作业,只需要下载 类别标签文件batches.meta 及 预测文件infer.zip (Datasets网站-CIFAR10-AIStudio)

将以下内容提交课堂派第四讲作业部分:

1.《实验报告Lec04》实验报告模板

2. 本实验报告的PDF版本(请保留代码运行结果,并确保运行结果的正确)

【实验内容】

一. 模型训练及评估

1.0 导入依赖及全局参数设置

1.1 数据准备

1.1.1 数据准备

CIFAR-10 是一个包含60000张图片的数据集。其中每张照片为32*32的彩色照片,每个像素点包括RGB三个数值,数值范围 0-255。所有照片分属10个不同的类别,分别是: 'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'。其中50000张图片被划分为训练集,剩下的10000张图片属于测试集。

Ch02ex01

数据集官网:http://www.cs.toronto.edu/~kriz/cifar.html
其他下载:http://datasets.ouxinyu.cn

1.1.2 设置训练和测试数据提供器

CIFAR10数据集包含完整图像数据文件,但在paddlepaddle库中依然可以通过内置函数进行调用,其中:

在进行单样本预测时,如果要输出样本的类别名称,需要从官方数据集的标签文件'batches.meta'中读取,具体方法如下:

  1. 解包 batches.meta 文件
  2. 将标签文件赋值并使用列表索引读取 例如:预测标签的id为4,则标签的名称为:
# 创建解包函数
import _pickle as cPickle
import pickle

def unpickle(file):    
    with open(file, 'rb') as fo:
        dict = pickle.load(fo, encoding='bytes')
    return dict

labels = list(unpickle('batches.meta').values())[1]
label_name = labels[4]

# labels 输出结果
labels = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] 
labels[4] = 'deer'

在本例中,所使用的数据集是外部数据,即paddlepaddle并没有内置,因此我们必须使用手动生成的样本列表来构建数据提供器。事实上,这也是最常见的方法。此处仍然使用paddle.batch()来构建数据提供器,以下为关键参数介绍:

1.1.3 数据测试(可选)

1.2 定义过程可视化函数

定义训练过程中用到的可视化方法, 包括训练损失, 训练集批准确率, 测试集准确率. 根据具体的需求,可以在训练后展示这些数据和迭代次数的关系. 值得注意的是, 训练过程中可以每个epoch绘制一个数据点,也可以每个batch绘制一个数据点,也可以每个n个batch或n个epoch绘制一个数据点.

1.3 配置网络

1.3.1 网络拓扑结构图

Ch02ex01

1.3.2 网络参数配置表

将网络结构图转换为配置及参数表如下。

Layer Input Kernels_num Kernels_size Stride Padding PoolingType Output Parameters
Input 3×32×32 3×32×32
Conv1 3×32×32 32 3×5×5 1 0 32×28×28 (3×5×5+1)×32=2432
Pool1 32×28×28 32 32×2×2 2 0 Max 32×14×14 0
Conv2 32×14×14 32 32×5×5 1 0 32×10×10 (32×5×5+1)×32=25632
Pool2 32×10×10 32 32×2×2 2 0 Avg 32×5×5 0
Conv3 32×5×5 64 32×4×4 1 0 64×2×2 (32×4×4+1)×64=32832
Pool3 64×2×2 64 64×2×2 2 0 Avg 64×1×1 0
FC1 64×1 64×1 64×64=4096
FC2 64×1 64×10 64×10=640
Output 10×1
Total = 65630

使用动态图模式比静态图要简单很多,只需要定义模型结构即可。模型定义需要使用Object-Oriented-Designed面向对象的类进行定义。以下为该前馈神经网络(多层感知机)的网络构建函数。此处我们使用动态图模式进行网络结构的定义。首先定义了一个多层感知机的类class CNN(fluid.dygraph.Layer)。在该类别使用__inin__(self)对参数进行初始化,并定义前向传播forward()方法。

# 以上是线性核Linear的说明,其中第一项为输入维度,第二项为输出维度,act为激活函数。对应于公式 $Out=Act(XW+b)$. 其中,X 为输入的 Tensor, W 和 b 分别为权重和偏置。
class paddle.fluid.dygraph.Linear(input_dim, output_dim, param_attr=None, bias_attr=None, act=None, dtype='float32')
# num_channels:输入维度, num_filters:卷积核个数, filter_size:卷积核尺度, stride:步长, padding:填充尺度, dilation:洞的尺度, dtype='float32')
class paddle.fluid.dygraph.Conv2D(num_channels, num_filters, filter_size, stride=1, padding=0, dilation=1, dtype='float32')
# input:输入维度, pool_size:池化尺度, pool_type:池化类型'max|avg', pool_stride:池化步长, pool_padding:池化填充, global_pooling:是否使用全局池化'False|True', 
class paddle.fluid.layers.pool2d(input, pool_size=-1, pool_type='max', pool_stride=1, pool_padding=0, global_pooling=False, data_format="NCHW")

1.3.3 定义神经网络类

1.3.4 输出网络各层的超参数

1.4 模型训练及评估

1.4.1 模型训练及在线测试函数

- 定义测试函数

测试部分的具体流程包括:

  1. 设置模型运行模式为验证模式model.eval()
  2. 基于周期epoch-批次batch的结构进行两层循环训练,具体包括:
    1). 定义输入层(image,label),图像输入维度 [batch, channel, Width, Height] (-1,3,32,32),标签输入维度 [batch, 1] (-1,1)
    2). 定义输出层,包括前向传播的输出predict=model(image)及精度accuracy。如果需要,还可以输出针对测试集的损失loss。
    值得注意的,在计算测试集精度的时候,需要对每个批次的精度/损失求取平均值。

- 定义训练函数

在动态图模式下,所有的训练测试代码都需要基于动态图守护进程fluid.dygraph.guard(PLACE)

训练部分的具体流程包括:

  1. 模型实例化,并设置为训练模式model.train()
  2. 定义优化器optimizer
  3. 基于周期epoch-批次batch的结构进行两层循环训练,具体包括:
    1). 定义输入层(image,label),图像输入维度 [batch, channel, Width, Height] (-1,3,32,32),标签输入维度 [batch, 1] (-1,1)
    2). 定义输出层,包括前向传播的输出predict=model(image),损失loss及平均损失,精度accuracy。
    3). 执行反向传播,并将损失最小化,清除梯度

在训练过程中,可以将周期,批次,损失及精度等信息打印到屏幕。

值得注意的是,在每一轮的训练中,每100个batch之后会输出一次平均训练误差和准确率。每一轮训练之后,使用测试集进行一次测试,在每轮测试中,均打输出一次平均测试误差和准确率。

【注意】注意在下列的代码中,我们每个epoch都执行一次模型保存,这种方式一般应用在复杂的模型和大型数据集上。这种经常性的模型保存,有利于我们执行EarlyStopping策略,当我们发现运行曲线不再继续收敛时,就可以结束训练,并选择之前保存的最好的一个模型作为最终的模型。FinalModel

- 执行训练主函数

1.4.2 离线测试

离线测试同样要基于动态守护框架fluid.dygraph.guard()。测试过程与训练过程中的在线测试流程基本一致,只需要提前实现载入已保存的模型即可,载入模型使用fluid.load_dygraph()方法。

二. 模型预测(应用)

在实际应用中,下面的工作可以视作是生产环境的具体应用。在研究中,也可以当作最终的结果对比。模型预测包含三个部分:

  1. 导入依赖库及全局参数配置
  2. 获取待预测数据,并进行初始化
  3. 载入模型并开始进行预测

2.1 导入依赖库及全局参数配置

2.2 获取待预测数据

获取待测数据包含两个步骤: 1.获取图像路径; 2. 读取图像并进行预处理,分别由下列两个函数完成

2.3 载入模型并开始进行预测

# 模型定义
class CNN(fluid.dygraph.Layer):
    # 模型定义与模型训练时一致