22 KiB
num_mnist 手写数字识别
摘要
本项目采用了机器学习中的卷积神经网络,实现了基于TensorFlow
的卷积神经网络数字识别,其主要功能包括网络模型训练、通过网络模型预测结果、保存模型和读取模型,特点是模型体积小、正确率高(98%)和方便使用。
关键字:机器学习、人工智能、卷积神经网络、TensorFlow
、Keras
、数字识别、监督学习。
目录
1.绪论
1.1需求分析
随着人工智能时代的不断发展,应用机器学习技术对图像识别已成为模式识别中非常重要的领域。图像识别属于模式识别的一个重要分支,已经成功应用在军事、医疗和工业等计算机视觉领域中。同时人们开始使用神经网络等学习框架,让计算机模仿人类大脑的学习机制,从训练数据中学习,进而解决很复杂的问题。其中手写数字识别由于其广泛的应用前景和良好的商业价值,如银行账单验证、车牌号码识别、个人身份检查等,吸引了越来越多人的关注。手写数字识别作为模式识别领域的一个重要问题,也有着重要的理论价值。阿拉伯数字是唯一的被世界各国通用的符号,对手写数字识别的研究基本上与文化背景无关,这样就为各国,各地区的研究工作者提供了一个施展才智的大舞台。在这一领域大家可以探讨,比较各种研究方法。2.由于数字识别的类别数较小,有助于做深入分析及验证一些新的理论。 这方面最明显的例子就是人工神经网络(ANN)相当一部分的ANN模型和算法都以手写数字识别作为具体的实验平台,验证理论的有效性,评价各种方法的优缺点。尽管人们对手写数字的识别已从事了很长时间的研究,并已取得了很多成果,但到目前为止机器的识别本领还无法与人的认知能力相比,这仍是一个有难度的开放问题(Open problem)。
1.2开发环境
本项目是建立在windows环境下,利用Pycharm
编译器开发而成。为部署开发所需环境,首先安装了Anaconda
,创建了TensorFlow所需环境。
Anaconda
介绍:Anaconda指的是一个开源的Python
发行版本,其包含了conda
、Python
等180多个科学包及其依赖项。
PyCharm
介绍:PyCharm
是由JetBrains
打造的一款Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。
2.设计与实现
2.1概要设计
本项目基于TensorFlow
库包进行机器学习神经网络的构建、训练与评估,以实现对手写数字的识别。训练过程中使用了TensorFlow
内置num_mnist训练集,保证了数据集的真实可靠。网络模型方面选择使用卷积神经网络与全连接神经网络结合,并搭配反向传播算法进行训练。选择ReLU
线性整流函数作为激活函数,保证训练效率的同时简化了计算过程。
同时考虑到人机交互方面,于是加入了网络模型本地保存与读取的功能,使得网络模型的使用更加方便。另外考虑到在训练过程中可能会发生意外状况导致训练中止,为此加入了训练检查点并支持网络模型读取检查点来获取权值数据从而继续训练。
2.2详细设计与实现
开发过程
1) 将项目所需的功能包导入,导包的时候确保环境已经配置完好。
2) 从TensorFlow
库中加载获取所需的数据集,将数据集分为训练集和测试集。
3) 将图片进行归一化处理,统一格式为28x28像素。
4) 利用Keras中
的Sequential
模型定义模型,Sequential
序贯模型是函数式模型的简略版,为最简单的线性、从头到尾的结构顺序,不分叉,是多个网络层的线性堆叠。
5) 设计测试全连接神经网络。创建基本的模型实例。创建保存模型权重的回调,用新的回调训练模型进行训练。最后通过评估函数得出训练好模型的准确度。
6) 设计测试卷积神经网络。激活函数选择ReLu
和softmax
。创建基本的模型实例进行训练。通过评估函数得出训练好模型的准确度。
7) 将全连接神经网络和卷积神经网络训练的结果进行对比,卷积神经网络的准确率显然高于全连接神经网络。
在我们的项目开发与研究过程中也学习了不少机器学习方面的理论知识。
激活函数
激活函数是神经网络中的非线性成分,是必不可少的。如果没有激活函数,由于线性成分的叠加还是线性成分,整个神经网络依然相当于线性回归,并不能拟合非线性假设。对于Logistic回归而言,Sigmoid函数用于预测输入属于某一类别的概率,只能用它。但是在神经网络中,使用Sigmoid函数仅仅是作为非线性成分,而且往往它的表现并不好,我们可以考虑使用其他的非线性函数。在反向传播的公式中,求取参数的梯度时要反复乘上 的值。实践中发现,经常乘着乘着值很快趋于0了,导致参数更新幅度太小,收敛过于缓慢,这被称为梯度消失;有的时候会乘着乘着出现巨大的数,导致参数剧烈震荡而不收敛,这被称为梯度爆炸。梯度消失则是普遍问题,跟激活函数、网络结构等都有关。
sigmoid函数
函数及其导函数
它的优点是输出对输入较敏感,参数更新较平稳。
但它有明显的缺点无论输入值是多少,导数值永远小于0.5,而且输入值很大时导数趋于0,这很容易导致梯度消失。
而且它的输出总是正数,由于 中的 恒为正,则偏导数的符号是固定的,会导致这一层的参数全部向正方向或负方向更新,使得收敛缓慢。计算时涉及到指数函数,比较费时。
tanh函数
函数及其导函数
双曲正切函数的解析式是 ,实际上是Sigmoid的变形,因为
它避免了Sigmoid输出恒正的缺点,并且扩大了导数值。但它也容易导致梯度消失,且计算较慢。
ReLU函数
函数及其导函数
ReLU是Rectified Linear Unit的缩写,指线性整流函数。它的解析式非常简单粗暴:
该函数在正区间导数恒为1,不会导致梯度消失。它的计算非常便捷。在实际应用上,他的表现相当好,神经网络收敛比Sigmoid和tanh快得多,是目前应用最广泛的激活函数。但是它也存在问题:在负区间上导数恒为0,不会继续传递梯度。也就是说,那些输出为正的节点会被更新且向上一层传播,而输出为负的节点不会被更新且不向上一层传播。实践中发现,有些节点可能永远不被激活(对于所有的输入都输出0),因而永远不被更新,这被称为神经元死亡问题。少量节点死亡问题不大,但是大量节点死亡,就会出现拟合效果很差且不能改进的局面(loss一直很大)。
maxout函数
maxout激活函数和之前的不同,它不是单纯的一对一的函数,它是在神经网络里面专门加了一层激活函数层。输出本来是线性组合 再套上一个非线性函数 得到的 ,maxout是取多种线性组合 再输出其中的最大值 .
maxout在图像上是一条可以变化的折线(ReLU是它的特例),可以说它是会学习的激活函数,因此能力非常强大,也没有dead ReLU的问题。但是缺点也是很明显的:引入了大量新的参数,更难计算。
网络模型
神经网络模型本质是一种仿生的设计,我们人类大脑的神经元通过突触与其他神经元相连接,接收其他神经元送来的信号,经过汇总处理之后产生输出。于是机器学习科学家们将大脑神经元进行抽象,并在计算机上进行实现,便有了网络模型的概念。神经网络一般有多个层,其中第一层为输入层,对应输入向量。第一层网络中的神经元的数量等于输入向量的维数,这个层不对数据进行处理,只是将输入向量送入下一层中进行计算。在第一层与最后一层之间的所有层为隐藏层,负责主要的处理工作。最后是输出层,该层神经元的数量根据实际需求而定,每个神经元对应着一个输出。
神经网络的训练方式为监督学习,通俗来讲就像我们刷题的过程,不断刷题,做完一题就看答案,从而学会如何考满分。通过输入值X
(题目)与真实值Y
(答案),来不断进行训练。输入值X
传入网络并经过一系列运算之后可以得到预测值Y^{'}
,通过预测值Y^{'}
与真实值Y
我们可以得到损失值loss
,并以损失值来评估预测结果的好坏,并通过反向传播算法将预测好坏程度反馈给网络,网络以此来修改自己使自己的预测更准确。
反向传播算法
反向传播算法适合于多层神经元网络的一种学习算法,它建立在梯度下降法的基础上。BP网络的输入输出关系实质上是一种映射关系:一个输入m输出的BP神经网络所完成的功能是从维欧氏空间向m维欧氏空间中一有限域的连续映射,这一映射具有高度非线性。它的信息处理能力来源于简单非线性函数的多次复合,因此具有很强的函数复现能力。这是BP算法得以应用的基础。简单来说,反向传播就是训练参数模型,在所有参数上用梯度下降的方式,使模型在训练数据上的损失最小。
梯度下降算法
梯度下降(gradient descent)在机器学习中应用十分的广泛,不论是在线性回归还是Logistic回归中,它的主要目的是通过迭代找到目标函数的最小值,或者收敛到最小值。梯度下降法的基本思想可以类比为一个下山的过程。假设这样一个场景:一个人被困在山上,需要从山上下来(找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低;因此,下山的路径就无法确定,必须利用自己周围的信息一步一步地找到下山的路。这个时候,便可利用梯度下降算法来帮助自己下山。怎么做呢,首先以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着下降方向走一步,然后又继续以当前位置为基准,再找最陡峭的地方,再走直到最后到达最低处;同理上山也是如此,只是这时候就变成梯度上升算法了。
网络模型选取
在部署好TensorFlow
之后的第一件事便是为项目选择合适的神经网络模型。由于缺乏项目经验,于是我们决定将常用的网络先试一下看看效果,并实验得出卷积神经网络比较适合该项目。
全连接神经网络(MLP):顾名思义就是每一层网络的每一个神经元都与相邻层网络的每个神经元连接(如下图)。
每个神经元可以视为对输入值x
乘以一个系数w
并加上一个偏置b
,从而得到输出值f
。
f=wx+b
其中我们称w
为该神经元的权值,称b
为该神经元的偏置。
为了保证输出值在一定范围之内,往往需要使用激活函数来实现归一化(输出值映射到[0,1]
的区间之内)。
神经元之间相互连接,最初的输入值经过多个神经元之手多次处理,从而得到最后输出层中的输出值。因为每一级输出的值都将作为下一级的输入,只有将输入归一化了,才会避免某个输入无穷大,导致其他输入无效,最终网络训练效果非常不好。
输入值在经过许多个神经元传递之后便得到了输出值。为了描述输出值的预测效果,我们定义损失值Loss
。这里使用最小二乘法来得到损失值。
Loss=\Sigma_{i}^{n}(y_i-(wx_i+b))^2
损失值可以用来评估预测结果的好坏,为了得到最小的损失值Loss
,我们需要通过梯度下降算法来求得最佳的权值w
与偏置b
。
从输出层到输入层,对每层的神经元使用梯度下降算法来从后向前更新权值与偏置,这便是反向传播算法。
卷积神经网络(CNN):
一个经典的CNN网络结构一般包含输入层(Input layer)、卷积层(convolutional layer)、池化层(pooling layer)和输出层(output layer)。卷积神经网络把图片转换成二维矩阵格式的数据,输入数据,网络的各层都以二维矩阵的方式处理数据,这样的数据处理方式适用于二维矩阵格式的数字图像,相较于传统的人工神经网络,它能更快更好地把特征值从图像数据中提取出来。
图像拥有很多冗余的信息,而且往往作为输入信息,它的矩阵又非常大,利用全连接神经网络去训练的话,计算量非常大,前人为此提出了CNN,它的亮点之一就是拥有卷积层。卷积层通俗易懂来说就是压缩提纯。
卷积核在滑动过程中做的卷积运算就是卷积核w 与其所覆盖的区域的数进行点积,最后将结果映射到卷积层(如下式)。这样看来,我们将9个点的信息量,压缩成了1个点,也就是有损压缩,这个过程我们也可以认为是特征提取。公式中(w , b)
和之前全连接神经网络并没有区别,就是权值w
和偏置b
,它们在初始化之后,随着整个训练过程一轮又一轮的迭代逐渐趋向于最优。在卷积核 之后一般会加一个ReLU
的激励函数,从而让训练结果更优。
f(x)=wx+b \\
w =
\begin{bmatrix}
-1 && -2 && -1 \\
0 && 0 && 0 \\
1 && 2 && 1 \\
\end{bmatrix}
同时通过局部连接与权值共享的方式来减少训练参数,加快训练速度。
一般来说,卷积层后面都会加一个池化层,可以将它理解为对卷积层进一步特征抽样,类似降低视频清晰度的操作,从而使得运算量进一步减小。
最后在输出层前加入Softmax
层,该层我们可以理解为使每个节点输出一个概率,所有节点的概率加和等于1,这也是CNN
选择softmax
层进行分类的原因所在,可以将一张待分类的图片放进模型,Softmax
输出的概率中,最大概率所对应的标签便是这张待分类图的标签。
实现过程
代码结构
调用TensorFlow
的Keras
包来构建网络模型。
# 构建网络 开始
'''
卷积 池化 卷积 池化 全连接 全连接
'''
model = keras.Sequential()
model.add(keras.layers.Conv2D(8, (3, 3), activation='relu', input_shape=(28, 28, 1))) # 卷积层,卷积核3x3
model.add(keras.layers.MaxPooling2D(2, 2)) #2D池化层
model.add(keras.layers.Conv2D(8, (3, 3), activation='relu')) # 卷积层,卷积核3x3
model.add(keras.layers.MaxPooling2D(2, 2)) #2D池化层
model.add(keras.layers.Flatten()) # 扁平化处理,实现从卷积到全连接的过渡
model.add(keras.layers.Dense(128, activation=tf.nn.relu)) # 全连接层
model.add(keras.layers.Dense(10, activation=tf.nn.softmax)) # 全连接层
# 使用 adam 优化器, Loss函数使用最小二乘法
model.compile(optimizer='adam', loss=tf.losses.sparse_categorical_crossentropy, metrics=['accuracy'])
# 构建网络 结束
训练并评估
# 训练
history = model.fit(
train_images_scaled.reshape(-1, 28, 28, 1), # 统一图片格式 28x28
train_labels,
epochs=8,
validation_data=(test_images.reshape(-1, 28, 28, 1), test_labels),
callbacks=[cp_callback]
)
# 使用测试集评估
results = model.evaluate(test_images.reshape(-1, 28, 28, 1), test_labels)
将训练后的模型保存
# 保存模型
model.save(model_path)
加载模型
# 读取训练好的模型
model = load_model(model_path)
可视化预测效果
# 可视化预测效果
show_num = 200
testShow = test_labels[:show_num]
pred = model.predict(test_images.reshape(-1, 28, 28, 1))
predict = []
for item in pred:
predict.append(np.argmax(item))
# 显示折线图
plt.figure()
plt.title('Conv Predict')
plt.ylabel('true number')
plt.xlabel('img num')
plt.plot(range(testShow.size), predict[:show_num], marker='^', color='coral', label='predict')
plt.plot(range(testShow.size), testShow, marker='o', color='deepskyblue', label='result')
plt.legend()
# 挑选出预测错误的图片,并显示预测值
wrongImg = []
wrongNum = []
for i in range(testShow.size):
if (predict[i] != testShow[i]):
wrongImg.append(test_images[i])
wrongNum.append(predict[i])
for i in range(len(wrongImg)):
plt.figure()
plt.title(str(wrongNum[i]))
plt.imshow(wrongImg[i])
plt.show()
3.系统测试
训练过程
预测过程
可视化预测效果。左侧x
轴为图片编号,纵轴为图片中数字的真实值;右侧是输出的预测错误的图片。
保存在本地的网络模型
4.总结
本项目实现了基于TensorFlow
的卷积神经网络数字识别,集训练、预测于一体,具有模型保存、读取功能并支持断点训练。在开发中遇到的最大的困难其实更多是配置TensorFlow
的环境以及API
的调用。使用Anaconda
来管理Python
环境使得环境配置更简单,搭配PyCharm使用让TensorFlow
的使用快捷方便。在构建网络模型与训练方面使用了TensorFlow
下的Keras
包,该包囊括了网络模型自定义、模型训练、预测效果评估、模型保存与读取等众多功能,大大降低了开发的复杂度,保证了开发效率。同时为了实现预测效果的可视化,我使用了Python
的matplotlib包,利用内置函数非常便捷的绘制出了形象生动的图片,使得预测效果一目了然。
通过本次项目,我不仅学习到了机器学习的相关知识,而且熟悉了Python
开发的各种工具以及API
的使用方法。同时也更加熟悉了Python
的语法,本次项目经验使得我能在今后的开发中更加得心应手。
5.参考文献
(1) https://zhuanlan.zhihu.com/p/104776627 一文掌握CNN卷积神经网络
(2) https://blog.csdn.net/baidu_36602427/article/details/96429838 全连接神经网络
(3) https://zhuanlan.zhihu.com/p/104576756 深度学习开端|全连接神经网络
(5) https://blog.csdn.net/qq_41800366/article/details/86583789 梯度下降算法原理讲解——机器学习
(6) https://zhuanlan.zhihu.com/p/102502396 机器学习(6)——激活函数