上期回顾:RT-Thread智能车目标识别系统连载教程——手写体识别模型 (1)
这里会介绍如何训练图像领域应用非常广的卷积神经网络 (Convolutional Neural Network)这一部分应当不会涉及到很多理论了,其实用 Keras 训练模型写起代码来非常简单,如果发现不太清楚代码为什么要这么写,可以看看上一部分对应的算子。
2.1 MNIST 手写体训练集
这个图上就是全球各地大家用不同方法做手写体识别得到的准确率汇总,可以看到我用红圈画出来的部分,用前面介绍的 Logistic Regression (Linear Classifier) 做手写体识别效果是最差的,所以我们之后要使用的是卷积神经网络 CNN (之后我就都用 CNN 简写了)。
在网站的下面给出了训练集的二进制格式定义:
当然,这是指自己从网站下载原始训练集,从当中提取图片才需要了解的,我们使用 tensorflow 不需要自己解析数据集。
2.2 开发环境搭建
首先介绍一下机器学习的开发环境,现在主流开发环境都是 Python,但是我们也不是一个裸 Python 打开记事本就直接开始写代码了,实际上数据科学家用的最多的开发环境是 Anaconda,里面集成了 Python 和 R 开发环境。
我们从官网下载 Anaconda 安装包 https://www.anaconda.com/distribution/ 根据自己的操作系统选择就可以了,因为安装过程基本就是单纯地下一步、下一步,所以这里就不介绍了。
安装好之后,我们打开 Anaconda Prompt:
Anaconda 其实是有图形界面的,叫 Anaconda Navigator,但是这里以控制台为主,因为图形界面其实用起来反而比较麻烦,因为控制台一行命令就解决了更加快速方便。
然后我们输入:
# 如果你是用的 CPU
conda create -n tensorflow-cpu tensorflow
# 如果你是用的 GPU (NVIDIA 显卡会自动安装显卡驱动,CUDA,cudnn,简直方便)
conda create -n tensorflow-gpu tensorflow-gpu
这样开发环境就搭好了,我们激活一下当前的开发环境:
# 如果你是用的 CPU
conda activate tensorflow-cpu
# 如果你是用的 GPU
conda activate tensorflow-gpu
这里 激活 开发环境是指,在 Anaconda 下我们可以有多个开发环境,比如如果你想对比一下 CPU 和 GPU 计算速度的差距,可以同时安装 2 个开发环境,然后根据需要切换到 CPU 开发环境,或者 GPU 开发环境,非常方便。如果不用 Anaconda 而是一个 Python 裸奔的话,要么使用 VirtualEnv,要么就只能反复安装卸载不同的开发环境了。
接下来就可以启动我们写代码的位置了:
# 这里的软件包 anaconda 可能已经都装好了,以防万一再确认一遍
pip install numpy scipy sklearn pandas pillow matplotlib keras onnx jupyter -i https://pypi.tuna.tsinghua.edu.cn/simple
# 启动编辑器
jupyter notebook
这样就会自动打开浏览器,看到我们的开发环境了,在这里新建一个 notebook:
接下来就可以开始训练模型了。
2.3 Keras 训练模型
2.3.1 导入库函数
#coding:utf-8
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
np.set_printoptions(suppress=True)
import matplotlib.pyplot as plt
%matplotlib inline
如果你对上面 导入库 这一个注释比较感兴趣,可以在一个把光标移到到一个输入框,按下 Esc 再按下 m,这个输入框就从 代码段 变成 注释段 了,Anaconda 也是代码、注释、输出可以同时保存所以用起来体验非常好。更多的快捷键可以在菜单栏的 Help –> Keyboard Shortcuts 找到。
2.3.2 下载 MNIST 训练集
在代码块输入一行代码:
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) #MNIST数据输入
2.3.3 看一看 MNIST 数据
我们把下载下来的数据集分成训练集和测试集,训练集用来训练模型,测试集用来检测最后模型预测的正确率:
X_train = mnist.train.images
y_train = mnist.train.labels
X_test = mnist.test.images
y_test = mnist.test.labels
# 输入图像大小是 28x28 大小
X_train = X_train.reshape([-1, 28, 28, 1])
X_test = X_test.reshape([-1, 28, 28, 1]
如果比较好奇这是一个怎样的图片,可以看看它长什么样子,比如我们看看训练集的第一张图片
plt.imshow(X_train[0].reshape((28, 28)), cmap='gray')
也可以看看第二张图片:
plt.imshow(X_train[1].reshape((28, 28)), cmap='gray')
下面就正式开始建立训练模型了。
2.3.4 构建模型
同样先导入一下 Keras 库:
# Importing the Keras libraries and packages
# Importing the Keras libraries and packages
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Dropout
from keras.layers import Flatten
接下来就可以建模了,可以看到这里的模型和上一部分介绍的 CNN 算子是一模一样的,熟悉的 conv2d, maxpooling, dropout, flatten, dense, softmax, adam, 如果忘了它们是什么意思,随时可以切换到上一个部分回忆一下。
def build_classifier():
classifier = Sequential()
# 第一层 Conv2D,激活函数 Relu
classifier.add(Conv2D(filters = 2, kernel_size = 3, strides = 1, padding = "SAME", activation = "relu", input_shape = (28, 28, 1)))
# 第二层 Maxpooling, 使用保持图像大小的 padding
classifier.add(MaxPooling2D(pool_size=(2, 2), padding='SAME'))
# 第三层 Dropout
classifier.add(Dropout(0.5))
# 第四层 Conv2D,激活函数 Relu
classifier.add(Conv2D(filters = 2, kernel_size = 3, strides = 1, padding = "SAME", activation = "relu"))
# 第五层 Maxpoling,使用保持图像大小的 padding
classifier.add(MaxPooling2D(pool_size=(2, 2), padding='SAME'))
# 第六层 Dropout
classifier.add(Dropout(0.5))
# 第七层 Flatten
classifier.add(Flatten())
# 第八层 Dense
classifier.add(Dense(kernel_initializer="uniform", units = 4))
# 第九层 softmax 输出
classifier.add(Dense(kernel_initializer="uniform", units = 10, activation="softmax"))
# 使用 adam 训练
classifier.compile(optimizer = 'adam', loss = 'categorical_crossentropy', metrics=['accuracy'])
return classifier
这样模型就建完了
代码真的就是一层只要一行,但是一定要知道自己的模型为什么要这么建,比如为什么 maxpooling 要放在 con2d 之后,为什么要加 dropout,最后的 softmax 到底是在干什么,可不可以不要?
我们可以看看自己建立的模型长什么样:
classifier = build_classifier()
classifier.summary()
2.3.5 训练模型
接下来我们就可以开始训练模型了:
from keras.callbacks import ModelCheckpoint
checkpointer = ModelCheckpoint(filepath='minions.hdf5', verbose=1, save_best_only=True, monitor='val_loss',mode='min')
history = classifier.fit(X_train, y_train, epochs = 50, batch_size = 50, validation_data=(X_test, y_test), callbacks=[checkpointer])
这个模型非常小,不过我用 CPU 训练才迭代 50 步,也差不多花了 10 分钟,所以能用 GPU 我们是坚决不用 CPU 的。
我们可以看看刚刚的训练过程:
def plot_history(history) :
SMALL_SIZE = 20
MEDIUM_SIZE = 22
BIGGER_SIZE = 24
plt.rc('font', size=SMALL_SIZE) # controls default text sizes
plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title
plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels
plt.rc('xtick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('ytick', labelsize=SMALL_SIZE) # fontsize of the tick labels
plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize
plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title
fig = plt.figure()
fig.set_size_inches(15,10)
plt.plot(history['loss'])
plt.plot(history['val_loss'])
plt.title('Model Loss')
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'test'],loc='upper left')
plt.show()
用图片显示一下训练过程:
plot_history(history.history)
可以看到,模型在训练集和测试集的 cost function 计算出来的 loss 都在减小,很神奇的是模型在测试集上的表现竟然比训练集还要好,不过模型精度不算太高才 60% 多一点的准确率,大家可以试着优化一下,在尝试改进模型的过程中,就会加深对模型的理解,如果我在这里直接就给出一个表现非常好的模型,可能对大家帮助反而不是那么大。
2.4 保存模型为 onnx
我们可以把模型保存为原生的 Keras 模型:
classifier.save("mnist.h5")
当然,为了在 stm32 上面加载,我们更想保存为通用机器学习模型 onnx 的格式:
import onnx
import keras2onnx
onnx_model = keras2onnx.convert_keras(classifier, 'mnist')
onnx.save_model(onnx_model, 'mnist.onnx')
这样在 Anaconda Prompt 默认目录下 C:/Users/你的用户名 可以看到 mnist.h5 和 mnist.onnx 两个文件,这就是训练好的模型。
这样我们模型也训练好了,也保存好了,下一步就是怎么使用训练好的模型了。
(大家可以试试把 Dropout 的概率从 0.5 改为 0.3,训练集准确度就会从 60% 提升到 80%,测试集则有 90% 以上,为什么呢?)
2.5 参考文献
- MNIST 训练集:http://yann.lecun.com/exdb/mnist/
- Anaconda GPU 环境:https://www.anaconda.com/tensorflow-in-anaconda/