基于knn的手写数字识别

一、实验目的

熟悉KNN算法的基本流程,同时掌握sklearn机器学习库的使用。

(代码在后面)

  • 实验内容

本实验首先使用基于Python实现KNN算法实现手写字识别,然后使用sklearn库的KNN算法实现手写字识别。

  1. 实验数据准备

我们首先使用如下解压命令将kNN_hand_writing.tgz压缩包解压,里面包含两个子目录,分别为trainingDigits和testDigitstrainingDigits为训练数据集的文件,文件数量为1935;testDigits为测试数据集的文件,文件数量为947。每个文件的命名格式都为“真实数字_编号.txt”。为了简便处理,实验中,用txt文本文件表示图片。原图片中像素值为黑色(0,0,0)的像素点在txt中对应的用0表示,像素值为白色(255,255,255)的像素点用1表示。所以,只需要处理这些文本文件即可,不用再去解析图片格式。每个txt文件中,数据共有32行和32列,这是由于原图片的大小为32X32。

  1. KNN核心算法

KNN的算法流程为:

a.计算已知类别数据集中的点与当前点之间的距离;

b.按照距离递增次序排序;

c.选取与当前点距离最小的k个点;

d.确定前k个点所在类别的出现频率;

e.返回前k个点所出现频率最高的类别作为当前点的预测分类。

在这一步里,我们将定义函数classify0作为KNN算法的核心函数,其函数的完整形式为: def classify0(inX, dataSet, labels, k): 其中各参数含义为:

inX – 用于要进行分类判别的数据,是一个向量(来自测试集)

dataSet – 所有训练数据的集合,是一个矩阵(训练集)

lables – 分类标签

k – KNN算法参数,选择距离最小的k个点

根据KNN算法流程,我们首先应该计算inX这个要判别分类的点到dataSet中每个点之间的距离,dataSet中每个点也是用向量表示的,点与点之间的距离,实则就是求两个向量之间的距离,在数学中有很多距离计算公式,我们这里选择最简单的欧氏距离计算方法。设A和B为两向量,则两向量间的欧氏距离为:

函数的输入参数和距离计算公式都明确了,下面让我们来实现KNN算法。这里,我们使用numpy提供的各种功能来实现该算法,相比于自己纯手写各种线性代数变换操作,使用numpy的效率要高的多。classify0函数的完整如下所示,第2行获得数据集的行数,即已知数据集中所有点的数量。第3到第6行为计算要分类的点到数据集中每个点的距离,计算过程中用到很多numpy的特性。第7行按照距离远近从近到远排序,注意argsort返回的是原始下标排序后的下标序列。你的任务是将统计距离最近的前k个点中每个类别出现的次数代码补充完整,并取出出现频率最高的分类。

接下来,我们用个小例子验证一下KNN算法,随机挑选6位高中生,分别让他们做文科综合试卷的分数和理科综合试卷的分数,下表为分数以及分类信息

直觉上,理科生的理综成绩比较高,文综成绩较低,文科生的文综成绩较高,理综成绩较高。基于这些信息,我们利用kNN算法判 断成绩为(105,210)所属的分类,通过如下代码实现,可以发现结果显示“文科生”,输出结果结果比较符合预期。 

  1. 数据加载和处理

接下来,我们将加载手写字训练集数据,由于kNN核心算法中,每个点都是用向量表示的。尽管已经转换为文本形式了,不过图片数据依然是32X32的二维数据格式,首先需要将其转换为一维数组,即表示成一个向量。函数img2vector用于将32X32的二维数组转换为一维数组,该函数传入参数文件名,返回转换后的一维数组:

下面来测试函数img2vector是否正确,传入的文件路径为:kNN_hand_writing/trainingDigits/1_1.txt,如果打印结果如下为1024维向量,函数编写正确。

(图片省略部分输出内容)

接下来我们编写loadTrainData函数,加载所有训练数据。该函数将返回表示训练集数据的矩阵以及每个点对应的分类标签:

  1. KNN手写识别

们来验证一下我们的kNN手写字识别算法的准确性,测试使用的数据集位于目录testDigits下。测试的流程为:依次读取testDigits目录下的每个文件,根据文件中的数据,使用分类函数classify0确定其分类,并和其真实分类进行对比,如果一致,表示分类正确,如果不一致,表示分类错误。统计错误的数量,计算错误率,错误率为分类出错的数量除以测试集中测试数据总量。编写handwritingClassTest函数测试基于kNN算法实现的手写字识别系统准确性,前面部分已经给出,你的任务是实现依次读取testDigits目录下的每个文件,根据文件名称获取其真实分类数字,然后使用classify0函数基于kNN算法进行分类,对比两个分类结果。如果不一致,就对errorCount自增1的代码。

