机器学习——KNN回归算法(机器学习实战)

1,646 阅读7分钟

「这是我参与11月更文挑战的第11天,活动详情查看:[2021最后一次更文挑战] (juejin.cn/post/702364… "juejin.cn/post/702364…

本文将详细讲解KNN算法中的度量问题,以便深刻了解KNN如何实现回归问题。

KNN算法

(机器学习实战) K-近邻算法采用测量不同特征值之间的距离方法进行分类。 工作原理: 已有一个样本数据集,即训练集,并且训练集的每个数据都有其对应的分类标签。输入没有标签的新数据后,将新数据的每个特征与样本集中的数据对应的特征进行比较,然后算法提取样本集中特征最相似的数据的分类标签。也就是看最相近的k个数据的标签,通过多数表决的方式进行。

距离度量选择

欧氏距离(Euclidean distance):欧几里得度量(Euclidean Metric)(也称 欧氏距离)是一个通常采用的距离定义,指在𝑚维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)。在二维和三维空间中的欧氏距离就是两点之间的实际距离。

距离公式为: image.png

曼哈顿距离(Manhattan distance):想象你在城市道路里,要从一个十字路口开车到另外一个十字路口,驾驶距离是两点间的直线距离吗?显然不是,除非你能穿越大楼。实际驾驶距离就是这个“曼哈顿距离” 。而这也是曼哈顿距离名称的来源,曼哈顿距离也称为城市街区距离(City Block distance)。

距离公式为: image.png

切比雪夫距离(Chebyshev distance):二个点之间的距离定义是其各坐标数值差绝对值的最大值。 国际象棋棋盘上二个位置间的切比雪夫距离是指王要从一个位子移至另一个位子需要走的步 数。由于王可以往斜前或斜后方向移动一格,因此可以较有效率的到达目的的格子。

距离公式为: image.png

闵可夫斯基距离(Minkowski distance):𝑝取1或2时的闵氏距离是最为常用的 𝑝 = 2即为欧氏距离, 𝑝 = 1时则为曼哈顿距离。 当𝑝取无穷时的极限情况下,可以得到切比雪夫距离。

image.png

汉明距离(Hamming distance):汉明距离是使用在数据传输差错控制编码里面的,汉明距离是一个概念,它表示两个(相同长度)字对应位不同的数量,我们以表示两个字之间的汉明距离。对两个字符串进行异或运算,并统计结果为1的个数,那么这个数就是汉明距离。

距离公式为: image.png

余弦相似度:两个向量有相同的指向时,余弦相似度的值为1;两个向量夹角为90°时,余弦相似度的值为0;两个向 量指向完全相反的方向时,余弦相似度的值为-1。 假定𝐴和𝐵是两个𝑛维向量,𝐴是 𝐴1, 𝐴2, … , 𝐴𝑛 ,𝐵是 𝐵1,𝐵2, … , 𝐵𝑛 ,则𝐴和𝐵的夹角的余弦等于:

image.png

knn选择算法

kd树划分:

KD树(K-Dimension Tree),也可称之为K维树 ,可以用更高的效率来对空间进行划分,并且其结构非常适合寻找最近邻居和碰撞检测。 假设有 6 个二维数据点,构建KD树的过程: 𝐷 ={ (2,3), (5,7), (9,6), (4,5), (6,4), (7,2)}。

1、从𝑥轴开始划分,根据𝑥轴的取值2,5,9,4,6,7 得到中位数为6 ,因此切分线为:𝑥 = 6 。

2、可以根据𝑥轴和𝑦轴上数据的方差,选择方差最大 的那个轴作为第一轮划分轴。

左子空间(记做 𝐷1)包含点 (2,3),(4,5),(5,7),切分轴轮转,从𝑦轴开始划分,切分线为:𝑦 = 5 。

右子空间(记做 𝐷2)包含点 (9,6),(7,2),切分轴轮转,从𝑦轴开始划分,切分线为:𝑦 = 6 。

