Press "Enter" to skip to content

深度学习笔记:基于Keras库的MNIST手写数字识别

目录

 

6 训练模型:Fitting/Training the model

 

7 使用训练好的做预测/推断

 

1 前言

 

本文介绍基于Keras库创建一个最简单的神经网络模型进行MNIST手写数字识别的试验。麻雀虽小五脏俱全,模型虽然简单但是这个试验基本上覆盖了一个深度学习任务的完整流程中各主要的基本步骤。这是深度学习的一个“Hello, World”式的试验。以此为基础就可以通过添枝加叶探索深度学习世界啦。

 

Keras最早是独立开发(Ref(1)作者正式Keras的作者)的,后来被Google收购并集成到Tensorflow中了。

 

2 数据加载和确认

 

MNIST数据集为Keras内置数据集,当然所谓的内置数据集并不是说数据文件本身内置于Tensorflow/Keras库,而是说其中封装了mnist数据处理相关函数,这样以以下方式导入mnist module,调用load_data()函数就会自动从网上下载MNIST数据。在同一环境中,下载后的数据会存放一个tensorflow/keras认识的数据缓存区,所以你不用担心它每次重新运行会傻傻地重新下载。它会先检查以前是不是下载过,没有的话才会去重新下载。

 

当然,你如果是离线作业,那你也可以先把数据下载下来,然后用另外的方式加载数据即可。这个以后在涉及到其它非内置数据使用时再来学习。

 

注意load_data()加载(下载)数据时顺便做完了训练集(train set)和测试集(test set)的分割。

 

from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

 

通常数据加载后要做的第一件事情就是对数据做基本的确认,包括各种可视化手段。获得对数据的第一手认识。稍微看一下数据长的啥样。首先看看各个数据的维度是什幺。

 

print(train_images.shape, train_labels.shape)
print(test_images.shape, test_labels.shape)
print(train_labels)
print(test_labels)

 

(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
[5 0 4 ... 5 6 8]
[7 2 1 ... 4 5 6]

 

由此可以看出训练集和测试集分别包含60000个和10000个数据样本,每个数据样本是一个28*28的数组,用以表示一个768像素的图片。

 

然后以图片的方式看看训练数据的的长相。

 

import matplotlib.pyplot as plt
digit = train_images[4]
plt.imshow(digit, cmap=plt.cm.binary)
plt.show()
train_labels[4]

 

 

嗯嗯,确实是一个手写数字。打印出来的标签值train_labels[4]表明它是9,与图片吻合。

 

3 构建模型

 

深度神经网络的核心组件是层(layer),它是一种数据处理模块,可以理解为数据过滤器,对输入数据进行一些变化,以得到一种新的表示。深度神经网络无非就是将很多的层摞起来,从而实现渐进式的数据蒸馏(data distillation)。

 

以下构建了一个最简单的神经网络,只有2个Dense层,Dense层也称为密集连接层或者全连接层(是与像卷积层相对的概念,这个暂且不提,以后再说)。第1层使用relu激励函数,这是深度神经网络除了最后一层(输出层)最常用的激励函数(之一?)。当你不知道用什幺激励函数时,先用RELU总不会差到哪里去。第2层也即最后一层(输出层)根据不同的深度学习任务可能采用不同的激励函数,对于像MNIST这种多分类任务来说,几乎可以肯定它的首选是softmax.

 

如上所述,深度神经网络无非就是将很多的层摞起来,所以构建一个深度神经网络模型就跟搭积木差不多。Keras提供了两种搭积木的方式,一种是函数API方式,另一种是使用Sequential类。这里我们采用第二种方式,第一种方式我们会在以后学到。这里不用关心Sequential类是什幺东西,用多了用熟了慢慢知道它能干啥、怎幺用就可以了(这类实践式技能的学习大抵都是这样的。好比学游泳,你对着游泳学习手册背怎幺挥手、怎幺打腿、怎幺换气,把概念背熟到飞起,并没有什幺卵用。得先一头扎到水里去扑腾,然后再回头来学习概念、理论性的东西)。

 

from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
    layers.Dense(512, activation="relu"),
    layers.Dense(10, activation="softmax")
])

 

4 编译模型

 

搭建完模型后在使用之前要先调用compile()函数进行编译,编译时要给编译器提供关于模型的参数,主要有以下三个信息:

 

(1) 优化器(optimizer),或者说优化算法

 

(2) 损失函数(loss function),模型训练过程基于什幺来衡量模型是在往好的方向发展还是往坏的方向发展。损失函数也称代价函数,意思是说模型如果猜“对”了其损失/代价会小一些,猜“错”了其损失/代价会大一些,基于反馈损失函数值模型训练就可以调整模型参数往正确的方向发展。

 