最后,添加main入口,调用handwritingClassTest函数,对手写字识别系统进行验证。可以看到测试总数据量为946,出错的数据量为10,错误率为0.010571%。下面结果是k取3的结果,你可以将k的数量进行调整,观察k的值和错误间的联系。

(图片省略部分输出结果)

  1. 基于sklearn的kNN实现手写字识别

sklearn.neighbors模块实现了k-近邻算法,点击官网英文文档地址,在sklearn官网上查看sklearn的kNN算法的详细使用手册。我们使用其中的KNeighborsClassifier也可以实现上小节自己手动实现的kNN算法,KNeighborsClassifier函数一共有8个参数,我们这里重点介绍一下n_neighbors,其默认为5,就是KNN算法的k的值,其余参数可以在官方文档学习。

由于基于sklearn提供的kNN算法,修改kNNHandWrittenTest函数,将调用classify0的部分逻辑修改为sklearn提供的kNN接口。两个版本的其他部分的逻辑是完全一样的:

出错的数据数量为12,错误率为0.012685%。你可以根据sklearn的kNN算法手册,调整kNN算法的相关参数,尝试着降低错误率。

# -*- coding: utf-8 -*-
"""
Created on Thu May 12 15:36:45 2022

@author: 86152
"""

'''
.KNN核心算法
KNN的算法流程为:
		a.计算已知类别数据集中的点与当前点之间的距离;
b.按照距离递增次序排序;
c.选取与当前点距离最小的k个点;
d.确定前k个点所在类别的出现频率;
e.返回前k个点所出现频率最高的类别作为当前点的预测分类。
'''



import os
import numpy as np
import operator
from os import listdir
def classify0(inX, dataSet, labels, k):
	m=dataSet.shape[0]   #返回dataSet的行数
	diffMat=np.tile(inX,(m, 1))-dataSet 
	sqDiffMat = diffMat**2 
	sqDistances = sqDiffMat.sum(axis=1) 
	distances = sqDistances *0.5 
	sortedDistIndices = distances.argsort() # argsort函数返回的数组值从小到大的索引值
	classCount = {}  #用于类别/次数的字典,key为类别, value为次数  
	for i in range(k):
		#取出第近的元素对应的类别
		voteIlabel=labels[sortedDistIndices[i]]
		#对类别次数进行累加
		classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
		#根据字典的值从大到小排序
		sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
	#返回次数最多的类别,即所要分类的类别
	return sortedClassCount[0][0]  

#             文理科生

dataSet=np.array([[250,100],[270,120],[111,230],[130,260],[200,80],[70,190]])
labels=["理科生","理科生","文科生","文科生","理科生","文科生"]
inX=[105,210]
print(classify0(inX,dataSet,labels,3))

#3.数据加载和处理


def img2vector(filename):
	returnVect = np.zeros((1, 1024)) #创建1x1024零向量
	fr = open(filename) 
	for i in range(32): 
		lineStr = fr.readline()
		for j in range(32): #每一行的前32个元素依次添加到returnVect中
			returnVect[0, 32*i+j] = int(lineStr[j])
	return returnVect #返回转换后的1x1024向量

np.set_printoptions(threshold=np.inf) #设置输出不省略
print(img2vector("D:\\课程上的一些文件夹\\机器学习II\\实验\\实验数据集\\实验六KNN\\kNN_hand_writing\\trainingDigits\\1_1.txt"))

def loadTrainData():
	hwLabels = []  #测试集的Labels
	trainingFileList = listdir('D:\\课程上的一些文件夹\\机器学习II\\实验\\实验数据集\\实验六KNN\\kNN_hand_writing\\trainingDigits') #返回trainingDigits目录下的文件名
	m = len(trainingFileList) #返回文件夹下文件的个数
	trainingMat = np.zeros((m, 1024)) #初始化训练的Mat矩阵,测试集
	for i in range(m): #从文件名中解析出训练集的类别
		fileNameStr = trainingFileList[i] #获得文件的名字
		classNumber = int(fileNameStr.split('_')[0])#获得分类的数字
		hwLabels.append(classNumber)#将获得的类别添加到hwLabels中
        #将每一个文件的1x1024数据存储到trainingMat矩阵中
		trainingMat[i,:] = img2vector('D:\\课程上的一些文件夹\\机器学习II\\实验\\实验数据集\\实验六KNN\\kNN_hand_writing\\trainingDigits/%s' % (fileNameStr))
	return hwLabels,trainingMat