3、 𝐷1的左子空间(记做𝐷3)包含点(2,3),切分轴轮转,从x轴开始划分,切分线为:𝑥 = 2。

其左子空间记做 𝐷7 ,右子空间记做 𝐷8 。由于 𝐷7 ,𝐷8都不包含任何点,因此对它们不再继续拆分。

𝐷1 的右子空间(记做 𝐷4)包含点(5,7),切分轴轮转 ,从x 轴开始划分,切分线为:𝑥 = 5。

其左子空间记做 𝐷9,右子空间记做 𝐷10 。由于𝐷9 ,𝐷10都不包含任何点,因此对它们不再继续拆分。

4、 𝐷2的左子空间(记做𝐷5)包含点(7,2),切分轴轮转,从x轴开始划分,切分线为:𝑥 = 7。

其左子空间记做 𝐷11,右子空间记做 𝐷12 。 由于𝐷11,𝐷12 都不包含任何点,因此对它们不再继续拆分。

𝐷2的右子空间(记做 𝐷6)不包含任何点,停止继续拆分。

流程图如下:

图片.png

kd树搜索

1.首先要找到该目标点的叶子节点,然后以目标点为圆心,目标点到叶子节点的距离为半径,建立一个超球体,我们要找寻的最近邻点一定是在该球体内部。

搜索(4,4)的最近邻时。首先从根节点(6,4)出发 ,将当前最近邻设为(6,4),对该KD树作深度优先遍历。以(4,4)为圆心,其到(6,4)的距离为半径 画圆(多维空间为超球面),可以看出(7,2)右侧 的区域与该圆不相交,所以(7,2)的右子树全部忽略。

2、返回叶子结点的父节点,检查另一个子结点包含的 超矩形体是否和超球体相交,如果相交就到这个子节 点寻找是否有更加近的近邻,有的话就更新最近邻。

接着走到(6,4)左子树根节点(4,5),与原最近 邻对比距离后,更新当前最近邻为(4,5)。以(4,4)为圆心,其到(4,5)的距离为半径画圆 ,发现(6,4)右侧的区域与该圆不相交,忽略该侧所有节点,这样(6,4)的整个右子树被标记为已忽略。

3、 如果不相交直接返回父节点,在另一个子树继续搜索最近邻。

4、当回溯到根节点时,算法结束,此时保存的最近邻节点就是最终的最近邻。 遍历完(4,5)的左右叶子节点,发现与当 前最优距离相等,不更新最近邻。 所以(4,4)的最近邻为(4,5)。

图片.png

KNN回归

使用knn算法实现回归

对新的样本,根据其𝑘个最近邻的训练样本标签值的均值作为预测值。

实现手写数字分类测试

使用sklearn库中封装的knn算法实现。 手写数字图片如下:

image.png

image.png

import numpy as np
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as KNN

"""
函数说明:将32x32的二进制图像转换为1x1024向量
"""


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


"""
函数说明:手写数字分类测试
"""


def handwritingClassTest():
    # 训练集的Labels
    hwLabels = []
    # 返回trainingDigits目录下的文件名
    trainingFileList = listdir('trainingDigits')
    # 返回文件夹下文件的个数
    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('trainingDigits/%s' % (fileNameStr))
    # 构建kNN分类器
    neigh = KNN(n_neighbors=3, algorithm='kd_tree')
    # 拟合模型, trainingMat为训练矩阵,hwLabels为对应的标签
    neigh.fit(trainingMat, hwLabels)
    # 返回testDigits目录下的文件列表
    testFileList = listdir('testDigits')
    # 错误检测计数
    errorCount = 0.0
    # 测试数据的数量
    mTest = len(testFileList)
    # 从文件中解析出测试集的类别并进行 分类测试
    for i in range(mTest):
        # 获得文件的名字
        fileNameStr = testFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 获得测试集的1x1024向量,用于训练
        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 * 100))


"""
函数说明:main函数
"""
if __name__ == '__main__':
    handwritingClassTest()

使用算法为kd——树。

预测结果为:

image.png