python、机器学习---k-近邻算法(kNN),手写体识别
1.先解释下kNN
关于k-近邻算法的介绍,大部分机器学习的书籍上都会有介绍。kNN大概是最简单的监督学习分类器,大概意思就是在一个n维空间中找出数据最近邻的分类类别。
举个例子:
上图中的对象可以分成两组,蓝色方块和红色三角。每一组也可以称为一个类。我们可以把所有的这些对象看成是一个城镇中房子,而所有的房子分别属于蓝色和红色家族,而这个城镇就是所谓的特征空间。
现在城镇中来了一个新人,他的新房子用绿色圆盘表示。我们要根据他房子的位置把他归为蓝色家族或红色家族。我们把这过程称为分类。我们检测 k 个最近邻居,哪一类这 k 个邻居中占据多数,那个新的成员就属于谁那一类。
基于上面的例子,将红色家族标记为 Class-0,蓝色家族标记为 Class-1。还要再创建 25 个训练数据,把它们非别标记为 Class-0 或者 Class-1。Numpy中随机数产生器可以帮助我们完成这个任务。借助 Matplotlib 将这些点绘制出来。红色家族显示为红色三角,蓝色家族显示为蓝色方块。
传入一个训练数据集,以及与训练数据对应的分类来训练 kNN 分类器(构建搜索树)。最后要使用 OpenCV 中的 kNN 分类器,我们给它一个测试数据,让它来进行分类。在使用kNN之前,我们应该对测试数据有所了解。
数据应该是大小为数据数目乘以特征数目的浮点性数组。然后我们就可以通过计算找到测试数据最近的邻居了。我们可以设置返回的最近邻居的数目。返回值包括:
- 由 kNN 算法计算得到的测试数据的类别标志(0 或 1) 。如果你想使用最近邻算法,只需要将 k 设置为 1,k 就是最近邻的数目。
- k 个最近邻居的类别标志。
- 每个最近邻居到测试数据的距离。
测试数据被标记为绿色。
完整代码如下:
import cv2
import numpy as np
import matplotlib.pyplot as plt
# Feature set containing (x,y) values of 25 known/training data
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)
# Labels each one either Red or Blue with numbers 0 and 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)
# Take Red families and plot them
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')
# Take Blue families and plot them
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')
newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')
knn = cv2.ml.KNearest_create()
knn.train(trainData,cv2.ml.ROW_SAMPLE,responses)# 第二个参数也可以为 cv2.ml.COL_SAMPLE
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)
print("result: ", results,"\n")
print("neighbours: ", neighbours,"\n")
print("distance: ", dist)
plt.show()
结果如下:
result: [[0.]]
neighbours: [[0. 0. 0.]]
distance: [[125. 449. 794.]]
2.手写体识别
手写体数据集,下载地址:https://download.****.net/my,创建文件夹handwrite_dis,下载文件解压到该文件夹下,解压后文件命名为digits,内含trainingDigits和testDigits。
在hanwrite_dis文件夹下创建digit_disinguish.py ,代码如下:
import operator
from os import listdir
import numpy as np
'''
函数img2vector,将32X32的二进制图像转换为1X1024 的向量,该函数创建了1X1024的numpy数组。
打开给定文件,循环读出文件的前32行,并将每行的头32个字符值存储在numpy数组中,最后返回数组。
'''
def img2vector(filename):
#创建向量
returnVect=np.zeros((1,1024))
#打开数据文件读取每行内容
fr=open(filename)
for i in range(32):
#读取每一行
lineStr=fr.readline()
#将每行的前32字符转换成int存入向量
for j in range(32):
returnVect[0,32*i+j]=int(lineStr[j])
return returnVect
'''
k-近邻算法实现过程:
1.计算已知类别数据集中的点与当前点之间的距离,采用欧式距离公式计算
2.按距离递增次序排序
3.选取与当前距离最小的k个点
4.确定当前k个所在类别出现的频率
5.返回前k个点出现频率出现最高的类别作为预测分类
'''
def classify0(inX,dataSet,labels,k):
'''
参数:
--inX:用于分类的输入向量
--dataSet:输入的训练样本集
--labels:样本数据的类标签向量
--k:用于选择近邻居的数目
'''
#获取样本数据的数量
dataSetSize=dataSet.shape[0]
#矩阵运算,计算测试数据与每个样本数据对应数据项的差值
diffMat=np.tile(inX,(dataSetSize,1))-dataSet
#sqDistance上一步骤结果的平方和
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1)
#取得平方根,得到距离向量
distances = sqDistances**0.5
#按照距离从低到高排位
sortedDistIndicies = distances.argsort()
classCount = {}
#依次取出最近的样本数据
for i in range(k):
voteIlabel=labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel,0)+1
#对类别出现的频率次数排序,从高到低
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回频率次数出现最高的类别
return sortedClassCount[0][0]
'''
测试算法:使用k近邻算法识别手写体数字
1.读取训练数据到向量(手写体图片数据),从数据文件名中提取类别标签列表(每个向量对应的真实的数字)
2.读取测试数据到向量,从数据文件名中提取类别标签
3.执行k近邻算法对测试数据进行测试,得到分类结果
4.与实际的类别标签进行对比,记录分类错误率
5.打印每个数据文件分类数据以及错误率作为最终的结果
'''
def handwritingClassTest():
hwLabels=[]
#样本数据文件列表
trainingFileList=listdir('G:/kNN_handwrite_rec/digits/trainingDigits')
m = len(trainingFileList)
print(m)
#初始化样本数据矩阵(M*1024)
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('digits/trainingDigits/%s' %fileNameStr)
#循环读取测试数据
testFileList = listdir('digits/testDigits')
#初始化错误率
errorCount = 0.00
mTest = len(testFileList)
print(mTest)
#循环测试每个测试数据文件
for i in range(mTest):
#提取文件中的数字
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int (fileStr.split('_')[0])
#提取数据向量
vectorUnderTest = img2vector('digits/trainingDigits/%s' % fileNameStr)
#对数据文件进行分类
classifierResult = classify0(vectorUnderTest,trainingMat,hwLabels,4)
#打印 K-近邻算法分类结果和真实的分类
print("测试样本:%d,分类器预测:%d,真实类别:%d" % (i+1,classifierResult,classNumStr))
#判断 k-近邻算法结果是否准确
if (classifierResult != classNumStr):
errorCount+=1.00
print("\n错误分类计数:%d" %errorCount)
print("\n错误分类比例:%f" %(errorCount/(mTest)))
print("\n正确分类比例:%f" %((mTest-errorCount)/(mTest)))
if __name__ == "__main__":
handwritingClassTest()
运行成功后结果如下:
.........
测试样本:934,分类器预测:9,真实类别:9
测试样本:935,分类器预测:9,真实类别:9
测试样本:936,分类器预测:9,真实类别:9
测试样本:937,分类器预测:9,真实类别:9
测试样本:938,分类器预测:9,真实类别:9
测试样本:939,分类器预测:9,真实类别:9
测试样本:940,分类器预测:9,真实类别:9
测试样本:941,分类器预测:9,真实类别:9
测试样本:942,分类器预测:9,真实类别:9
测试样本:943,分类器预测:9,真实类别:9
测试样本:944,分类器预测:9,真实类别:9
测试样本:945,分类器预测:9,真实类别:9
测试样本:946,分类器预测:9,真实类别:9
错误分类计数:8
错误分类比例:0.008457
正确分类比例:0.991543
改变变量k值,修改函数handwriteClassTest,随机选取训练样本,改变训练样本数目,都会对k-近邻算法的错误分类数目产生影响。