python、机器学习---k-近邻算法(kNN),手写体识别

1.先解释下kNN

关于k-近邻算法的介绍,大部分机器学习的书籍上都会有介绍。kNN大概是最简单的监督学习分类器,大概意思就是在一个n维空间中找出数据最近邻的分类类别。

举个例子:

python、机器学习---k-近邻算法(kNN),手写体识别

上图中的对象可以分成两组,蓝色方块和红色三角。每一组也可以称为一个类。我们可以把所有的这些对象看成是一个城镇中房子,而所有的房子分别属于蓝色和红色家族,而这个城镇就是所谓的特征空间。

现在城镇中来了一个新人,他的新房子用绿色圆盘表示。我们要根据他房子的位置把他归为蓝色家族或红色家族。我们把这过程称为分类。我们检测 k 个最近邻居,哪一类这 k 个邻居中占据多数,那个新的成员就属于谁那一类。

基于上面的例子,将红色家族标记为 Class-0,蓝色家族标记为 Class-1。还要再创建 25 个训练数据,把它们非别标记为 Class-0 或者 Class-1。Numpy中随机数产生器可以帮助我们完成这个任务。借助 Matplotlib 将这些点绘制出来。红色家族显示为红色三角,蓝色家族显示为蓝色方块。

传入一个训练数据集,以及与训练数据对应的分类来训练 kNN 分类器(构建搜索树)。最后要使用 OpenCV 中的 kNN 分类器,我们给它一个测试数据,让它来进行分类。在使用kNN之前,我们应该对测试数据有所了解。

数据应该是大小为数据数目乘以特征数目的浮点性数组。然后我们就可以通过计算找到测试数据最近的邻居了。我们可以设置返回的最近邻居的数目。返回值包括:

  1. 由 kNN 算法计算得到的测试数据的类别标志(0 或 1) 。如果你想使用最近邻算法,只需要将 k 设置为 1,k 就是最近邻的数目。
  2. k 个最近邻居的类别标志。
  3. 每个最近邻居到测试数据的距离。

测试数据被标记为绿色。

完整代码如下:

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.]]

python、机器学习---k-近邻算法(kNN),手写体识别

2.手写体识别

手写体数据集,下载地址:https://download.csdn.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-近邻算法的错误分类数目产生影响。