您的位置:首页 > 其它

使用k-近邻算法识别手写数字。

2018-01-06 17:37 357 查看
在之前的文章中介绍了k-近邻算法的原理知识并且用Python实现了一个分类器,而且完成了一个简单的优化约会网站配对效果的实例。在《机器学习实战》中有关kNN的后一部分内容就是一个手写识别系统,可以识别手写的0-9的数字。下面就基于这一章的内容完成这样一个手写数字识别系统。

案例的描述以及流程介绍。

既然我们明白了kNN算法是根据计算新数据和样本数据集之间的距离,然后找到距离最小的样本的分类作为新数据的分类。所以我们也需要考虑如何计算0-9之间这十个数字的距离。首先书中已经提供了0-9数字的样本集,每个数字提供了大概200左右的样本,在trainingDigits目录下。



比如其中的0_0.txt就是数字0的第一个样本,又比如9_45.txt就是数字9的第45个实例,数字样本采用的是32*32文本存储方式:

比如:0_0.txt,从图中也能看出来像数字0。



但是提供的有些数据集并不是日常所见的数字写法,比如数字7:



由于我们后续需要计算的距离就是根据这些0和1的排列方式计算的,所以类似数字7这种写法差异的话,最后分类结果也可能有差异。如果可能的话,自己也是可以根据自己的书写习惯写样本集,然后将其转换为32*32文本存放,这样一来分类结果肯定要比这种的好一点。

至于如何将图片转换为32*32数组,可以参考之前的文章:手写数字图片二值化转换为32*32数组。

在本次案例中kNN算法的使用流程:

收集数据:提供文本文件。

准备数据:自己提供的32*32数组。

分析数据:查看数据,确保符合要求。

训练算法:此步骤不适用于k-近邻算法。

测试算法:编写函数使用提供的部分数据集作为测试样本,测试样本与非测试样本的区别在于测试样本是已经完成分类的数据,如果预测分类与实际类别不同,则标记为一个错误。

使用算法:将一个图片作为输入数据,然后使用分类器进行分类得到结果。

准备数据:将图像转换为测试向量。

上面已经介绍了本次的样本集是一个32*32的数组,为了使用前面的分类器,我们必须将这个32*32的数组转换为一个1*1024的向量。首先编写一个img2vector的函数,传入一个样本数据的文件名,可以将这个文件中的32*32数组转换为一个1*1024的向量。

def img2vector(filename):
"""
将32*32的图像矩阵转化为1*1024的向量
:param filename: 文件名
:return:
"""
# 制造一个空数组
returnVect = np.zeros((1,1024))

# 打开文件
fr = open(filename)

# 将矩阵转换为1*1024的
for i in range(32):
# 读取一行数据
lineStr = fr.readline().strip()

# 将每行的数字存放在数组中
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j])

return returnVect


编写一个简单的测试代码:



首先将0_13.txt作为参数传入img2vector函数中,然后使用len计算结果的长度是不是1*1024的,然后输出第一行内容,输出结果:



其中输出的testVector[0, 0:31]就是0_13.txt的第一行内容,可以打开0_13.txt查看对比一下:



能够看到结果一致,依此论推,testVector[0, 32:63]就是第二行的内容等等,所以确实是将0_13.txt的32*32数组转换为了1*1024向量数组。

测试算法:使用k-近邻算法识别手写数字。

我们在上一步中将32*32数组转化为1*1024向量数组的目的就是为了能够使用之前编写的kNN分类器。现在就可以将样本数据集输入到分类器中进行使用了。

我们先来测试一下这个算法,编写一个handwritingClassTest函数,该函数将trainingDigits目录下的所有样本进行读取,然后创建一个m*1024的训练矩阵进行存储,很显然该矩阵每一行都代表着一个图像。然后再读取testDigits目录下的测试样本集,每次读取一个文件,也就代表着一个图像,然后将其转换为1*1024数组,和原来的训练矩阵共同输入到之前的kNN分类器中进行分类。分类器会计算这个测试样本集和训练矩阵的距离,根据欧氏距离的计算方法,这个测试数据共计算m次距离,然后找到距离最近的,这个计算量还是比较巨大的。

