摘要

手写数字识别是模式识别中一个非常重要和活跃的研究领域,数字识别也不是一项孤立的技术,他涉及的问题是模式识别的其他领域都无法回避的;应用上,作为一种信息处理手段,字符识别有广阔的应用背景和巨大的市场需求。因此,对数字识别的研究具有理论和应用的双重意义。

人工神经网络识别方法是近年该研究领域的一种新方法,该方法具有一些传统技术所没有的优点:良好的容错能力、分类能力强、并行处理和自学习能力,并且是离线训练和在线识别的。这些优点使它在手写体字符的识别中能对大量数据进行快速实时处理,并达到良好的识别效果。

由于手写数字识别难于建立精确的数学模型,所以本文使用Python实现了三种方法,KNN、基于Tensorflow的卷积神经网络和SEnet神经网络三种手写数字识别算法,并编程实现GUI 界面,构建手写数字识别系统。本系统界面设计友好,功能完善。通过测试,本识别系统对于较规范的手写体数字的识别达到了很好的识别效果。

下载mnist数据集

http://yann.lecun.com/exdb/mnist/

安装必要的库

pip install PyQt5
pip install PyQt5 -sip
pip install PyQt5 -tools
pip install numpy
pip install tensorflow
pip install keras
pip install opencv-python
pip install matplotlib
pip install Pillow

训练模型

1、KNN算法

(1)使用图形处理软件,将数据处理成具有相同的色彩和大小:32像素*32像素的黑白图像。当前使用文本格式存储图像。
在这里插入图片描述(2)准备数据:将图像转换为测试向量
  trainingDigits中包含了大约2000个例子,每个数字大约有200个样本;测试文件testDigits中包含了大约900个测试数据。两组数据没有重叠。为了使用kNN算法分类器必须将一个32x32的二进制矩阵转换为1x1024的向量,以便我们使用分类器处理数字图像信息。

def img2vector(filename):
    returnVect = zeros((1, 1024))  # 创建一个1*1024的数组
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()  # 每次读入一行
        for j in range(32):
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect

(3)KNN算法进行分类

 def classify0(inX, dataSet, labels, k):  # inX为用于分类的输入向量,dataSet为输入的训练样本集, labels为训练标签,k表示用于选择最近的数目
    dataSetSize = dataSet.shape[0]  # dataSet的行数
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet  # 将inX数组复制成与dataSet相同行数,与dataSet相减,求坐标差
    sqDiffMat = diffMat ** 2  # diffMat的平方
    sqDistances = sqDiffMat.sum(axis=1)  # 将sqDiffMat每一行的所有数相加
    distances = sqDistances ** 0.5  # 开根号,求点和点之间的欧式距离
    sortedDistIndicies = distances.argsort()  # 将distances中的元素从小到大排列,提取其对应的index,然后输出到sortedDistIndicies
    classCount = {}  # 创建字典
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]  # 前k个标签数据
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1  # 判断classCount中有没有对应的voteIlabel,
        # 如果有返回voteIlabel对应的值,如果没有则返回0,在最后加1。为了计算k个标签的类别数量
    sortedClassCount = sorted(classCount.items(),
                              key=operator.itemgetter(1), reverse=True)  # 生成classCount的迭代器,进行排序,
    # operator.itemgetter(1)以标签的个数降序排序
    return sortedClassCount[0][0]  # 返回个数最多的标签

(4)测试算法:使用kNN识别手写数字

