基于神经网络的数字识别
摘要:
手写识别是计算机从纸张文档,照片,触摸屏和其他设备等来源接收和解释可理解的手写输入的能力。本文主要基于一些计算机视觉基础和神经网络的知识以及分类方法(如SVM和K-近邻),用于从数万个中手写图像中识别出正确的数字使,并用经典的手写图像数据集 minist 作为测试 。
前言
手写数字识别的重要性
字符识别处理的信息可分为两大类:一类是文字信息,处理的主要是用各国家、各民族的文字(如:汉字,英文等)书写或印刷的文本信息,目前在印刷体和联机手写方面技术已趋向成熟,并推出了很多应用系统; 另一类是数据信息,主要是由阿拉伯数字及少量特殊符号组成的各种编号 和统计数据,如:邮政编码、统计报表、财务报表、银行票据等等,处理这类信息的核心技术就是手写数字识别。如果能通过手写数字识别技术实现信息的自动录入,无疑会促进这一事业的进展。因此,手写数字的识别研究有着重大的现实意义,一旦研究成功并投入应用,将产生巨大的社会和经济效益。
应用场景
手写数字识别有着极为广泛的应用前景,这也正是它受到世界各国研究工作者重视的一个主要原因。主要有以下几个方面的应用:
- 应用于大规模数据统计
- 应用于财务、税务、金融领域
- 应用于邮件分拣
手写数字识别的方法
手写数字识别在学科上属于模式识别和人工智能的范畴。在过去的四十年中,人们想出了很多办法获取手写字符的关键特征。这些手段分两大类:全局分析和结构分析。对前者,我们可以使用模板匹配、象素密度、矩、特征点、数学变换等技术。这类的特征常常和统计分类方法一起使用。对后者,多半需要从字符的轮廓或骨架上提取字符形状的基本特征,包括:圈、端点、节点、弧、突起、凹陷、笔画等等。与这些结构特征配合使用的往往是句法的分类方法。
本文解决思路
个人认为,在手写数字识别的研究中,神经网络和多种方法的综合是值得重视的方法,因此本文主要采用卷积神经网络,和分类预测来实现数字识别。
相关工作
本文的思路和实现主要参考了国内外的一些论文中提到的算法和思路。
-
1、Classification of MNIST Handwritten Digit Database using Neural Network.
本文介绍了一种使用反向传播神经网络实现MNIST手写数字数据库分类的实现,并通过训练期间的损失图和分类准确性来确定神经网络的性能。实验结果表明,反向传播神经网络在一定程度上可以用来解决现实世界中的分类问题。 -
2、Efficient Handwritten Digit Recognition based on Histogram of Oriented Gradients and SVM
本文提出了一种基于外观特征的方法,该方法使用方向梯度直方图(HOG)处理数据。 HOG是用于手写数字的非常有效的特征描述符,其在照明变化上是稳定的,因为它是基于梯度的描述符。此外,线性SVM已被用作分类器,其具有比多项式,RBF和S形核更好的响应。我们已经在MNIST数据集上分析了我们的模型,并且已经实现了97.25%的准确率
最先进的。 -
3、Handwritten Recognition Using SVM, KNN and Neural Network
手写识别(HWR)是计算机接收和解释可理解的手写输入的能力,来自诸如纸质文档,照片,触摸屏和其他设备之类的来源。本文使用三种分类方法,SVM,KNN和神经网络来,识别手写的数字。 -
4、Handwritten Digit Recognition Using Convolutional Neural Networks.
因为手写数字识别在各种机器学习和计算机视觉应用中的应用,广泛受到研究者的关注。然而,在阿拉伯语模式数字上完成的工作有缺陷,因为阿拉伯数字比英语模式更具挑战性。本文使用深度卷积神经网络进行分类,并获得了优异的结果。 -
5、Fast and Accurate Digit Classification.
本文主要证明了,通过改进的特征,低复杂度分类器,特别是加性核SVM,可以实现最先进的性能。方文中法在MNIST数据集上实现了0.79%的误差,在USPS数据集上实现了3.4%的误差,同时运行速度与基于多层神经网络的数据集上的最快算法相当,并且显着更快且更容易训练。 -
6、一种基于多分类器的无分割手写数字字符串识别算法.
本文针对分割识别算法复杂度较高、准确率较低的问题,提出一种多分类器下无分割手写数字字符串识别算法。该算法的核心是采用四个分类器实现粘连字符串的无分割识别;将残差结构应用于 LeNet-5 网络,以增加网络深度,提高识别准确率,加快收敛速度;使用动态选择策略,以避免长度分类器误分类对识别结果的影响。 -
7、基于形变卷积神经网络的手写体数字识别研究
本文引入形变卷积模块来增强网络对数字几何变换的建模能力,提出了一种基于改进的形变卷积神经网络手写体数字识别框架,在提高识别精度的同时,还有效的减少了训练的参数量,提高识别速度.本文在手写体数据集及变换后的数据集中进行验证.实验结果的分析以及与相应算法的比较,证明了本算法是有效的. -
8、基于改进 KNN 算法的手写数字识别研究
本文针对传统KNN算法手写数字识别速度和精度都不高的问题,提出了一种改进的KNN手写数字识别方案。改进的KNN算法利用PCA方法对手写数字进行降维,降低传统KNN算法在距离计算过程中的时间和空间复杂度;并采用距离权重统计改善手写数字识别错误问题。在手写数据集MNIST上验证改进的KNN算法,结果显示识别速度有显著提升,识别准确度有一定提高。 -
9、基于深度神经网络的手写数字识别模拟研究
当前图像识别大多采用基于特征提取的传统机器学习方法与卷积神经网络的方法,但传统图像识别技术需要手动提取图片特征,而卷积神经网络对硬件要求高,训练时间长等。针对以上问题,本文提出基于深度神经网络模型的手写体图像识别方法,让机器自动学习特征,并在此基础上,通过改进成本函数,加入 dropout 防止过拟合,来提高手写数字识别的识别率。仿真实验对比结果表明,基于深度神经网络模型的方法比当前传统算法的识别率提高了 3. 41% ,有效解决了人工识别费力耗时问题,对手写数字的研究具有重要意义。 -
10、基于TensorFlow实现手写数字识别
本文主要研究了TensorFlow深度学习环境的体系框架、数据模型、运行模型与模型评价机制,以手写数字图片识别的深度学习为例,对手写数字图片的训练、测试与验证进行应用实践,得出手写数字识别模型,获得百分之九十以上的识别成功率。
研究问题的定义
- 算法输入
算法的输入是已知的数据集,通过 python 的 模块加载方法引入训练集 和测试集。
train = pd.read_csv("./csv/train.csv")
- 算法输出
算法的输出是对数据集预测的准确率 和损失率,如:
算法设计
程序的总体框架流程如图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/4ba7d5f9de0a56dd963bd1b3527172a4.png)
各个步骤详细介绍:
引入相关库
import numpy as np # 用于数据处理
import pandas as pd # 数据处理
import matplotlib.pyplot as plt # 展示 绘制
from collections import Counter
from sklearn.metrics import confusion_matrix
import itertools # 迭代工具
import seaborn as sns
from subprocess import check_output
import matplotlib
import keras # 用于神经网络
from keras.models import Sequentia
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D
from keras.layers.normalization import BatchNormalization
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
数据处理
# 先加载数据
train = pd.read_csv("./csv/train.csv")
print(train.shape)
print(train.head())
# 加载完毕
# 获取训练集,分离标签和数据, 统计观察, 数据展示
trainy = Counter(train['label'])
print(trainy)
sns.countplot(train['label'])
# 获取测试集
test = pd.read_csv("./csv/test.csv")
print(test.shape)
print(test.head())
# 分离数据和标签
trainx = (train.iloc[:, 1:].values).astype('float32') # 数据类型转换, 从numpy转出来
print(trainx)
trainy = (train.iloc[:, 0].values).astype('int32')
testx = (test.iloc[:, 0:].values).astype('float32')
# 数据图像展示
plt.figure(figsize=(12, 10)) # 创建一个图像,figsize为图像大小
x, y = 10, 4 # 图片展示数量
for t in range(40):
plt.subplot(y, x, t+1) # 类似matlab中的subplot
plt.imshow(trainx[t].reshape((28, 28)), interpolation='nearest')
plt.show()
# 前置准备(包括数据的归一化、把一维数据转换为二维图像数据)
# 数据归一化
trainx = trainx/255.0
testx = testx/255.0
# 数据改变形状
trainX = trainx.reshape(-1, 28, 28, 1) # 第一个参数就是说明有多少个图片,-1让他自己取
testX = testx.reshape(-1, 28, 28, 1)
batch_size = 64
num_classes = 10
epochs = 3
input_shape = (28, 28, 1)
# 切分训练集和验证集
trainy = keras.utils.to_categorical(trainy, num_classes)
trainX, valX, trainY, valY = train_test_split(trainX, trainy, test_size = 0.15, random_state = 42)
建立神经网络模型
神经网络结构
![](https://i-blog.csdnimg.cn/blog_migrate/211b677b3633af3762b5630b8cc16440.png)
神经网络各层的原理与作用
输入层
由于使用梯度下降进行学习,卷积神经网络的输入特征需要进行归一化处
理。在本例中输入数据为二维图片,所以使用 784 个输入神经元代表 28*28
像素点二维图片的一维化向量,并将分布于[0,255]的灰度归一化至[0,1]区间
内。
隐含层
卷积神经网络的隐含层包含卷积层、池化层和全连接层 3 类常见构筑,其
在隐含层中的顺序通常为:输入-卷积层-池化层-卷积层-池化层-全连接层-输 出。本例中加入了 dropout 层、Flatten 层和批规范化层。
- (a)卷积层(Conv2D)
- i. 卷积核 :
1. 一定大小的较小矩阵,将其覆盖在上一层的较大矩阵上进行内积
计算(相同位置乘算并求和)得到特征图对应位置的元素,并依
序每次滑动卷积步长个单位,最后得到整个特征图。卷积层中通
常有多个不同的卷积核。
2. 卷积核有效的原因在于,通过卷积计算可以提取出图片的特征,
比如通过观察到特征图某一列的绝对值很大,能得出原图片对应
位置有一条垂直方向的特征,等等。
- ii. 卷积层参数
包括卷积核大小、步长和填充,三者共同决定了卷积层输出特征
图的尺寸,是卷积神经网络的超参数。
1. 卷积核大小:
可以指定为小于输入图像尺寸的任意值,卷积核越大,可提
取的输入特征越复杂。
2. 卷积步长:
定义了卷积核相邻两次扫过特征图时位置的距离,卷积步长
为 1 时,卷积核会逐个扫过特征图的元素,步长为 n 时会在下
一次扫描跳过 n-1 个像素。
3. 填充:
在特征图通过卷积核或池化层之前,人为增大其尺寸以抵消
计算中尺寸收缩影响的方法,因为随着卷积层的堆叠,特征图的
尺寸会逐步减小。常见的填充方法为按 0 填充和重复边界值填
充。
4. 计算公式:
b 为偏差量,Zl和 Zl+1表示第 l+1 层的卷积输入和输出,Ll+1为 Zl+1 的尺寸,这里假设特征图长宽相同。Z(I, j)对应特征图的像
素,K 为特征图的通道数,f、s0 和 p 是卷积层参数,对应卷积
核大小、卷积步长和填充层数。
- iii. 激励函数
激励函数可以引入非线性因素,解决线性模型所不能解决的问题。
该操作通常在卷积核之后,以协助表达复杂特征,其表示形式如下:
- (b) 池化层(MaxPooling)
- i. 主要目的
通过降采样的方式,在不影响图像质量的情况下压缩图片,减少
参数。
- ii. 池化方法
确定一个窗口大小和步长,在卷积层得到的特征图上进行滑动,
每次取窗口中的最大值(MaxPooling)或平均值(AveragePooling),
得到一个新的降低了尺寸的矩阵
- iii. 一般而言,我们采用 MaxPooling 进行池化,因为每一个卷积核可以
看做一个特征提取器,不同的卷积核负责提取不同的特征,那么我们
对其进行 Max Pooling 操作后,提取出的是真正能够识别特征的数
值,其余被舍弃的数值,对于提取特定的特征并没有特别大的帮助。
当然这并不是绝对的,有时候周边信息也会对识别产生一定效果,这
时候 AveragePooling 就更好。
- (c) dropout 层
在机器学习模型中,如果模型参数太多而训练样本又太少,训练出的
模型很容易产生过拟合的现象,具体表现在:在训练数据上损失函数较小,
准确率较高;而测试数据上损失函数较大,准确率较低。dropout 层可以
很有效地缓解这个问题,在一定程度上达到正则化的效果。
- i. 工作方式
在向前传播的时候,让某个神经元的激活值以概率 p 停止工作,
使得模型泛化性更强。
- ii. 具体操作
1. 临时随机删掉网络中一半的神经元输入输出神经元保持不变
2. 将输入 x 经由修改后的网络向前传播,并把得到的损失结果反
向传播,对没有被删除的神经元按照随机梯度下降法更新对应参
数(w, b)
3. 恢复删掉的神经元
4. 重复 1、2、3
- iii. 作用原理
dropout 掉不同的隐藏神经元类似于训练不同的网络,整个过程相当于对很多个不同神经网络取平均值,不同网络具有的不同过拟合可以在一定程度上相互抵消达到整体减少过拟合的目的。同时,dropout 能减少神经元之间的复杂共适应关系,阻止某些特征仅仅在其他特征下才能有效的情况,也能增加网络对丢失特定神经元连接的鲁棒性
- (d)Flatten 层
输入扁平化,即把多维的输入一维化,常用在从卷积层到全连接层的
过渡。
- (e)批规范化层(BatchNormalization)
BN 层是对于每个神经元做归一化处理。
- (f)稠密层(全连接层)Dense(fully connected) Layer
全连接层的下游,对于图像分类问题,输出层使用逻辑函数或归一化指数函数
输出分类标签。
输出层
全连接层的下游,对于图像分类问题,输出层使用逻辑函数或归一化指数函数
输出分类标签。
# 建立模型 线性模型
model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3), activation='relu', kernel_initializer='he_normal', input_shape=input_shape)) # ?
model.add(Conv2D(32, kernel_size=(3, 3), activation='relu', kernel_initializer='he_normal'))
model.add(MaxPool2D((2, 2)))
model.add(Dropout(0.20))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_normal'))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_normal'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Conv2D(128, (3, 3), activation='relu', padding='same', kernel_initializer='he_normal'))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(BatchNormalization())
model.add(Dropout(0.25))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.RMSprop(), metrics=['accuracy'])
第一层分析说明:
- 卷积核数量为 32,大小为 3*3
- 激活函数(activation):
- 非线性函数作为激活函数的引入是必需的,它能使得深层神经网络有意义, 否则(相当于激活函数为 f(x)=x)每一层输出都是上一层的线性组合,隐含层就发挥不了效果,最终的学习结果也无法期待。
- 选择 ReLU(x)=(x+|x|)/2 作为激活函数的原因:
i.其它非线性激活函数如 Sigmoid(指数运算)与之相比,首先本身计算量就很大,反向传播求梯度误差的时候求导涉及除法,更是加大计算量, 而 ReLU 函数则可以节省大量计算时间。
ii.ReLU 会使一部分神经元的输出为 0,这样就造成了网络的稀疏性,并且减少了参数的相互依存关系,缓解了过拟合问题的发生。
- 使用正态分布随机初始化权值,可以得到均值为 0,方差为 1 的权值矩阵
- 序贯模型的第一层需要明确输入的尺寸(后续层可以自动判断),此处为(28, 28, 1)
神经网络层的顺序说明:
输入→卷积(32 核)→卷积(32 核)→池化(池化窗口2*2)
→dropout(20%)→卷积(64核)→卷积(64 核)→池化
→dropout(25%)→卷积(128 核) →dropout(25%)→Flatten
→全连接(输出空间维度为 128,即输出上一个卷积层的 128 核代表的特征)
→批规范化→dropout(25%)
→全连接(输出空间维度为 10,即输出对图片的分类标签,激活函
数为 Softmax 归一化指数函数)
最后一行用于确认模型学习的精确度与损失率
生成结果
- 使用训练集开始对模型进行分批训练
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc', patience=3, verbose=1, factor=0.5, min_lr=0.0001)
datagen = ImageDataGenerator(featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
rotation_range=15,
zoom_range=0.1,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=False,
vertical_flip=False)
# model.load_weights("digital.h5")
model.summary()
datagen.fit(trainX)
h = model.fit_generator(datagen.flow(trainX, trainY, batch_size=batch_size), epochs = epochs, validation_data = (valX, valY),
verbose=1, steps_per_epoch=trainX.shape[0], callbacks=[learning_rate_reduction])
- 返回模型的训练结果
final_loss, final_acc = model.evaluate(valX, valY, verbose=0)
print('Final loss: {0:.6f}, final accuracy: {1:.6f}'.format(final_loss, final_acc))
preY = model.predict(valX)
preY_c = np.argmax(preY, axis = 1)
corY = np.argmax(valY, axis = 1)
errors = (preY_c - corY != 0)
preY_c_e = preY_c[errors]
preY_e = preY[errors]
corY_e = corY[errors]
valX_e = valX[errors]
- 展示出错类,分析出错误最多的几个类
# 通过循环展示错误类
def display_errors(errors_index, img_errors, pred_errors, obs_errors):
n = 0
nrows = 2
ncols = 2
fig, ax = plt.subplots(nrows, ncols, sharex=True, sharey=True)
for row in range(nrows):
for col in range(ncols):
error = errors_index[n]
ax[row, col].imshow((img_errors[error]).reshape((28, 28)))
ax[row, col].set_title("Predicted label :{}\nTrue label :{}".format(pred_errors[error], obs_errors[error]))
n += 1
# 计算出出错数最多的4个类进行展示
Y_pred_errors_prob = np.max(preY_e, axis = 1)
true_prob_errors = np.diagonal(np.take(preY_e, corY_e, axis=1))
delta_pred_true_errors = Y_pred_errors_prob - true_prob_errors
sorted_dela_errors = np.argsort(delta_pred_true_errors)
most_important_errors = sorted_dela_errors[-4:]
display_errors(most_important_errors, valX_e, preY_c_e, corY_e)
- 开始使用测试集进行模型验证
# 测试数据的预测
predict_class = model.predict_classes(testX)
model.save("digital.h5")
testy = test.iloc[:, 0]
correct = np.nonzero(predict_class==testy)[0]
incorrect = np.nonzero(predict_class!=testy)[0]
target_names = ["Class {}".format(i) for i in range(num_classes)]
print(classification_report(testy, predict_class, target_names=target_names))
submissions=pd.DataFrame({"ImageId":list(range(1, len(predict_class)+1)), "Label": predict_class})
submissions.to_csv("out.csv", index=False, header=True)
json_string = model.to_json()
实验
先介绍数据集、评价标准;在展示实验结果并分析。
数据集描述
数据文件train.csv和test.csv包含从0到9的手绘数字的灰度图像。
每个图像的高度为28个像素,宽度为28个像素,总共为784个像素。每个像素具有与其相关联的单个像素值,指示该像素的亮度或暗度,较高的数字意味着较暗。该像素值是0到255之间的整数,包括0和255。
训练数据集(train.csv)有785列。第一列称为“标签”,是用户绘制的数字。其余列包含关联图像的像素值。
训练集中的每个像素列都具有像pixelx这样的名称,其中x是0到783之间的整数,包括0和783。为了在图像上定位该像素,假设我们已经将x分解为x = i * 28 + j,其中i和j是0到27之间的整数,包括0和27。然后,pixelx位于28 x 28矩阵的第i行和第j列上(索引为零)。
例如,pixel31表示从左边开始的第四列中的像素,以及从顶部开始的第二行,如下面的ascii图中所示。
在视觉上,如果我们省略“像素”前缀,像素组成图像如下
000 001 002 003 ... 026 027
028 029 030 031 ... 054 055
056 057 058 059 ... 082 083
| | | | ... | |
728 729 730 731 ... 754 755
756 757 758 759 ... 782 783
测试数据集(test.csv)与训练集相同,只是它不包含“标签”列。
提交文件格式:对于测试集中的28000个图像中的每一个,输出包含ImageId和您预测的数字的单行。例如,如果预测第一个图像是3,第二个图像是7,第三个图像是8,那么提交文件将如下所示:
ImageId,Label
1,3
2,7
3,8
(27997 more lines)
评价标准
评估指标是分类准确度,或正确分类的测试图像的比例。例如,分类准确度为0.97表示您已正确分类了除3%以外的所有图像。
实验结果与分析
- 加载数据:
![](https://i-blog.csdnimg.cn/blog_migrate/b81ea503530e51e5478d346e1e4dfe69.png)
如图所示,训练集尺寸为 42000785,即 1+2828,首列为分类标签,代表图片
对应的数字,之后 784 列代表图片一维化后每个像素的灰度[0, 255],共计 42000 张
图片,计数器 counter 显示了各个数字的数量。
验证集尺寸为 28000*784,不提供分类标签列。
将训练集第 1 列到最后一列单独分割出来强制转换为浮点数并保存,此为数据部
分;第 0 列也分割出来,此为数据对应的数字标签;测试集则不进行分割。
- 绘制数字数量的统计表,并展示部分数字的图片:
训练过程
- 神经网络每一层的输入输出以及参数情况:
- 可以看到,随迭代次数的增加,模型的准确率在逐渐增加,损失率在逐渐降低
测试结果
- 最终各批次的训练结果
Epoch 1/3
35700/35700 ==============================- 630s
18ms/step - loss: 0.0386 - acc: 0.9884 - val_loss: 0.0207 - val_acc:
0.9944
Epoch 2/3
35700/35700 ============================== 856s
24ms/step - loss: 0.0192 - acc: 0.9945 - val_loss: 0.0177 - val_acc:
0.9951
Epoch 3/3
35700/35700 ==============================- 601s
17ms/step - loss: 0.0162 - acc: 0.9954 - val_loss: 0.0200 - val_acc:
0.9948
Final loss: 0.019956, final accuracy: 0.994762
结论
由于之前了解过一些神经网络方面的知识,所以这次使用了神经网络的方法来进行分类预测,而且使用的库已经封装好了相关的接口,所以得到的结果也是十分好的,在只进行了 3 次 epoch 的情况下,得到了 99 的准确率。不过这个训练集本身也就挺好的,我们才能在数据处理上花费较少的功夫。总得来说,还是比较艰难的,因为自己是15级的缘故,难以找到队友,一个人完成所有任务还是花费了很多时间和精力的。整个项目我觉得可以再配合使用 openCV ,实现一个动态的预测,即用户在绘制完数字图像后,程序直接可以马上对其分类,判断出数字是什么。
参考文献列表
[1] Classification of MNIST Handwritten Digit Database using Neural Network. http://users.cecs.anu.edu.au/~Tom.Gedeon/conf/ABCs2018/paper/ABCs2018_paper_117.pdf
[2] Efficient Handwritten Digit Recognition based on Histogram of Oriented Gradients and SVM.
https://arxiv.org/ftp/arxiv/papers/1702/1702.00723.pdf
[3] Handwritten Recognition Using SVM, KNN and Neural Network.
https://arxiv.org/ftp/arxiv/papers/1702/1702.00723.pdf
[4] Handwritten Digit Recognition Using Convolutional Neural Networks.
http://www.rroij.com/open-access/handwritten-digit-recognition-using-convolutional-neural-networks-10-15680IJIRCCE-2016- 0402001.pdf
[5] Fast and Accurate Digit Classification.
https://www2.eecs.berkeley.edu/Pubs/TechRpts/2009/EECS-2009-159.pdf
[6] 任晓奎,丁鑫,陶志勇,何欣键.一种基于多分类器的无分割手写数字字符
串识别算法[J/OL].计算机应用研究.
[7] 茹晓青,华国光,李丽宏,等.基于形变卷积神经网络的手写体数字识别研究 [J].微电子学与计算机,2019,36(4):47-51
[8] 胡君萍,傅科学. 基于改进 KNN 算法的手写数字识别研究 [ A ] . 智能计算机与应用 2019.02
[9] 宋晓茹,吴 雪,高 嵩,等. 基于深度神经网络的手写数字识别模拟研究[J]. 科学技术与工程,2019,19( 5) : 193-196
[10] 王宇洋. 基于 TensorFlow 实现手写数字识别 [ A ] . 信息技术与标准化 2018.11