def handwritingClassTest():
"""
数字测试
:return:
"""
# 数字对应的标签,也就是数字的本身
hwLabels = []

# 得到目录下的所有文件名称
trainingFileList = listdir('trainingDigits')

# 计算共有多少个文件
m = len(trainingFileList)

# 构造m*1024 数组,用来存放所有的数字
trainingMat = np.zeros((m, 1024))

# 遍历所有的文件,将其加载到数组中
for i in range(m):
# 得到文件名称
fileNameStr = trainingFileList[i]

# 去除后面的.txt,得到有用的文件名
fileStr = fileNameStr.split('.')[0]

# 解析出来当前是哪个数字
classNumStr = int(fileStr.split('_')[0])

# 添加到标签上
hwLabels.append(classNumStr)

# 将文件转化为数组并存放到总的数组中
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)

# 得到测试文件的目录
testFileList = listdir('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('testDigits/%s' % fileNameStr)

# 使用分类器得到结果
classifierResult = kNN.classify0(vectorUnderTest, trainingMat, hwLabels, 3)

# 打印结果
print('使用分类器得到的结果为:%s,真实的结果为:%s' % (classifierResult, classNumStr))

# 错误的话记录下来
if classifierResult != classNumStr:
errorCount += 1.0

print('识别错误的个数为:%s' % errorCount)
print('分类器的正确率为:%f' % (errorCount/float(mTest)))


编写一个测试代码执行这个函数:



查看一下输出结果:



可以看到错误率大概为1.2%,其实这些都是根据计算得到的结果,如果我们修改分类器k的值,或者选取不同的测试样本或者训练样本,都会影响这个错误率,不过可以看到这些训练样本集的正确率已经很高了,我们现在就可以真正的使用分类器进行识别了。

使用算法:将手写数字图片进行识别。

在测试完分类器之后,我们就可以使用了。最终我们的目的就是输入一个自己手写的数字图片,然后分类器告诉我这个数字是几。编写classifyHandwriting函数来使用完整的系统,该函数需要一个图像名称参数,然后会将这个图像转换为32*32数组,接下来转换为1*1024数组,在读取训练样本集,然后将新数据和训练样本集共同输入到分类器中得到分类结果。

def classifyHandwriting(filename):
"""
将图片转化为01矩阵,然后使用分类器进行分类
:return:
"""
# 得到32*32的01数组
imgTo01.picTo01(filename)

# 得到对应名称的txt文件
name01 = filename.split('.')[0]
name01 = name01 + '.txt'

# 将文件中的32*32 转化为1*1024的
hwMat = img2vector(name01)

# 数字对应的标签,也就是数字的本身
hwLabels = []

# 得到目录下的所有文件名称
trainingFileList = listdir('trainingDigits')

# 计算共有多少个文件
m = len(trainingFileList)

# 构造m*1024 数组,用来存放所有的数字
trainingMat = np.zeros((m, 1024))

# 遍历所有的文件,将其加载到数组中
for i in range(m):
# 得到文件名称
fileNameStr = trainingFileList[i]

# 去除后面的.txt,得到有用的文件名
fileStr = fileNameStr.split('.')[0]

# 解析出来当前是哪个数字
classNumStr = int(fileStr.split('_')[0])

# 添加到标签上
hwLabels.append(classNumStr)

# 将文件转化为数组并存放到总的数组中
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)

# 进行分类
classifierResult = kNN.classify0(hwMat, trainingMat, hwLabels, 3)

print('使用分类器的结果为:%d' % classifierResult )


编写测试代码:



准备手写数字:



运行分类器得到结果:



能够看到识别出了数字,但是能够正确识别的关键点在于能否准确的将图片转换为32*32数组。因为使用的图片转换为32*32数组是本人简单写的一个程序,在转换过程中可能会出现问题导致分类结果不准确,如果能够用其他办法准确的将图片转换为32*32数组的话,这个分类器还是比较准确的。

全部代码及数据集地址:MachineLearningNote
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: