在之前的文章中介绍了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)
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)
trainingMat = np.zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
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:
"""
imgTo01.picTo01(filename)
name01 = filename.split('.')[0]
name01 = name01 + '.txt'
hwMat = img2vector(name01)
hwLabels = []
trainingFileList = listdir('trainingDigits')
m = len(trainingFileList)
trainingMat = np.zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
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
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)