keras实现手写数字识别

所用环境为Colab.

定义任务目标

拿到一些手写数字的图片, 每张图片上含有一个手写的数字, 把这些数字识别出来, 划分到10个类别中(从0到9).

数据收集

数据收集

使用 MNIST 数据集.

1
2
3
4
5
6
7
8
9
# 加载数据集(需联网)
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# 检查数据
train_images.shape, train_labels.shape, test_images.shape, test_labels.shape, train_images.dtype
# ((60000, 28, 28), (60000,), (10000, 28, 28), (10000,), dtype('uint8'))

train_images.ndim # 训练集张量的轴的个数

数据可视化

看一下数据集里的图片到底长什么样子, 图片对应的标签是什么.

1
2
3
4
5
6
7
8
9
import matplotlib.pyplot as plt

# 显示训练集中的第5张图片
digit = train_images[4]
plt.imshow(digit, cmap=plt.cm.binary)
plt.show()

# 查看训练集中的第5张图片的标签
train_labels[4]

数据标注

已标注好

数据清理

无需清理

选择模型评估方法

确定简单基准(模型应能超越这个基准)

数字一共有10类(0~9), 如果为手写数字图片随机指定分类, 能分类正确的概率为10%, 因此建立的模型的分类正确率应该超过10%.

评估方法选择

这里选择精度(accuracy)作为模型评估的指标. 精度, 即正确分类的图像所占比例.

怎样用? 构建好模型后, 在模型编译阶段, 将model.compile()metrics参数值设定为["accuracy"]

构建第一个模型

特征选择(过滤没有信息量的特征; 开发新特征)

在本例中特征就是图片的张量值了.

选择架构

两个密集连接层(全连接层).

层1: Dense层, 输出空间的维数units512, 激活函数activationrelu.

层2: Dense层, 输出空间的维数units10, 激活函数activationsoftmax. 这是一个10路的softmax分类层, 它的输出是一个数组, 数组元素为10个概率值(总和为1), 表示图像分别属于10个类别(数字)的概率值.

训练配置(损失函数, 批量大小, 学习率)

优化器 optimizer

这里选rmsprop

损失函数 loss function

这里选sparse_categorical_crossentropy

训练轮数

这里训练5轮.

数据批量大小

批量大小设为128.

模型构建代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from tensorflow import keras
from tensorflow.keras import layers

# 构建模型
model = keras.Sequential([
  layers.Dense(512, activation="relu"),
  layers.Dense(10, activation="softmax")
])

# 编译模型
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

数据预处理

数据划分: 训练集, 验证集, 测试集

mnist数据集已划分为训练集和测试集. 这里不再划分训练集和验证集.

数据向量化

从前面知训练集的shape为(60000, 28, 28), shape为(60000, 28 * 28), 即一条记录(一个向量)表示一张图片.

1
2
3
4
train_images = train_images.reshape((60000, 28 * 28))
test_images = test_images.reshape((10000, 28 * 28))
train_images.shape, test_images.shape
# ((60000, 784), (10000, 784))

数据规范化

从前面知训练集的数据类型为uint8, 将其变换为一个float32数组.

数据点(相当于图片上的一个像素点的值)的取值范围是0~255, 把值缩放到0~1.

1
2
3
4
5
train_images = train_images.astype("float32") / 255
test_images = test_images.astype("float32") / 255

train_images.dtype, test_images.dtype, train_images.min(), train_images.max()
# (dtype('float32'), dtype('float32'), 0.0, 1.0)

处理缺失值

不需要处理缺失值.

拟合模型

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

输出:

Epoch 1/5 469/469 [==============================] - 7s 13ms/step - loss: 0.2651 - accuracy: 0.9239 
Epoch 2/5 469/469 [==============================] - 5s 11ms/step - loss: 0.1070 - accuracy: 0.9686 
Epoch 3/5 469/469 [==============================] - 5s 10ms/step - loss: 0.0701 - accuracy: 0.9792 
Epoch 4/5 469/469 [==============================] - 6s 12ms/step - loss: 0.0510 - accuracy: 0.9844 
Epoch 5/5 469/469 [==============================] - 5s 10ms/step - loss: 0.0377 - accuracy: 0.9890

可以看到, 训练了5轮后, 在训练集上的精度达到了0.9890.

1
2
3
history.history # 一个字典, 键是"指标"; 值是列表, 指标在每轮训练时的值.

# {'loss': [0.26506927609443665, 0.10701579600572586, 0.07010699808597565, 0.051034048199653625, 0.03765270486474037], 'accuracy': [0.9239166378974915, 0.968583345413208, 0.979200005531311, 0.9843833446502686, 0.9889833331108093]}

利用模型进行预测

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
test_digits = test_images[0:10] # 只预测测试集中的前10张图片

predictions = model.predict(test_digits)

predictions[0] # 对第1张图片的预测结果, 数组, 各元素为概率值

predictions[0].argmax() # 第1张图片的预测结果, 最大的元素的索引为7, 表明这张图片的预测结果是7

predictions[0][7] # 索引7对应的元素的值(该图片对应数字是7的概率值)

test_labels[0] # 查看测试集第一张图片的标签, 看预测结果是否和实际结果一样

评价模型

前面选择精度(accuracy)作为模型评估的指标, 因此在对模型效果进行评价时, 使用模型在整个测试集上的平均精度.

1
2
3
4
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"测试精度: {test_acc}")
# 313/313 [==============================] - 1s 3ms/step - loss: 0.0642 - accuracy: 0.9804
# 测试精度: 0.980400025844574

可以看到, 模型在测试集上的精度为0.9804, 比在训练集上略低.

改进模型

模型在测试集上的精度能达到98%, 已经很不错了, 就暂时不改进了.

部署模型

不部署.