欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

机器学习_KNN实验(手写数字的识别)

程序员文章站 2022-07-14 20:01:07
...

kNN实验

手写数字识别

实验内容

  1. 使用K-NN算法识别数字0-9,实现最基本的KNN算法,使用trainingDigits文件夹下的数据,对testDigits中的数据进行预测。(K赋值为1,使用欧氏距离多数投票决定分类结果)
  2. 改变K的值,并观察对正确率的影响。
  3. 更改距离度量方式,更改投票方式(距离加权),分析错误率。

实验要求

1.要求给出代码,以及运行窗口截图。

2.K对正确率的影响,最好用表格或作图说明,并做简要分析。

算法描述:

基本思想

以所有已知类别的样本作为参照,计算未知样本与所有已知样本的距离,从中选取与未知样本距离最近的K个已知样本,根据少数服从多数的投票法则(majority-voting),将未知样本与K个最邻近样本中所属类别占比较多的归为一类。

算法关键

(1) 样本的所有特征都要做可比较的量化 : 若是样本特征中存在非数值的类型,必须采取手段将其量化为数值。例如样本特征中包含颜色,可通过将颜色转换为灰度值来实现距离计算。

(2) 样本特征要做归一化处理 : 样本有多个参数,每一个参数都有自己的定义域和取值范围,他们对距离计算的影响不一样,如取值较大的影响力会盖过取值较小的参数。所以样本参数必须做一些 scale 处理,最简单的方式就是所有特征的数值都采取归一化处置。

(3) 需要一个距离函数以计算两个样本之间的距离 :通常使用的距离函数有:欧氏距离、余弦距离、汉明距离、曼哈顿距离等,一般选欧氏距离作为距离度量,但是这是只适用于连续变量。

(4) 确定K的值: K值选的太大易引起欠拟合,太小容易过拟合,需交叉验证确定K值。

实验步骤:

数据集介绍

digits 目录下有两个文件夹,分别是:1、trainingDigits:训练数据,1934个文件,每个数字大约200个文件;2、testDigits:测试数据,946个文件,每个数字大约100个文件。每个文件中存储一个手写的数字,文件的命名类似0_7.txt,第一个数字0表示文件中的手写数字k是0,后面的7是个序号。

实现基本knn算法

首先将32x32的二进制图像矩阵转换为1x1024的向量

def img2vector(fname):
    reVect = np.zeros((1,1024))#将32x32的二进制图像矩阵转换为1x1024的向量
    fr = open(fname)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            reVect[0,32*i+j] = int(lineStr[j])
    return reVect

然后实现knn分类器

#实现knn分类器
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
     #下面的四行代码计算距离,使用欧氏距离
    diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    #对距离进行排序
    sortedDistIndicies = distances.argsort()     
    classCount={}          
    #确定前k个较小距离的类别
    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]

最后就可以检测一下knn的效果了

def handwritingTest(n):#n为设定的k值
    hwLabels = []
    tFL = listdir('trainingDigits')#加载训练集trainingFileList
    m = len(tFL)
    tM = np.zeros((m,1024))
    for i in range(m):
        #从文件名解析分类数字
        fileNameStr = tFL[i]
        fileStr = fileNameStr.split('.')[0] 
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        tM[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 = classify0(vectorUnderTest, tM, hwLabels, n)
        print ("分类器得到结果为: %d, 实际情况为: %d\n" % (classifierResult, classNumStr))
        if (classifierResult != classNumStr): errorCount += 1.0
    print ("总错误个数为: %d" % errorCount)
    print ("\n总错误率为: %f" % (errorCount/float(mTest)))
    
if __name__ =="__main__":   
    start=time.process_time() 
    handwritingTest(3)
    end=time.process_time()
    print('Running time:',end-start)#显示运行时间
实验一结果

k=5时结果为:
机器学习_KNN实验(手写数字的识别)

观察k对正确率的影响

下图为k值对错误率的影响:(可以看到k=3时错误率最小,同理可得对正确率的影响图)

机器学习_KNN实验(手写数字的识别)

机器学习_KNN实验(手写数字的识别)

更改距离度量方式更改投票方式(距离加权)分析错误率
使用余弦距离测试 排序时降序
def classify0(inX, dataSet, labels, k):
    distances = np.array([0 for _ in range(1934)] , dtype=float)
    #计算距离,使用余弦距离(Cosine Distance)
    sqDiffMat = inX**2
    sqDistances = sqDiffMat.sum()
    sqDistances = sqDistances**0.5
    #放在循环外,因为每次都要算测试样本的长度
    reVect = dataSet
    for i in range(1934):
        diffMat = float(np.dot(inX,reVect[i].T))
        sqDiffMat1 = reVect[i]**2
        sqDistances1 = sqDiffMat1.sum()
        sqDistances1 = sqDistances1**0.5
        distances[i] = float(diffMat/sqDistances*sqDistances1)
    #对距离进行排序,由性质得cos越大相似度越大
    sortedDistIndicies = np.argsort(-distances)     
    classCount={}          
    #确定前k个较小距离的类别
    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]

但是得出结果错误率较高,发现是没有归一化的问题,后面改进

机器学习_KNN实验(手写数字的识别)

归一化处理后代码 结果k=5时效果比欧式距离好
def classify0(inX, dataSet, labels, k):
    distances = np.array([0 for _ in range(1934)] , dtype=float)
    reVect = dataSet
    for i in range(1934):
        num = float(np.dot(inX,reVect[i].T)) 
        denom = linalg.norm(inX) * linalg.norm(reVect[i])
        cos = num / denom #余弦值
        distances[i] = 0.5 + 0.5 * cos #归一化
    #对距离进行排序
    sortedDistIndicies = np.argsort(-distances)     
    classCount={}          
    #确定前k个较小距离的类别
    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]

机器学习_KNN实验(手写数字的识别)

更改投票方式(距离加权)
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1/(1+math.log(i+1,2))
#只需要改这一行即可,将投票机制加权

得到结果如下:

机器学习_KNN实验(手写数字的识别)

将所有的数据进行对比:
机器学习_KNN实验(手写数字的识别)
机器学习_KNN实验(手写数字的识别)
机器学习_KNN实验(手写数字的识别)
机器学习_KNN实验(手写数字的识别)
可以看到,加权投票机制使得错误率得到一定的下降,余弦距离的相似度对k值较大时效果还不如欧氏距离。