KNN算法-手写识别

来源:互联网 发布:二级c语言报名时间 编辑:程序博客网 时间:2024/05/02 01:41

一:KNN算法介绍

1.1 简介

KNN算法即k最近邻分类算法,是机器学习的一种。从训练样本集中选取k个与测试样本“距离”最近的样本,这k个样本中出现频率最高的类别作为该测试样本的类别。

1.2 要求

目标:分类未知类别的案例。
输入:待分类未知类别案例项目(测试集),已知类别案例集合D(训练集)。
输出:未知类别案例的可能分类。

1.3 步骤

下面是一个常见的一个二分类示意图。
这里写图片描述
其中已知类别为三角形与正方形,圆点为待分类的案例。
分类过程如下:
1.首先确定k值,即待分类样本用多少邻居进行估计。以上述问题为例,分别选取k为3和5 。
2.确定合适的距离度量公式(如:欧氏距离),然后根据一个待分类样本点与所有已知类别的样本点中,找出待分类样本点中最近的k个邻居。
3.统计这k个样本点的类别,选取类别最多的一类作为待分类样本点的类别。以上述问题为例,当k=3时圆点为三角形,当k=5时,圆点为正方形。

1.4 优缺点

优点:算法实现简单,不需要参数估计,不需要事先训练模型。
缺点:直到分类时才开始训练,运算量大,而且训练样本存储在本地,内存开销大。

1.5 注意点

k的选取一般不大于20.

二:手写识别过程

2.1 numpy库介绍

numpy库中有两种基本数据类型,分别是矩阵和数组array。

shape()查看矩阵或者数组的维度
>>>shape(array) 若矩阵有m行n列,返回(m,n)
>>>array.shape[0] 返回m,若下标为1,返回n
tile()构造数组
>>>tile(A,(m,n)) 将数组A作为元素构造一个m行n列的数组
sum()
>>>array.sum(axis=1)按行累加,axis=0为按列累加
argsort() 返回矩阵中每一个元素原来的下标号
>>>A=array.argsort() A[0]表示排序后 排在第一个的那个数在原来数组中的下标
min()、max()
>>>array.min(0) 返回一个数组,数组中每个数都是它所在列的所有数的最小值
>>>array.min(1) 返回一个数组,数组中每个数都是它所在行的所有数的最小值
split()切割字符串
>>>string.split(‘str’)以字符str为分隔符切片,返回list

2.2 实现“手写识别

这里写图片描述
每一个数字图片都预先被处理为32*32二进制文件。
1.将每一个图片(即txt文本)转化为一个向量,即将32*32的数组转换为1 *1024的数组。这个1*1024的数组在ML中即一个特征向量。
2.训练数据中一共有10*10个文件,将其合并为10*1024的矩阵,每一行对应一个图片。(这是为了方便计算,很多机器学习算法在计算的时候采用矩阵运算,可以简化代码,有时还可以减少计算复杂度)。
3.测试样本中有10*5个图片,我们要让程序自动判断每个图片所表示的数字。同样的,对于测试图片,将其转化为1*1024的向量,然后计算它与训练样本中各个图片的“距离”(这里两个向量的距离采用欧式距离),然后对距离排序,选出较小的前k个,因为这k个样本来自训练集,是已知其代表的数字的,所以被测试图片所代表的数字就可以确定为这k个中出现次数最多的那个数字。

2.3 代码实现

第一步,转化特征向量

#输入二进制文件def img2vector(filename):    returnVect = zeros((1,1024))    with open(filename) as file:        for i in range(32):            line = file.readline()            for j in range(32):                returnVect[1,32*i+j] = int(line[j])    return returnVect

第二步,组合所有的训练集,同时对测试集进行样本分类。

def handWritingClassTest():    #组合所有的训练集    #所有已有的类别集合    handLabels = []    #将每一个文件的文件名存储在一个列表中    trainingFileList = listdir('traingDigits')    m = len(trainingFileList)    trainingMat = zeros((m,1024))    #循环对每一个数字文件进行处理    for i in range(m):        fileNameStr = trainingFileList[i]        #切除后缀名        fileName = fileNameStr.split('.')[0]         #提取类别名        classNumStr = int(fileName.split('_')[0])        handLabels.append(classNumStr)        #采用格式化参数        trainingMat[i,:] = img2vector('trainDigits/%s' % fileNameStr)    #对测试集进行分类    testFileList = listdir('testDigits')    errorCount = 0.0    mTest = len(testFileList)    for i in range(mTest):        fileNameStr = testFileList[i]        fileName = fileNameStr.split('.')[0]        classNumStr = int(fileName.split('_')[0])        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)        classifierResult = classify(vectorUnderTest, trainingMat, handLabels, 3)        print "The classifier came back with: %d, and the real answer is: %d" % (classifierResult,classNumStr)        if(classifierResult != classNumStr ):            errorCount += 1.0        print "\nThe total number of errors is: %d" % errorCount        print "\nThe total error rate is %f" % (errorCount/float(mTest))

分类函数:

#testData为测试向量;trainData为训练数据;label为训练数据对应的标签;k为近邻数def classify(testData, trainData, label, k):    #记录训练集数据个数    trainNum = trainData.shape[0]    #diffMat的每一行为该测试向量与训练数据的差异值,每一行中的每一个元素对应一个维度上的差异    diffMat = tile(testData,(trainNum,1)) - trainData    sqDiffMat = diffMat ** 2    #axis=1为按行累加,然后开方,即最终的欧式距离    sqDistances = sqDiffMat.sum(axis=1)    distances = sqDistances ** 0.5    #获取排序后元素在原数组中的下标列表,即sortDistIndicies[0]为排序后第一个元素在原来的序列中的下标    sortDistIndicies = distances.argsort()    #统计k个相似值中的类别数    classCount = {}    for i in range(k):        getLabel = label(sortDistIndicies[i])        classCount[getLabel] = classCount.get(getLabel,0)+1    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)     #返回最多的label    return sortedClassCount[0][0]