翻译:张睿毅
校对:吴金笛
本文约9300字,建议阅读20分钟。
本文章逐步介绍了卷积神经网络的建模过程,最终实现了MNIST手写数字分类。
MNIST手写数字分类问题是计算机视觉和深度学习中使用的标准数据集。
尽管数据集得到了有效的解决,但它可以作为学习和实践如何开发、评估和使用卷积深度学习神经网络从头开始进行图像分类的基础。这包括如何开发一个用于评估模型性能的强大测试工具,如何探索模型的改进,以及如何保存模型,然后加载它以对新数据进行预测。
在本教程中,您将了解如何从头开始开发用于手写数字分类的卷积神经网络。
完成本教程后,您将了解:
如何开发测试工具以开发稳健的模型评估并建立分类任务性能的基准线
如何在基准模型上拓展以改进学习及模型容量
如何开发最终模型,评估最终模型性能,并用它对于新图像进行预测
让我们开始吧!
Photo by Richard Allaway, some rights reserved
图片来源:https://www.flickr.com/photos/geographyalltheway_photos/2918451763/
教程概览
教程的五个部分分别是:
1. MNIST手写数字分类数据集
2. 模型评估方法
3. 如何建立基准模型
4. 如何建立改进模型
5. 如何完成模型建立并进行预测
1. MNIST 手写数字分类数据集
MNIST数据集是修改后的国家标准与技术研究所数据集的缩写。
它是一个由60000个28×28像素的小正方形灰度图像组成的数据集,这些图像的手写单位数介于0和9之间。
任务是将手写数字的给定图像分类为10个类中的一个,这些类表示0到9之间的整数值(包括0和9)。
它是一个广泛使用和深入理解的数据集,并且在大多数情况下是“已解决”的。性能最好的模型是深度学习卷积神经网络,其分类精度达到99%以上,保留测试数据集的错误率在0.4%到0.2%之间。
下面的示例使用keras API加载MNIST数据集,并创建训练数据集中前九个图像的绘图。
# example of loading the mnist dataset
from keras.datasets import mnist
from matplotlib import pyplot
# load dataset
(trainX, trainy), (testX, testy) = mnist.load_data()
# summarize loaded dataset
print('Train: X=%s, y=%s' % (trainX.shape, trainy.shape))
print('Test: X=%s, y=%s' % (testX.shape, testy.shape))
# plot first few images
for i in range(9):
# define subplot
pyplot.subplot(330 + 1 + i)
# plot raw pixel data
pyplot.imshow(trainX[i], cmap=pyplot.get_cmap('gray'))
# show the figure
pyplot.show()
运行该示例将加载MNIST训练和测试数据集并打印它们的形状。
我们可以看到训练数据集中有60000个例子,测试数据集中有10000个例子,图像确实是28×28像素的正方形。
Train: X=(60000, 28, 28), y=(60000,)Test: X=(10000, 28, 28), y=(10000,)
同时,我们创建了数据集中前九个图像的绘图,显示待分类图像的自然手写性质。
从MNIST数据集中选出的子集
2. 模型评估方法
尽管MNIST数据集得到了有效的解决,但使用卷积神经网络解决图像分类任务的方法可以作为开发和实践的一个有用的起点。
我们可以从头开始开发一个新的模型,而不是回顾数据集上性能良好的模型的文献。
数据集已经有了一个明确定义的训练和测试数据集,我们可以使用它。
为了估计给定训练运行模型的性能,我们可以进一步将训练集划分为训练和验证数据集。然后,可以绘制每次运行的训练和验证数据集的性能,以提供学习曲线,并洞察模型学习问题的程度。
keras API通过在训练模型时向 model.fit() 函数指定 “validation_data” 参数来支持这一点,该参数将返回一个对象,该对象描述了每个训练阶段所选损失和指标的模型性能。
# record model performance on a validation dataset during training
history = model.fit(..., validation_data=(valX, valY))
为了估计一个模型在一般问题上的性能,我们可以使用k倍交叉验证,或者5倍交叉验证。这将在训练和测试数据集的差异以及学习算法的随机性方面,给出一些模型的方差。考虑到标准差,模型的性能可以作为k-折叠的平均性能,如果需要,可以用它来估计置信区间。
我们可以使用scikit Learn API中的Kfold类来实现给定神经网络模型的k重交叉验证评估。虽然我们可以选择一种灵活的方法,其中kfold类只用于指定每个spit所用的行索引,但实现这一点的方法有很多种。
我们将保留实际的测试数据集,并将其用作最终模型的评估。
3. 如何建立基准模型
第一步是建立一个基准模型。
这一点很关键,因为它即涉及到为测试工具开发基础设施,以便我们设计的任何模型都可以在数据集上进行评估,并且它在模型性能方面建立了一个基线,通过这个基线可以比较所有的改进。
测试工具的设计是模块化的,我们可以为每个部件开发单独的功能。如果我们愿意的话,这允许对测试工具的某个特定方面进行修改或相互更改,独立于其他部分。
我们可以用五个关键要素开发这个测试工具。它们是数据集的加载、数据集的准备、模型的定义、模型的评估和结果的表示。
加载数据集
我们对数据集已经有一些了解。
例如,我们知道图像都是预先对齐的(例如,每个图像只包含一个手绘数字),所有图像都具有相同的28×28像素的正方形大小,并且图像是灰度的。
因此,我们可以加载图像并将数据数组整形为具有单一颜色通道。
# load dataset (trainX, trainY), (testX, testY) = mnist.load_data() # reshape dataset to have a single channel trainX = trainX.reshape((trainX.shape[0], 28, 28, 1)) testX = testX.reshape((testX.shape[0], 28, 28, 1)) |
我们还知道有10个类,这些类被表示为唯一的整数。
因此,我们可以对每个样本的类元素使用一个热编码,将整数转换为一个10元素的二进制向量,其中1表示类值的索引,0表示所有其他类的值。我们可以使用to_categorial()实际程序函数来实现这一点。
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
load_dataset()函数实现这些行为,并可以被用作加载数据集。
# load train and test dataset
def load_dataset():
# load dataset
(trainX, trainY), (testX, testY) = mnist.load_data()
# reshape dataset to have a single channel
trainX = trainX.reshape((trainX.shape[0], 28, 28, 1))
testX = testX.reshape((testX.shape[0], 28, 28, 1))
# one hot encode target values
trainY = to_categorical(trainY)
testY = to_categorical(testY)
return trainX, trainY, testX, testY
准备像素数据
我们知道,数据集中每个图像的像素值都是介于黑白或0到255之间的无符号整数。
我们不知道缩放用于建模的像素值的最佳方法,但我们知道需要进行一些缩放。
一个好的起点是规范化灰度图像的像素值,例如将其重新调整到范围[0,1]。这涉及到首先将数据类型从无符号整数转换为浮点数,然后将像素值除以最大值。
# convert from integers to floats
train_norm = train.astype('float32')
test_norm = test.astype('float32')
# normalize to range 0-1
train_norm = train_norm / 255.0
test_norm = test_norm / 255.0
以下的prep_pixels()函数实现这些行为,我们提供这些从训练和测试数据集中需要被测量的像素值给这个函数。
# scale pixels
def prep_pixels(train, test):
# convert from integers to floats
train_norm = train.astype('float32')
test_norm = test.astype('float32')
# normalize to range 0-1
train_norm = train_norm / 255.0
test_norm = test_norm / 255.0
# return normalized images
return train_norm, test_norm
这个函数必须被调用以在任何模型之前准备好像素值。
定义模型
接下来,我们需要为问题定义一个基线卷积神经网络模型。
该模型主要有两个部分:前端特征提取由卷积层和池化层组成,后端分类器进行预测。
对于卷积前端,我们可以从单个卷积层开始,该卷积层具有较小的过滤器大小(3,3)和少量的过滤器(32),然后是最大池化层。然后可以展平过滤器映射,为分类器提供特性。
考虑到该问题是一个多类分类任务,我们知道我们需要一个具有10个节点的输出层来预测属于这10个类中每个类的图像的概率分布。这还需要使用SoftMax激活功能。在特性提取器和输出层之间,我们可以添加一个全连接层来解释特性,在本例中是100个节点。
所有层都将使用relu激活函数和He 权重初始化方案,这两个都是最佳方法。
我们将对学习率为0.01,动量为0.9的随机梯度下降优化器使用保守配置。分类交叉熵损失函数将得到优化,适用于多类分类,我们将监测分类精度指标,这是适当的,因为我们在10个类中的每一类都有相同数量的例子。
下面的define_model()函数将定义并返回此模型。
# define cnn model
def define_model():
model = Sequential()
model.add(Conv2D(32, (3, 3), activation='relu', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(100, activation='relu', kernel_initializer='he_uniform'))
model.add(Dense(10, activation='softmax'))
# compile model
opt = SGD(lr=0.01, momentum=0.9)
model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])
return model
评估模型
模型定义后,我们需要对其进行评估。
模型将通过五重交叉验证进行评估。选择k=5的值为重复评估提供基线,并且不需要太长的运行时间。每个测试集将是训练数据集的20%,或大约12000个示例,接近此问题的实际测试集大小。
训练数据集在分割前进行洗牌,每次都进行样本洗牌,这样我们评估的任何模型在每个折叠中都将具有相同的训练和测试数据集,从而提供模型之间的逐个比较。
我们将为一个适度的10个训练阶段培训基线模型,默认批量大小为32个示例。每个阶段的测试集将用于评估模型在训练运行的每个阶段,以便我们可以稍后创建学习曲线,并在运行结束时,以便我们可以评估模型的性能。因此,我们将跟踪每次运行的结果历史,以及折叠的分类精度。
下面的evaluate_model()函数实现了这些行为,将定义的模型和培训数据集作为参数,并返回一个精度分数和训练历史的列表,这些列表可以稍后进行总结。
# evaluate a model using k-fold cross-validation
def evaluate_model(model, dataX, dataY, n_folds=5):
scores, histories = list(), list()
# prepare cross validation
kfold = KFold(n_folds, shuffle=True, random_state=1)
# enumerate splits
for train_ix, test_ix in kfold.split(dataX):
# select rows for train and test
trainX, trainY, testX, testY = dataX[train_ix], dataY[train_ix], dataX[test_ix], dataY[test_ix]</