(3) 性能度量:在训练和测试过程中需要监控的指标/度量(metric)。注意不要把这个跟损失函数混淆在一起,虽然它们都是模型优劣的度量,但是性能度量是最终的度量,而损失函数是中间度量。把最终度量用作训练的指引的话会显得过于粗糙反馈信息的精度不够,而损失函数则可以提供更精确的中间信息助力训练过程。本例为10分类问题,只要关心正确分类的比例即可,所以用”accuracy”即可。注意metrics参数的传递方法是指定一个列表,这意味着可以指定的性能度量项可以不限于一项。注意,经常可以看到有人把”accuracy”译成‘精度’,这是属于概念性错误,精度对应”precision”与accuracy好像挺像其实完全不是一回事。

 

model.compile(optimizer="rmsprop",
              loss="sparse_categorical_crossentropy",
              metrics=["accuracy"])

 

5 数据预处理

 

上面所搭建的模型对于输入数据格式有一定的要求,比如说要求数据格式为float32类型,而原始MNIST数据集中数据是以整型int8格式存储的。所以首先需要进行格式转换。

 

其次,通常我们会对数据进行normalization,特别是对于存储不同类型不同scale的features时。对于像本例这样的数据范围为[0,255]的图像数据通常的做法是将其变换到[0,1]之间。

 

以下这段代码将数据类型转换为’float32’以及将将数据范围变换到[0,1]区间。

 

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255

 

6 训练模型:Fitting/Training the model

 

接下来就可以把数据喂给以上搭建好的模型进行训练啦!

 

Keras模型用fit()函数表示训练其调用格式也基本上继承了scikit-learn风格。第1个参数表示训练数据,第2个参数表示标签数据(本例为监督学习),第3个参数epochs表示训练多少轮(对完整的数据集扫描一遍进行训练叫做一个epoch),第4个参数batch_size表示整个训练集分为多大的数据块进行分别处理。将整个数据集作为一个数据块训练叫batch mode(批量模式),分为多个数据块(通常是相同大小的)逐个处理的话叫做minibatch mode(小批量模式),逐个数据进行训练处理的话叫做stochastic mode。stochastic mode其实就是batch_size=1时的minibatch mode的特例。反之,如果batch_size等于训练集大小的话就是batch mode了。把batch_size传递给模型,它自动计算需要分为多少个mini-batch。如果除不尽的话,是向下取整,最后不足部分舍弃。

 

以下代码中指定了训练5轮,小批量训练数据块大小为128个数据样本。

 

model.fit(train_images, train_labels, epochs=5, batch_size=128)

 

正常的话你将看到如下训练过程:

 

 

如上所示,在5轮训练过后,在训练集上的准确度可以达到98.9%,非常不错!而且也可以看到随着训练轮数的增加,loss在持续减小,而accuracy则持续增大。但是第5轮相比第4轮的边际增加很小,这很可能说明第5轮训练这多余的,即有可能踩到了overfit红线,这个以后再谈。

 

7 使用训练好的做预测/推断

 

从测试集中取1个数据样本看看以上训练好的模型能不能把它们正确地识别出来。

 

test_digits = test_images[0:10]
predictions = model.predict(test_digits)
print(predictions[2])
print(predictions[2].argmax())
test_labels[2]

 

[4.6298595e-08 9.9950302e-01 1.9649169e-04 8.8730678e-07 2.0431406e-05
 9.9562667e-06 3.1885143e-06 1.3386145e-04 1.3205952e-04 1.3534594e-07]
1

 

注意,本模型是个十分类模型,针对每个测试数据样本,模型输出的是一个10维向量(不要与10维张量混淆!)。该向量的每个元素代表一个分类的概率,其中概率最大的项所对应的分类即为推断结果。如上所示,predictions[2]表示针对第3个测试数据的模型输出,它的第2项为0.9995,表示模型非常肯定这个数字是1。由测试集对应的标签也可以知道这个数字确实是1.

 

8 在测试集上评估模型

 

要评估模型的性能像上面这样一个一个地看肯定不行。Keras提供了evaluate()函数用于进行模型性能评估,将测试数据集测试标签集一起传递给evaluate()函数即可。

 

test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"test_acc: {test_acc}")

 

313/313 [==============================] - 1s 2ms/step - loss: 0.0698 - accuracy: 0.9802
test_acc: 0.9801999926567078

 

如上所示,在测试集上的分类准确度为98.1%比训练集上的98.9%略微差一丢丢。

 

9 Summary

 

以上我们用tensorflow.keras库搭建一个简单的仅基于Dense later的两层神经网络模型,用于MNIST数据集(手写体数字)的识别,达到了98%的分辨准确度!

 

总结一下整个过程分为以下几个步骤:

 

(1) 数据加载和可视化确认

 

(2) 搭建模型

 

(3) 编译模型

 

(4) 数据预处理

 

(5) 模型训练

 

(6) 使用训练好的模型进行预测/推断

 

(7) 在测试集上进行模型性能评估

 

期待后续更加精彩的旅程!

 

Ref:

 

(1) Francois Chollet: Deep Learning with Python–>此书中文版已出版

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注