一、实验目的
• 1、理解图像识别、手写识别的原理;
• 2、掌握Sklearn实现基于MLP的手写识别;
• 3、掌握Sklearn实现基于KNN的手写识别。
二、实验内容与要求
【实验内容】基于MLP的手写识别
手写数字识别是一个多分类问题,共有10个分类,每个手写数字图像的类别标签是0~9中的其中一个数。例如下面这三张图片的标签分别是0,1,2。
利用sklearn来训练一个简单的全连接神经网络,即多层感知机 (Multilayer perceptron,MLP)用于识别数据集DBRHD的手写数字。
MLP的输入:
DBRHD数据集的每个图片是一个由0或1组成的32*32的文本矩阵; 多层感知机的输入为图片矩阵展开的1*1024个神经元。
MLP输出:“one-hot vectors”
一个one-hot向量除了某一位的数字是1以外其余各维度数字都是0。图片标签将表示成一个只有在第n维度(从0开始)数字为1的10维向量。比如,标签0将表示成[1,0,0,0,0,0,0,0,0,0,0]。即,MLP输出层具有10个神经元。
MLP结构:
MLP的输入与输出层,中间隐藏层的层数和神经元的个数设置都将影响该MLP模型的准确率。在本实验中,只设置一层隐藏层,将在后续实验中比较该隐藏层神经元个数为50、100、200时的MLP效果。
【任务介绍】
本实验利用sklearn来训练一个K最近邻(k-Nearest NeighborKNN)
分类器,用于识别数据集DBRHD的手写数字。比较KNN的识别效果与多层感知机的识别效果。
【KNN手写识别实体构建】
• 步骤1:建立工程并导入sklearn包
• 步骤2:加载训练数据
• 步骤3:构建KNN分类器
• 步骤4:测试集评价
三、实验程序与结果
import numpy as np
from os import listdir
from sklearn.neural_network import MLPClassifier
from sklearn.neighbors import KNeighborsClassifier
def img2vector(fileName):
retMat = np.zeros([1024],int)
fr = open(fileName)
lines = fr.readlines()
for i in range(32):
for j in range(32):
retMat[i*32+j] = lines[i][j]
return retMat
def readDataSet(path):
'''加载训练数据,并将样本标签转化为one-hot向量'''
fileList = listdir(path)
numFiles = len(fileList)
dataSet = np.zeros([numFiles,1024],int)
hwLabels = np.zeros([numFiles,10])
for i in range(numFiles):
filePath = fileList[i]
digit = int(filePath.split('_')[0])
hwLabels[i][digit] = 1.0
dataSet[i] = img2vector(path +'/'+filePath)
return dataSet,hwLabels
#读取训练集
train_dataSet, train_hwLabels = readDataSet('D:\python\pythonproject\pythonproject1\\trainingDigits')
clf = MLPClassifier(hidden_layer_sizes=(100,),
activation='logistic', solver='adam',
learning_rate_init = 0.0001, max_iter=2000)
clf.fit(train_dataSet,train_hwLabels)
#KNN
#knn = KNeighborsClassifier(algorithm='kd_tree', n_neighbors=3)
#knn.fit(train_dataSet, train_hwLabels)
#读取测试集
dataSet,hwLabels = readDataSet('D:\python\pythonproject\pythonproject1\\testDigits')
res = clf.predict(dataSet)
error_num = 0
num = len(dataSet)
for i in range(num):
if np.sum(res[i] == hwLabels[i]) < 10:
error_num += 1
print("总数:",num," 错误数量:", \
error_num," 错误率:",error_num / float(num))
四、实验结果分析
基于MLP的分析:增加隐藏层神经元个数可能增强MLP模型的学习能力,使其能够更好地捕捉手写数字图像中的特征和模式。但过小的隐藏层神经元个数可能导致模型无法表达足够的复杂性,从而会影响模型的性能;过大的隐藏层神经元个数会导致过拟合。迭代次数影响模型的训练时间和最终收敛状态。
基于KNN的分析:KNN算法中的K值决定了模型考虑的邻居数量,较小的K值可能导致模型对噪声敏感,容易产生过拟合;而较大的K值可能导致模型欠拟合。KNN算法中常用的距离度量方式包括欧氏距离、曼哈顿距离等,不同的距离度量方式可能对模型性能产生影响。
上述实验采用的隐藏层为1层,可发现KNN算法运行时间短于MLP,更快得到了结果。MLP的输入层神经元个数为1024,输出层神经元个数为10,在总数为946的情况下,MLP算法的错误数量为39,错误率为0.0412,KNN算法的错误数量为13,错误率为0.0137。MLP模型具有更强的表达能力和学习能力,但训练时间较长,而KNN模型则简单直观,训练时间较短。
MLP
神经元个数 | 50 | 100 | 200 | 500 |
错误数量 | 47 | 40 | 37 | 37 |
错误率 | 0.0497 | 0.0423 | 0.0391 | 0.0391 |
学习率 | 500 | 1000 | 1500 | 2000 |
错误数量 | 50 | 41 | 41 | 40 |
错误率 | 0.0529 | 0.0433 | 0.0433 | 0.0423 |
过小的迭代次数可能会使得MLP早停,造成较低的正确率。迭代次数100以上时,正确率基本不变,剩余的迭代次数不再进行,MLP在1000迭代时已收敛。
学习率 | 0.1 | 0.01 | 0.001 | 0.0001 |
错误数量 | 946 | 57 | 41 | 41 |
错误率 | 1.0000 | 0.0602 | 0.4333 | 0.4333 |
较小的学习率带来了更低的正确率,较小学习率无法在2000次送代内完成收敛,而步长较大的学习率使得MILP在2000次送代内快速收敛到最优解。可知,较小的学习率一般要配备较大的迭代次数以保证其收敛。
领居数量K | 1 | 3 | 5 | 7 |
错误数量 | 13 | 13 | 20 | 24 |
错误率 | 0.0137 | 0.0137 | 0.0211 | 0.0235 |
K=1、3时正确率相同,但当K>3时正确率开始下降,这是由于当样本为稀疏数据集时(总样本数量只有946个),其第k个邻居点可能与测试点距离较远,从而投出了错误的一票来影响了最终预测结果。
五、实验问题解答与体会
通过实验发现,MLP和KNN在手写数字识别任务中的性能差异较大。MLP模型具有更强的表达能力和学习能力,但训练时间较长,而KNN模型则简单直观,训练时间较短。
此次试验我掌握了Sklearn实现基于MLP和KNN的手写识别。 可知KNN的准确率远高于MIP分类器,这是由于MIP在小数据集上容易过拟合的原因,MLP对于参数的调整比较敏感,若参数设置不合理容易得到较差的分类效果,因此参数的设置对于MLP至关重要。
在调参过程中发现,MLP模型的性能受到隐藏层神经元个数、迭代次数和学习率的影响较大。需要通过交叉验证等方法来选择最佳参数,以避免过拟合或欠拟合。
对于KNN模型,邻居数量K和距离度量方式是影响性能的重要因素,合适的K值和距离度量方式可以提高模型的性能。
此次试验让我明白了,在编写代码之前,务必仔细检查文件路径和文件名,确保它们是正确的。在处理数据时,要格外注意数据的质量和完整性,以避免在后续的分析和建模过程中出现问题。对于模型训练效果不佳的情况,需要耐心和细心地调整模型参数、优化数据预处理步骤,并且不断尝试不同的方法,直到找到合适的解决方案。通过不断学习和尝试代码算法,才会得到实用的经验和教训。所以,在以后的工作中,我们要保持持续学习的态度,不断改进和优化模型,提高工作效率和质量。