【基础算法】K-近邻演算法(kNN)(k-NearestNeighbor)概述及Python实践

K-近邻演算法

原理:测量不同特征值之间的距离方法来分类

目标:分类未知类别的案例

优点:算法简单、易于实现、不需要参数估计、不用事先训练、精度高、对异常值不敏感、对输入数据无假定

缺点:计算复杂度高、空间复杂度也高、属于懒惰算法、计算量和内存开销也大

适用数据:数值型和标称型

输入:未知类别的实例

输出:输入实例的类别


工作原理

取一个样本数据集作为训练样本集,并且训练集中的每个数据都有标签,则我们知道了训练集中每一条数据与所属分类的对应关系后,每当输入没有标签的新数据后,将新数据与训练集中的每一条数据进行比较,然后算法返回最相似数据(最近邻)的分类标签,一般来说我们只取训练集中前K个最相似的数据,这就是K-近邻算法的由来。

通常k的取值不大于20-《机器学习实战》。


实践

我尽量将有疑问的地方都注解好
如果有哪里不懂的话可以留言给我

创建一个打好标的训练集

# 科学计算包
from numpy import *
# 运算符模块
import operator

# 创建一个打标的数据集
def createDataSet():
    group = array([[1.0,1,1],
                   [1.0,1.0],
                   [0,0],
                   [0,0.1]])
    labels = ['A','A','B','B']
    return group,labels

kNN伪代码:
(1)透过标签,计算已知的类别数据集中的每个点与当前点之间的距离
(2)按照距离递增次序排序
(3)选取与当前点距离最小的k个点
(4)确定前k个点所在类别出现的频率
(5)返回前k个点出现频率最高的类别作为当前点的预测分类

再来是kNN算法的部分

# kNN分类算法
# inX:输入的向量
# dataSet:训练样本集
# labels:标签
# k:选择最近邻的样本数目
def classify0(inX , dataSet , labels , k):
    # 取得训练集实例个数
    dataSetSize = dataSet.shape[0]
    # 计算距离(这边用欧式距离)
    # tile():将输入的向量复制成dataSetSize组的数组
    diffMat = tile(inX , (dataSetSize,1))-dataSet
    # **:次方,这边**2为取2次方(平方)来去掉负号
    sqDiffMat = diffMat**2
    # 计算平方后的距离差
    sqDistances = sqDiffMat.sum(axis=1)
    # 开根号,与之前的平方配合,取绝对值的效果
    distances = sqDistances**0.5
    
    # 将距离作排列
    # sortedDistIndicies:输入inX与各实例的距离(已按照大小排列)
    sortedDistIndicies=distances.argsort()
    classCount={}
    # 选择距离最小的k个点
    for i in range(k):
        # 将sortedDistIndicies中前k个取出
        voteIlabel = labels[sortedDistIndicies[i]]
        # 从classCount中取得voteIlabel对应的value(标签)
        classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
    # 排序操作:将距离最近的点和标做排序
    # classCount.iteritems():可迭代的对象
    # key:比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
    # reverse:排序规则,reverse = True 降序 , reverse = False 升序(默认)。
    sortedClassCount = sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

kNN的手写数字辨识实例

使用kNN来识别0~9的数字
输入格式:每个手写数字已经事先处理成32*32的二进制文本,存储为txt文件。
0~9每个数字都有10个训练样本,5个测试样本。

再来将分成两个步骤
(1)将每个txt转成一个向量(将32 * 32转成1 * 1024的数组)
这个1* 1024的数组就是机器学习中的特征向量。

训练集中有0~9的数字共10个,每个共10份txt,将每一份txt转换后可组成100 * 1024的矩阵,每一行对应一份txt。(用矩阵运算可以简化代码,有时也可以减少计算复杂度)

(2)测试集有0~9个数字,每个数字有5份txt;同样将测试集转成1*1024的向量后,计算出测试集实例与训练集中每个特征向量的距离(这里是用欧式距离),然后对距离排序,选出距离较小的前k个,而这k个来自打了标的训练集,故这个测试集的实例可以确定为这k个中出现次数最多的那个数字。

第一部分代码:将txt档转成(1*1024)的向量

# img2vector:将txt(32*32)转成(1*1024)的向量
# filename:读取的档名
def img2vector(filename):
    # 声明returnVect为一个(1*1024)的0向量
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        # lineStr:每一行读入
        lineStr = fr.readline()
        for j in range(32):
            # 将returnVect改写成lineStr
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

第二部分代码:将档案读入并鉴别

def handwritingClassTest():
    # 目标类别标签
    hwLabels = []
    
    # 训练集部分
    # 将训练集合并成100*1024的大矩阵
    #os模块中的listdir('str')可以读取目录str下的所有文件名,返回一个字符串列表
    trainingFileList = listdir('trainingDigits')
    # m:目录底下所有文件的个数
    m = len(trainingFileList)
    #加载训练集到大矩阵trainingMat
    trainingMat = zeros((m,1024))
    # 为将训练集写入矩阵对档名作处理
    # 训练集样本的命名格式是'1_1.txt'
    for i in range(m):
        fileNameStr = trainingFileList[i]                  
        # string.split('str')以字符str为分隔符切片,返回list,这里去list[0],得到分隔符前段部分(1_1)
        fileStr = fileNameStr.split('.')[0]                
        # 再以'_'为切片得到1,即类别
        classNumStr = int(fileStr.split('_')[0])  
        # 将类别添加到hwLabels        
        hwLabels.append(classNumStr)
        # 将档案转换成(1*1024的向量)
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    
    # 测试集部分
    # 读入路径下的测试实例(为列表形式)
    testFileList = listdir('testDigits')       
    errorCount = 0.0
    # mtest:测试实例个数
    mTest = len(testFileList)
    for i in range(mTest):
        # 逐个抓出路径下的档案名做处理
        fileNameStr = testFileList[i]
        # 取出标签部分
        fileStr = fileNameStr.split('.')[0]     
        classNumStr = int(fileStr.split('_')[0])
        # vectorUnderTest:将测试集底下的txt转成(1*1024)的向量
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        
        #调用classify0分类
        #vectorUnderTest:测试集
        #trainingMat:训练集
        #hwLabels:标签
        #3:选择最近的3个点
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print ("the classifier came back with: %d, 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)))

结果

在这里插入图片描述
因为训练集和测试集都比较小,所以凑巧没有错误发生的情况。

k-近邻算法必须保存全部数据集,如果训练数据集过大,必须使用大量的存储空间。此外,必须对每条数据计算距离,实际使用上非常耗时。

数据集及详细代码放在GitHub中欢迎参阅、星标,若有疑问欢迎留言讨论。

个人GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值