print(loadTrainData())

#4.KNN手写识别
#手写数字识别系统测试代码
def handwritingClassTest():
    #样本数据的类标签列表
    hwLabels = []
    #样本数据文件列表
    trainingFileList = os.listdir("kNN_hand_writing/trainingDigits")
    #得到文件行数
    m = len(trainingFileList)
    #初始化样本数据矩阵
    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('kNN_hand_writing/trainingDigits/%s' %(fileNameStr))
    #读取测试数据
    testFileList = os.listdir('kNN_hand_writing/testDigits')
    #初始化错误率
    errorCount = 0.0
    mTest = len(testFileList)
    errfile = []
    #循环测试每个测试数据文件
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        #提取数据向量
        vectorUnderTest = img2vector('kNN_hand_writing/testDigits/%s' %(fileNameStr))
        #对数据文件进行分类
        classifierResult = classify0(vectorUnderTest,trainingMat,hwLabels ,3)    #传值k=3
        #输出k-近邻算法分类结果和真实的分类
        print('分类返回结果: %d,真实结果:%d' %(classifierResult,classNumStr))
        #判断k-近邻算法是否准确
        if(classifierResult != classNumStr):
            errorCount +=1.0
            errfile.append(fileNameStr)
    print('\n 错误总数: %d' %(errorCount))          #错误的总数
    print('错误的是:%s ;' %[i for i in errfile])
    print('\n 总错误率: %f' %(errorCount/float(mTest)))   #总错误率



handwritingClassTest()












基于sklean

# -*- coding: utf-8 -*-
"""
Created on Sat May 14 18:03:12 2022

@author: 86152
"""

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat May 14 14:04:43 2022

@author: yanxiaoyu
"""

import numpy as np
import operator
from os import listdir


def classify0(inX, dataSet, labels, k):
    m = dataSet.shape[0]
    diffMat = np.tile(inX,(m,1))-dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances * 0.5
    sortedDistIndices = distances.argsort()
    #print(sortedDistIndices)
    classCount = {}
    for i in range(k):
        if labels[sortedDistIndices[i]] not in classCount.keys():
            classCount[labels[sortedDistIndices[i]]] = 1
        else:
            classCount[labels[sortedDistIndices[i]]] +=1
        #print(classCount)
        sort = sorted(classCount.items(),key=lambda x:x[1],reverse=True)
    return sort[0][0]

def img2vector(filename):
	returnVect = np.zeros((1, 1024)) #创建1x1024零向量
	fr = open(filename) 
	for i in range(32): 
		lineStr = fr.readline()
		for j in range(32): #每一行的前32个元素依次添加到returnVect中
			returnVect[0, 32*i+j] = int(lineStr[j])
	return returnVect #返回转换后的1x1024向量

def loadTrainData():
	hwLabels = []  #测试集的Labels
	trainingFileList = listdir('trainingDigits') #返回trainingDigits目录下的文件名
	m = len(trainingFileList) #返回文件夹下文件的个数
	trainingMat = np.zeros((m, 1024)) #初始化训练的Mat矩阵,测试集
	for i in range(m): #从文件名中解析出训练集的类别
		fileNameStr = trainingFileList[i] #获得文件的名字
		classNumber = int(fileNameStr.split('_')[0])#获得分类的数字
		hwLabels.append(classNumber)#将获得的类别添加到hwLabels中
        #将每一个文件的1x1024数据存储到trainingMat矩阵中
		trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
	return hwLabels,trainingMat


from sklearn.neighbors import KNeighborsClassifier as kNN
def handwritingClassTest():
    errorCount = 0.0  
    hwLabels,trainingMat=loadTrainData()
    testFileList = listdir('testDigits') 
    mTest = len(testFileList) #测试数据的数量
	#sklearn库使用
    neigh=kNN(n_neighbors=3,algorithm='auto')  #'auto' 将尝试根据传递给fit方法的值来决定最合适的算法。
    neigh.fit(trainingMat,hwLabels)
    for i in range(mTest):
        fileNameStr = testFileList[i] #获得文件的名字
        classNumber = int(fileNameStr.split('_')[0])#获得分类的数字
        vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
        classifierResult = neigh.predict(vectorUnderTest)
        print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0	
    print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest))

if __name__ == "__main__":
    #handwritingClassTest2();
    handwritingClassTest();








 

你可能感兴趣的:(python,机器学习,sklearn)