机器学习第二章 k-近邻算法

1.概述

  • 简介:k-近邻算法(k-Nearest Neighbors),是一种基本的分类和回归算法。

  • 分类原理:通过测量不同特征值之间的距离进行分类。具体为:将新数据的信息与样本集中数据的特征进行比对,然后选择k个最近邻个数据中出现次数最多的标签作为新数据的标签。前面提到的距离通过欧式距离公式可得,如式1所示
    d = ( x A 0 − x B 0 ) 2 + ( x A 1 − x B 1 ) 2 (1) d = \sqrt{(xA_0-xB_0)^2+(xA_1-xB1)^2}\tag1 d=(xA0xB0)2+(xA1xB1)2 (1)
    直接理解为计算出两点间的距离

  • 举例:假设测定一块牛排是否为合格品

    import numpy as np
    import matplotlib.pyplot as plt
    
    # 定义牛排样品的特征以及是否合格,y轴为1表示合格,为0表不合格
    X_train = np.array(
        [[2, 5], [3, 4], [4, 7], [6, 1], [7, 2], [8, 4], [1, 3], [2, 6], [3, 8], [5, 2], [6, 3], [7, 5], [8, 6], [9, 3],
         [10, 4], [11, 6], [12, 2], [13, 3], [14, 5], [15, 4]])
    y_train = np.array([1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1])
    
    
    # 定义k-近邻算法
    def knn(X_train, y_train, x, k):
        distances = np.sqrt(np.sum((X_train - x)**2, axis=1))  # 计算欧式距离
        nearest = np.argsort(distances)[:k]  # 找到距离最近的k个样本的索引
        y = np.bincount(y_train[nearest]).argmax()  # 统计k个样本中出现最多的标签
        return y
    
    
    # 绘制散点图
    def showPic(x, y):
        plt.scatter(x[:, 0], x[:, 1], c=y)
        plt.xlabel('Feature 1')
        plt.ylabel('Feature 2')
        plt.savefig('./2.1.png', dpi=300)
        plt.show()
    
        
    if __name__ == '__main__':
        # 输入牛排的属性值
        x = np.array([10, 5])
    
        # 使用k-近邻算法判断牛排是否为合格品
        y = knn(X_train, y_train, x, k=3)
        if y == 1:
            print("这是一块合格的牛排")
        else:
            print("这是一块不合格的牛排")
        showPic(X_train, y_train)
    

    二维离散点图在这里插入图片描述
    三维点图
    在这里插入图片描述

    分析:用以训练的数据集有20个具有两个特征的样本数据(y值为0表示不合格,为1表合格),k值设置为3(表示选取与测试样本距离最近的3个样本),若距离最近的这3个样本中有至少2个合格品,则测试样本也被判定为合格品,反之不合格。knn函数中,计算出对应的欧氏距离,用argsort函数找出最近的k个点的索引,bincount函数计算出这k个点中每个类别出现的次数,返回出现次数最多的那个样本。最后的结果显示,y的值为0,测试的牛排是不合格品。
    在这里插入图片描述

算法实现过程:计算距离,从小到大排序,选取前k的点,将点与其类别一一对应,取频数最大者为预测结果

准备数据:因为算法用到“欧氏距离”,如果样本存在特征的值特别大或特别小,将导致计算结果有很大出入,因此需要考虑是否要对数据做归一化处理,量子力学里常用的归一化方法之一如式2所示
n e w D a t a = ( o l d D a t a − m i n ) / ( m a x − m i n ) (2) newData = (oldData-min)/(max-min)\tag2 newData=(oldDatamin)/(maxmin)(2)
这种方法对数据做归一化处理后能将数值转换为[0,1]间的值,虽然提高了算法复杂度,但是能很大程度地提高准确性

优点:容错率高,不易受坏值影响,简单易懂好实现,能有效实现分类

缺点:计算量非常大,且需要因地制宜设置k值

2.识别手写数字

使用到书本提供的数据集,内含约2千个例子,形成的数字为0-9,每个数字样本约200个

先将一副32x32的二进制图像转为1x1024的numpy数组,打开给定的文件,循环读出文件的前32行,将每行的前32个字符转换为一个长度为32的一维数组中,并返回这个数组。

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

实例:手写数字识别算法

先设置一个空的测试集hwLabels,用listdir读取指定文件夹下所有文件名,记录下文件数量m,将测试集矩阵的所有元素置为0。创建一个for循环,读取所有文件之名称,并将其拆分得到样本所表征的数字,所有的文件均是诸如0_0.txt5_99.txt格式命名,他们分别表示:字符为0的第0个样本,字符为5的第99个样本(我们主要是要获得这些文件所表征的字符而非顺序),将这些字符逐一导入测试集hwLabels,再依次把各个文件里的数据存储进测试集矩阵。自此,测试集初始化完毕。

下一步要用到k-近邻算法,构建一个k-NN分类器,创建一个实例对象neigh,‘k值’设置为3,表示每次找距离最近的三个样本;使用fit()拟合训练数据,参数是刚才所有填充好的测试矩阵和他们对应的字符;然后就可以开始测试算法的准确性,通过一个测试样本集分类后得到的预测结果,计算出算法出错的概率。

def handwritingClassTest():
    # 测试集的Labels
    hwLabels = []
    # 返回trainingDigits目录下的文件名
    trainingFileList = listdir('train')
    # 返回文件夹下文件的个数
    m = len(trainingFileList)
    # 初始化训练的Mat矩阵,测试集
    trainingMat = np.zeros((m, 1024))
    # 从文件名中解析出训练集的类别
    for i in range(m):
        # 获得文件的名字
        fileNameStr = trainingFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 将获得的类别添加到hwLabels中
        hwLabels.append(classNumber)
        # 将每一个文件的1x1024数据存储到trainingMat矩阵中
        trainingMat[i, :] = img2vector('train/%s' % (fileNameStr))
    # 构建kNN分类器
    neigh = kNN(n_neighbors=3, algorithm='auto')
    # 拟合模型, trainingMat为测试矩阵,hwLabels为对应的标签
    neigh.fit(trainingMat, hwLabels)
    # 返回testDigits目录下的文件列表
    testFileList = listdir('test')
    # 错误检测计数
    errorCount = 0.0
    # 测试数据的数量
    mTest = len(testFileList)
    # 从文件中解析出测试集的类别并进行分类测试
    for i in range(mTest):
        # 获得文件的名字
        fileNameStr = testFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 获得测试集的1x1024向量,用于训练
        vectorUnderTest = img2vector('test/%s' % (fileNameStr))
        # 获得预测结果
        # classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        classifierResult = neigh.predict(vectorUnderTest)
        # print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0
    print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount / mTest * 100))

在这里插入图片描述

3.小结

k-NN的优点是容易理解,训练原理简单,但存在计算复杂度过高存储开销大等问题,它需要计算每个测试样本与所有训练样本之间的距离,当样本集很大时,计算量也会变得很大;并且,训练样本需要存储各自的“距离”,“邻居”。书上有一个“约会系统”的例子,里面存在如下两个参数,“飞行里程数”,“每周消费的冰淇淋公升数”,前者数值至少为几百,后者的值在[0,1.5]这个区间,k-NN默认样本的所有特征对欧氏距离计算的贡献相同,如果没有对数据做归一化处理的话,会导致“冰淇淋消耗”这一特征几乎被抹去,样本的判断基本取决于“飞行里程数”,导致算法测试样本的准确率降低

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值