CIFAR-10 数据集共包含 60000 彩色(RGB)图像,尺寸 32*32。
这些图片总共可分为 10 个分类,用 Python 字典可表示如下:
{ 0 : 'airplane',
1 : 'automobile',
2 : 'bird',
3 : 'cat',
4 : 'deer',
5 : 'dog',
6 : 'frog',
7 : 'horse',
8 : 'ship',
9 : 'truck' }
60000 张图片中,有 50000 张用作训练集,10000 张用于测试。
CIFAR-10 数据集包含很多用于训练机器学习与计算机视觉算法的图片集,它是目前机器学习研究领域使用最广泛的数据集之一。
CIFAR 的全称是 Canadian Institute For Advanced Research,即加拿大高等研究院,致力于建立和维护研究复杂领域的全球研究网络。该机构另外还有一个叫 CIFAR-100 的数据集。这个数据集中有 100 个分类,每个分类有 600 张图片。
除了 CIFAR 以外,有关计算机视觉比较著名的还有 ImageNet 项目。ImageNet 是一个拥有超过 2 万个类别,超过 1400 万张标注图像的大型视觉数据库。
ImageNet 每年会举办一次大规模视觉识别挑战赛(ILSVRC),这项比赛自 2012 年由 AlexNet 夺得冠军之后,就成了深度卷积神经网络大放异彩的舞台。深度神经网络发展至 2015 年,其准确率甚至超过了人类。因此,ImageNet 宣布,2018 年将引入更难的新挑战——3D 图像。
本次实验将通过 Keras 构建一个我们自己的深度神经网络模型,然后使用 CIFAR-10 数据集进行训练,最后评价其准确度。
一、模块导入
导入 numpy。搭建 Keras 的 Sequential 模型,导入 Sequential 类。要导入的其他类也位于他们各自的模块中。
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D
from keras.preprocessing.image import ImageDataGenerator
from keras.utils import np_utils
现在我们要载入 CIFAR-10 数据集。实际上,Keras “内置”了 CIFAR-10 数据集,我们可以直接用 Python 导入。虽然数据集已经“内置”了,但 Keras 要到我们用的时候才会真正下载,毕竟这个数据集还是挺大的。现在我们来载入它。
如果稍后你运行的时候,没有看到 CIFAR-10 下载的过程,那就是本实验的服务器已经提前为你下载好了。
可以看到上述代码从 CIFAR-10 中取出了总共 4 个变量,这样应证了我们第 1.1 节中的关键信息:60000 张图片中,有 50000 张用作训练集,10000 张用于测试。
from keras.datasets import cifar10
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
三、探索数据集
如果你之前没有实际接触过数据集,不要紧,Keras 操作数据集实际上就是在操作 Numpy 数组。
我们首先可以观察一下 CIFAR-10 训练集中的“图片”,我们在末尾添加代码:
print('X_train.shape:', X_train.shape)
这行代码可以在稍后我们运行 cifar.py 时输出 X_train 的“形状”。具体的输出结果我们将在小节末尾解释。我们再加一行来查看训练集中的标签:
print('y_train.shape:', y_train.shape)
在查看形状之后,我们详细看看第一张“图片”以及它的标签:
print('X_train[0]:', X_train[0], '\ny_train[0]:', y_train[0])
保存、退出 nano 之后,我们运行 cifar.py 来查看输出结果,输入命令:
python3 cifar.py
你会得到很长的一串输出结果,我们分别分析。
前 2 行是这样的:
X_train.shape: (50000, 32, 32, 3)
y_train.shape: (50000, 1)
X_train 是训练集中的图片,50000 代表图片的数量;32, 32 代表图片的尺寸,即 32*32;3 表示通道数,由于图片是 RGB 彩色图片,因此有 3 个通道。如果你使用其他库作为 Keras 的后端,此处可能会输出不同的内容,但基本上都是这些信息。
y_train 是训练集的标签,数量 50000 个,和图片数量等同。
输出的最后一行是这样的:
y_train[0]: [6]
y_train[0] 是训练集第一张图片的标签,第 1.1 节中介绍过,CIFAR-10 共有 10 个分类,其中第 6 个是 frog,青蛙,说明训练集的第一张图片是青蛙。
输出的中间还有一长串:
X_train[0]: [[[ 59 62 63]
[ 43 46 45]
[ 50 48 43]
...
[158 132 108]
[152 125 102]
[148 124 103]]
[[ 16 20 20]
[ 0 0 0]
[ 18 8 0]
...
这实际上就是第一张“图片”,不过是“计算机眼中的图片”。图实际上是这样的:
我们已经知道,这张图片的标签是青蛙,仔细一看,确实是一张青蛙的图片。
探索过数据集之后,我们确定了实验的目标:我们的神经网络所要做的事情,就是根据“图片”,预测图片所属的分类;而我们优化模型的目标,则是让模型根据训练集中的图片进行预测的同时,调整参数,使模型预测的结果逼近训练集的标签。
当然了,深度神经网络拥有数量庞大的参数,本实验中,我们需要借用 Keras 的高级 API 来完成训练。
3.1 数据预处理
本小节将带大家探索 CIFAR-10 数据集。输入命令 `nano cifar.py` 打开 nano,继续编辑 cifar.py。
2.4 小节“数据集探索”中,我们输出了很多信息,为了方便查看本小节的输出信息,我们先在末尾写一行代码,用来输出 5 行空行,以及一个标题:
print('\n\n\n\n\n数据预处理')
我们先来定义几个变量,在后面的代码中,它们将作为常量被使用。
height, width, nb_class = 32, 32, 10
分别是图片的高度、宽度,以及分类的数目。
之后我们要对训练集和测试集的图片,即 X 部分做归一化。
X_train = X_train / 255.
X_test = X_test / 255.
除了处理图片 X 之外,我们还需要处理标签 y:
y_train = np_utils.to_categorical(y_train, nb_class)
y_test = np_utils.to_categorical(y_test, nb_class)
最后,我们要遵循少量多次的原则,将数据“喂”给神经网络。通过 Python 的生成器,我们可以很轻松地实现这一需求。Keras 则为我们实现了一个图像数据生成器:
gen = ImageDataGenerator()
上述代码完成了数据的预处理。
我们复制 2.4 小节“数据集探索”中用于输出的代码,比较一下预处理前和预处理后,数据集的差别:
print('X_train.shape:', X_train.shape)
print('y_train.shape:', y_train.shape)
print('X_train[0]:', X_train[0], '\ny_train[0]:', y_train[0])
保存(按下 Ctrl + O,再按下回车)并退出(Ctrl + X)。
输入 python3 cifar.py 运行 cifar.py,可以看到“数据预处理”后的输出信息,我们来和之前对比一下。
X_train 现在变成了这样:
X_train[0]: [[[0.23137255 0.24313725 0.24705882]
[0.16862745 0.18039216 0.17647059]
[0.19607843 0.18823529 0.16862745]
...
而执行之前 X_train 是这样的:
X_train[0]: [[[ 59 62 63]
[ 43 46 45]
[ 50 48 43]
对比可以发现,归一化处理之后的数据,范围归到了 0~1 之间。较小的范围可以减少模型在优化过程中的搜索空间,能有效提升图像分类的准确率。
处理后的标签 y_train[0] 形如:
y_train[0]: [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
包含 10 个数字,从第 0 位开始数,第 6 个元素是 1,其余皆为 0,那么我们就认为这个标签是 6,即青蛙。这样做的原因很简单,分类器对于一张图片的分类,其输出的结果并不是绝对、确定的。
比如对一张青蛙的图片,分类器认为它是青蛙的几率是 80%,它是鸟的几率为 10%,是飞机的概率为 0.01%,等等等等。最终,分类器会返回它认为图片属于各个分类的可能性,这些可能性加在一起就是 100%,程序可以根据哪一个分类的可能性最高,将图片归为该分类。
训练集中有 50000 个这样的标签,因此整个训练集标签矩阵的形状由 (50000, 1) 变为了 (50000, 10),第二项的 10 和分类的数目相同。
3.2 搭建神经网络
本小节将带大家搭建神经网络。输入命令 nano cifar.py 打开 nano,继续编辑 cifar.py。
前面两小节我们输出了很多信息,为了方便查看本小节的输出信息,我们先在末尾写一行代码,用来输出 5 行空行,以及一个标题:
print('\n\n\n\n\n搭建网络')
首先我们要生成一个全新的 Sequantial 模型:
model = Sequential()
之后我们通过 Keras model 的 add() 方法,先添加输入层:
model.add(Conv2D(32, 3, padding='same', input_shape=X_train.shape[1:], activation='relu'))
然后添加卷积和池化层:
model.add(Conv2D(32, 3, activation='relu'))
model.add(MaxPool2D(2))
在卷积层和池化层之后,我们要添加一个 Dropout 层。Dropout 层的作用是在训练过程中,每次更新参数,都随机断开一定的神经元(也可以说是使一部分神经元失活,因此 Dropout 层也叫失活层)。添加 Dropout 层是一种有效的防止过拟合的方法。
model.add(Dropout(0.25))
之后依然是卷积、池化与失活:
model.add(Conv2D(64, 3, padding='same', activation='relu'))
model.add(Conv2D(64, 3, activation='relu'))
model.add(MaxPool2D(2))
model.add(Dropout(0.25))
在这之后,我们要添加一个 Flatten 层,或者叫展开层,它可将高维的张量展开,变成一个一维张量(即向量)。
model.add(Flatten())
之后是全连接层、失活层,以及输出层:
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(nb_class, activation='softmax'))
通过模型的 summary,我们可以从输出信息中得到关于我们搭建的神经网络的一些信息。
model.summary()
保存(按下 Ctrl + O,再按下回车)并退出(Ctrl + X)。
保存、退出 nano 之后,我们运行 cifar.py。输入命令:
python3 cifar.py
上述代码搭建的神经网络,如果通过 keras.utils.vis_utils 中的 plot_model 可视化工具可以得到这样的图片,供大家参考:
4.1 使用凸优化模块训练模型
本小节将带大家训练模型。输入命令 `nano cifar.py` 打开 nano,继续编辑 cifar.py。
前面两小节我们输出了很多信息,为了方便查看本小节的输出信息,我们先在末尾写一行代码,用来输出 5 行空行,以及一个标题:
print('\n\n\n\n\n使用凸优化模块训练模型')
模型的训练是最耗时、耗资源的环节,幸亏有 Keras,我们需要做的事情却非常少。
我们选用 categorical_crossentropy 作为我们的损失函数,或者叫目标函数,这是一个多分类中比较常用的损失函数。
model.compile(loss='categorical_crossentropy', optimizer='Adam', metrics=['accuracy'])
最后调用模型的训练方法:
batch_size = 32
model.fit_generator(gen.flow(X_train, y_train, batch_size=batch_size),
steps_per_epoch=X_train.shape[0] // batch_size,
epochs=20, verbose=1, validation_data=(X_test, y_test))
上述代码使用 X_test, y_test,即测试集,作为验证数据进行训练。这段代码会执行很长一段时间,完成之后,我们就训练好了属于我们自己的神经网络。
注意,这里不用保存退出。训练模型会花费大量时间,本节的内容将和下一节的代码一起运行。
4.2 使用模型进行预测
前面两小节我们输出了很多信息,为了方便查看本小节的输出信息,我们先在末尾写一行代码,用来输出 5 行空行,以及一个标题:
print('\n\n\n\n\n使用模型进行预测')
现在我们可以试试模型的效果了,我们选取测试集 X_test 的前 10 张图片,用训练好的模型进行预测:
y_preds = model.predict(X_test[:10])
为了方便查看输出结果,我们创建一个字典,将标签的值和分类的名字对应起来:
cifar10_cats = {0: 'airplane', 1: 'automobile', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog',
6: 'frog', 7: 'horse', 8: 'ship', 9: 'truck'}
通过以下代码,我们可以比对前 10 张图的真实标签与预测结果:
for i in range(10):
print('正确结果: {}\n预测结果: {}\n'.format(cifar10_cats[np.argmax(y_test[i])],
cifar10_cats[np.argmax(y_preds[i])]))
保存(按下 Ctrl + O,再按下回车)并退出(Ctrl + X),然后输入命令,训练并预测,注意,训练会消耗大量时间:
python3 cifar.py