def handwritingClassTest():
    hwLabels = []  # 标签集
    trainingFileList = listdir('./digits/trainingDigits')  # listdir获取训练集的文件目录
    m = len(trainingFileList)  # 文件数量
    trainingMat = zeros((m, 1024))  # 一个数字1024个字符,创建m*1024的数组
    for i in range(m):
        fileNameStr = trainingFileList[i]  # 获取文件名
        fileStr = fileNameStr.split('.')[0]  # 以'.'将字符串分割,并取第一项,即0_0.txt取0_0
        classNumStr = int(fileStr.split('_')[0])  # 以'_'将字符串分割,并取第一项
        hwLabels.append(classNumStr)  # 依次存入hwLabels标签集
        trainingMat[i, :] = img2vector('./digits/trainingDigits/%s' % fileNameStr)  # 将每个数字的字符值依次存入trainingMat
    testFileList = listdir('./digits/testDigits')  # 读入测试数据集
    errorCount = 0.0  # 测试错误数量
    mTest = len(testFileList)  # 测试集的数量
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])  # 测试数据标签
        vectorUnderTest = img2vector('./digits/testDigits/%s' % fileNameStr)  # 读入测试数据
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)  # 分类器kNN算法,3为最近邻数目
        print("the calssifier came back with: %d, the real answer is : %d" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print("\nthe total number of errors is : %f" % errorCount)
    print("\nthe total error rate is :%f" % (errorCount / float(mTest)))

2、CNN算法

(1)数据加载

mnist = tf.keras.datasets.mnist  # 加载待训练的数据集mnist

(2)数据处理

(x_train, y_train), (x_test, y_test) = mnist.load_data()  # 进行训练集和测试集的划分
x_train, x_test = x_train / 255.0, x_test / 255.0  # 对输入特征进行归一化
x_train = x_train.reshape(-1, 1, 28, 28)  # 确保输入特征与网络输入要求一致,chanel为1
x_test = x_test.reshape(-1, 1, 28, 28)

(3)定义网络模型

# 构建模型结构
model = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(filters=16, kernel_size=(3, 3), padding='same'),  # 2d卷积,输出通道16
    tf.keras.layers.BatchNormalization(),  # BN,防止过拟合
    tf.keras.layers.Activation('relu'),  # relu激活函数
    tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),  # 池化,降低维度
    tf.keras.layers.Dropout(0.2),  # Dropout 随机舍弃

    tf.keras.layers.Conv2D(filters=32, kernel_size=(3, 3), padding='same'),  # 2d卷积,输出通道32
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.Activation('relu'),
    tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2, padding='same'),
    tf.keras.layers.Dropout(0.2),

    tf.keras.layers.Flatten(),  # 拉直,送入全连接层
    tf.keras.layers.Dense(128, activation='relu'),  # 全连接层,128个隐藏节点
    tf.keras.layers.Dropout(0.2),  # Dropout 随机舍弃,防止过拟合
    tf.keras.layers.Dense(10, activation='softmax')  # softmax输出最终预测的10个值概率向量
])

(4)模型训练

adam = Adam(lr=0.01, decay=1e-06)  # adam优化器,lr学习率可自,decay防止过拟合
model.compile(optimizer='adam',  # 告知训练时用的优化器、损失函数和准确率评测标准
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./model/mnist.ckpt"  # 判断是否已存在模型,存在的话就加载
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)  # 加载

# 回调函数
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)

# 开始训练
history = model.fit(x_train, y_train, batch_size=128, epochs=200, validation_data=(x_test, y_test), validation_freq=1,
                    callbacks=[cp_callback])

(5)训练效果可视化

可视化训练效果, loss和acc

acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()

3、SEnet算法

(1)数据加载

mnist = tf.keras.datasets.mnist  # 加载待训练的数据集mnist

(2)数据处理

(x_train, y_train), (x_test, y_test) = mnist.load_data()  # 进行训练集和测试集的划分
x_train, x_test = x_train / 255.0, x_test / 255.0  # 对输入特征进行归一化
x_train = x_train.reshape(-1, 1, 28, 28)  # 确保输入特征与网络输入要求一致,chanel为1
x_test = x_test.reshape(-1, 1, 28, 28)

(3)定义网络结构

model = SENet(input_shape=(28, 28, 1), output_units=10, activation='sigmoid', SE=True, data_format='channels_last')

(4)模型训练

adam = Adam(lr=0.01, decay=1e-06)  # adam优化器,lr学习率可自,decay防止过拟合
model.compile(optimizer='adam',  # 告知训练时用的优化器、损失函数和准确率评测标准
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./se_model/mnist.ckpt"  # 判断是否已存在模型,存在的话就加载
if os.path.exists(checkpoint_save_path + '.index'):
    print('-------------load the model-----------------')
    model.load_weights(checkpoint_save_path)  # 加载

# 回调函数
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
                                                 save_weights_only=True,
                                                 save_best_only=True)

# 开始训练
history = model.fit(x_train, y_train, batch_size=128, epochs=200, validation_data=(x_test, y_test), validation_freq=1,
                    callbacks=[cp_callback])

(5)模型训练可视化

# 可视化训练效果, loss和acc
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()

模型最后重新训练一下,效果会更优

PYQT界面

在这里插入图片描述

演示视频

https://live.csdn.net/v/280110

参考链接:https://zhuanlan.zhihu.com/p/630087433

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