【机器学习】快速入门K近邻算法

本文详细介绍了KNN算法的工作原理,包括K值选择、距离度量(如欧氏距离和闵可夫斯基距离)以及决策规则(多数表决法和加权平均)。还展示了KNN在分类和回归预测中的应用,以及编程实现中的关键步骤,包括KDTree的使用。文章最后讨论了KNN算法的优点和缺点,如计算复杂度和内存需求。
摘要由CSDN通过智能技术生成

KNN直观解释

image.png

KNN算法原理

K近邻算法(KNN).png
• K近邻(K-nearst neighbors, KNN)是一种基本的机器学习算法,所谓k近邻,就是 k 个最近的邻居的意思,说的是每个样本都可以用它最接近的 k 个邻居来代表。比如:判断一个人的人品,只需要观察与他来往最密切的几个人的人品好坏就可以得出,即“近朱者赤,近墨者黑”;KNN算法既可以应用于分类应用中,也可以应用在回归应用中。
• KNN在做回归和分类的主要区别在于最后做预测的时候的决策方式不同。KNN在分类预测时,一般采用多数表决法;而在做回归预测时,一般采用平均值法
• 1. 从训练集合中获取K个离待预测样本距离最近的样本数据;
• 2. 根据获取得到的K个样本数据来预测当前待预测样本的目标属性值。
image.png

一个案例了解KNN

image.png

  • 我们标记电影的类型:爱情片、动作片

  • 每个电影有两个特征属性:打斗镜头、接吻镜头

  • 预测一个新的电影的电影类型

  • 第一步:将训练集中的所有样例画入坐标系,也将待测样例画入

image.png

  • 第二步:计算待测分类的电影与所有已知分类的电影的欧氏距离

image.png

  • 第三步:这些电影按照距离升序排序,取前k个电影,假设k=3,那么我们得到的电影依次是 《He’s Not Really Into Dudes》、《Beautiful Woman》和《California Man》。 而这三部电影全是爱情片,因此我们判定未知电影是爱情片。

KNN三要素

image.png
在KNN算法中,非常重要的三个因素:
K值的选择、距离的度量、决策规则
K值的选择:对于K值的选择,一般根据样本分布选择一个较小的值,然后通过交叉验证来选择一个比较合适的最终值;当选择比较小的K值的时候,表示使用较小领域中的样本进行预测,训练误差会减小,但是会导致模型变得复杂,容易过拟合;当选择较大的K值的时候,表示使用较大领域中的样本进行预测,训练误差会增大,同时会使模型变得简单,容易导致欠拟合;
距离的度量:一般使用欧氏距离(欧几里得距离);

闵可夫斯基距离本身不是一种距离,而是一类距离的定义。对于n维空间中的两个点x(x1,x2,…xn和y(y1,y2,……yn),x和y之间的闵可夫斯基距离可以表示为:

d x y = ∑ k = 1 n ( x k − y k ) p q d_{xy}=\sqrt[q]{\sum_{k=1}^{n}(x_{k}-y_{k})^p} dxy=qk=1n(xkyk)p

其中,p是一个可变参数:
当p=1时,被称为曼哈顿距离
当p=2时,被称为欧氏距离
当p= ∞ \infin 时,被称为切比雪夫距离

决策规则:在分类模型中,主要使用多数表决法或者加权多数表决法;在回归模型中,主要使用平均值法或者加权平均值法。

KNN分类预测规则

  • 在KNN分类应用中,一般采用多数表决法或者加权多数表决法。
  • 多数表决法:每个邻近样本的权重是一样的,也就是说最终预测的结果为出现类别最多的那个类,比如右图中蓝色正方形的最终类别为红色;
  • 加权多数表决法:每个邻近样本的权重是不一样的,一般情况下采用权重和距离成反比的方式来计算,也就是说最终预测结果是出现权重最大的那个类别;比如右图中,假设三个红色点到待预测样本点的距离均为2,两个黄色点到待预测样本点距离为1,那么蓝色圆圈的最终类别为黄色。

image.png

归一化(Normalization)是指将数据按比例缩放,使之落入特定的范围。在数据处理和机器学习中,归一化是一种常用的数据预处理技术,它可以将不同单位或不同量级的数据转化为统一的范围,以便更好地进行比较和分析。

KNN回归预测规则

image.png

  • 在KNN回归应用中,一般采用平均值法或者加权平均值法。
  • 平均值法:每个邻近样本的权重是一样的,也就是说最终预测的结果为所有邻近样本的目标属性值的均值;比如右图中,蓝色圆圈的最终预测值为:2.6;
  • 加权平均值法:每个邻近样本的权重是不一样的,一般情况下采用权重和距离成反比的方式来计算,也就是说在计算均值的时候进行加权操作;比如右图中,假设上面三个点到待预测样本点的距离均为2,下面两个点到待预测样本点距离为1,那么蓝色圆圈的最终预测值为:2.43。(权重分别为: 1/7和2/7)

编程——经典面试题KNN的实现

使用Python的二维List对KNN进行实现(使用等权投票),然后对未知影片的类型进行预测
image.png
训练KNN模型需要进行特征标准化吗?需要

import sys

import numpy as np
import pandas as pd

from sklearn.neighbors import KDTree
from sklearn.metrics import accuracy_score

"""
简单实现一下等权分类  封装成KNN类
KNN计算待预测样本和训练数据

在' __init__ '方法中,我们用' k '的值初始化KNN分类器,k表示要考虑的最近
邻居的数量。“拟合”方法用于训练KNN分类器。它接受训练数据' x '(特征)和' y '
(标签)。该方法将数据转换为numpy数组并分别保存为' train_x '和' train_y '。
如果' with_kd_tree '标志被设置为' True ',它也会使用训练数据构建一个KDTree。
' fetch_k_neighbors '方法用于检索给定样本' x '的' k '个最近邻居。如果' with_kd_tree
'标志为' True ',则它使用KDTree来有效地查找最近的邻居。否则,它计算样本和所有训练
样本之间的距离,并返回“k”个最近的邻居。' predict '方法用于预测给定样本' x '的类。
它使用' fetch_k_neighbors '方法检索最近的邻居,然后使用多数投票来确定预测的类。
它返回预测的类标签。最后,有一个KNN分类器的示例用法。我们创建一个k=3的KNN类的实例,
用一些训练数据对它进行训练,然后用它来预测新样本的类。我希望这个解释对你有帮助!如果你还有
任何问题,请告诉我。
"""


class KNN:
    """
    KNN的步骤:
    1. 从训练集合中获取K个离待测样本距离最近的样本数据
    2. 根据获取得到的K个样本数据来预测当前待遇测样本的目标属性值
    """

    def __init__(self, k, with_kd_tree=True):
        self.k = k
        self.with_kd_tree = with_kd_tree
        self.kd_tree = None
        self.train_x = None
        self.train_y = None

    def fit(self, x, y):
        """
        fit  训练模型  保存训练数据
        如果with_kd_tree=True 则训练构建kd_tree
        :param x: 训练数据的特征矩阵
        :param y: 训练数据的label
        :return:
        """
        # # # 将数据转化为numpy数组的形式
        x = np.asarray(x)
        y = np.asarray(y)
        self.train_x = x
        self.train_y = y
        if self.with_kd_tree:
            self.kd_tree = KDTree(x, leaf_size=10, metric='minkowski')

    def fetch_k_neighbors(self, x):
        """
        # # 1. 从训练集合中获取K个离待预测样本数据;
        # # 2. 根据获取得到的K个样本数据来预测当前待预测样本的目标属性值
        :param x: 当前样本的特征属性x(一条样本)
        :return:
        """
        if self.with_kd_tree:
            # self.kd_tree.query([x], k=self.k, return_distance=True)
            # 返回对应最近的k个样本的下标,如果return_distance=True同时也返回距离
            # print(self.kd_tree.query([x], k=self.k, return_distance=False)[0])

            # 获取对应最近k个样本的标签
            index = self.kd_tree.query([x], k=self.k, return_distance=False)
            # print(index)
            k_neighbors_label = []
            for i in index:
                k_neighbors_label.append(self.train_y[i])
            # print(k_neighbors_label)
            return k_neighbors_label
        else:
            # # 定义一个列表用来村塾每个样本的距离以及对应的标签
            # [[距离1, 标签1], [距离2, 标签2], [距离3, 标签3]...]
            lst_distance = []
            for index, i in enumerate(self.train_x):
                dis = np.sum((np.array(i) - np.array(x)) ** 2) ** 0.5
                lst_distance.append([dis, self.train_y[index]])
            # print(lst_distance)

            # # 按照dis对lstDistance进行排序
            # lst_distance.sort()
            # print(lst_distance)
            # sort_lst_distance = np.sort(lst_distance, axis=1)
            lst_distance.sort()
            k_neighbors_label = np.array(lst_distance)[:self.k, -1]
            # print(sort_lst_distance)
            # print(type(sort_lst_distance))
            #
            # # 获取取前K个最近距离的样本的标签
            # k_neighbors_label = sort_lst_distance[:self.k, -1]
            # print(k_neighbors_label)
            # # 也可以获取前k个最近例句的距离
            # k_neighbors_dis = sort_lst_distance[:self.k, :-1]
            # print(k_neighbors_dis)
            #
            # # 也可以获取前k个最近距离的样本的标签
            # k_neighbors_label = sort_lst_distance[:self.k, -1]
            # print(k_neighbors_label)
            # # 也可以获取前k个最近邻居的距离
            # k_neighbors_dis = sort_lst_distance[:self.k, :-1]
            return k_neighbors_label

    def predict(self, x):
        """
        模型预测
        :param x: 待预测样本的特征矩阵(多个样本)
        :return: 预测结果
        """
        # ## 将数据转化为numpy数组的格式
        X = np.asarray(x)

        # # 定义一个列表接收每个样本的预测结果
        result = []
        for x in X:
            # print(x)
            k_neighbors_label = self.fetch_k_neighbors(x)
            # ## 统计每个类别出现的次数
            y_count = pd.Series(k_neighbors_label).value_counts()
            # print('y_count', y_count)
            # ## 产生结果
            y_ = y_count.idxmax()
            # y_ = y_count.argmax()  # idxmax() 和 argmax功能一样,获取最大值对应的下标索引
            result.append(int(y_))
        return result

    def score(self, x, y):
        """
        模型预测得分:我们使用准确率  accuracy_score
        :param x: 特征矩阵
        :param y: 真实标签
        :return:
        """
        y_true = np.array(y)
        y_pred = self.predict(x)
        print('y_hat', y_pred)  # y的预测值
        return np.mean(y_true == y_pred)
        # return accuracy_score(y_true, y_pred)

    def save_model(self, path):
        """
        存储模型???存储训练数据到文件中
        :param path:
        :return:
        """
        pass

    def load_model(self, path):
        """
        加载模型???从文件中加载训练数据
        :param path:
        :return:
        """
        pass


if __name__ == '__main__':
    T = np.array([
        [3, 104, -1],
        [2, 100, -1],
        [1, 81, -1],
        [101, 10, 1],
        [99, 5, 1],
        [98, 2, 1]])
    X_train = T[:, :-1]
    Y_train = T[:, -1]
    x_test = [[18, 90], [50, 10]]
    knn = KNN(k=5, with_kd_tree=False)
    knn.fit(x=X_train, y=Y_train)
    # print(knn.predict(X_train))
    print(knn.score(x=X_train, y=Y_train))
    # knn.fetch_k_neighbors(x_test[0])
    print('预测结果:{}'.format(knn.predict(x_test)))
    # sys.exit()
    print('----------下面测试一下鸢尾花数据----------')
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split

    X, Y = load_iris(return_X_y=True)
    print(X.shape, Y.shape)
    x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=0)  # 随机数种子
    print(x_train.shape, y_train.shape)
    knn01 = KNN(k=3, with_kd_tree=False)
    knn01.fit(x_train, y_train)
    print(knn01.score(x_train, y_train))
    print(knn01.score(x_test, y_test))

KNN算法实现方法

KNN算法的重点在于找出K个最邻近的点,重要方式有以下几种:

  • 蛮力实现(brute):计算预测样本到所有训练集样本的距离,然后选择最小的k个距离即可得到K个最邻近点。缺点在于当特征数比较多、样本数比较多的时候,算法的执行效率比较低;
  • KD树(kd_tree):KD树算法中,首先是对训练数据进行建模,构建KD树,然后再根据建好的模型来获取邻近样本数据。
  • 除此之外,还有一些从KD_Tree修改后的求解最邻近点的算法,比如:Ball Tree、BBF Tree、MVP Tree等。

KD Tree


KD 树(KD-tree)是一种用于高效查找最近邻的数据结构。它是一种二叉树,每个节点代表一个 k 维空间中的点,并且每个节点都有一个分割超平面,将空间划分为两个子空间

KD Tree是KNN算法中用于计算最近邻的快速、便捷构建方式。当样本数据量少的时候,我们可以使用brute这种暴力的方式进行求解最近邻,即计算到所有样本的距离。但是当样本量比较大的时候,直接计算所有样本的距离,工作量有点大,所以在这种情况下,我们可以使用kd tree来快速的计算。

构造KDtree

KD树采用从m个样本的n维特征中,分别计算n个特征取值的方差,用方差最大的第k维特征nk作为根节点。对于这个特征,选择取值的中位数nkv作为样本的划分点,对于小于该值的样本划分到左子树,对于大于等于该值的样本划分到右子树,对左右子树采用同样的方式找方差最大的特征作为根节点,递归即可产生KD树


给定一个二维空间的数据集 T = {(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T},请给出:特征空间的划分过程、kd树的构造过程。
🏴第一步:选择x(1)轴,6个数据点的x(1)坐标上的数字分别是2,5,9,4,8,7.取中位数7(不是严格意义的中位数,取较大的数),以x(1)=7将特征空间分为两个矩形:
image.png
🏴第二步:选择x(2)轴,处理左子树,3个数据点的x(2)坐标上的数字分别是3,4,7。取中位数4,以x(2)=4将左子树对应的特征空间分为两个矩形;处理右子树,2个数据点的x(2)坐标上的数字分别是6,1。取6,以x=6将右子树对应的特征空间分为两个矩形:
image.png
🏴第三步:x(1)轴,分别处理所有待处理的节点:

KD tree查找最近邻

  1. 从树的根节点开始,沿着KD树向下递归,找到最接近目标点的叶子节点(最后一个节点)
  2. 回溯这条路径,对于每个节点执行以下步骤:
  1. 如果该节点比当前最近邻距离更近,则将该节点设置为最近邻节点
  2. 计算目标点与当前节点所在超矩形的距离,得到当前最近邻距离d_min
  3. 根据步骤1和步骤2,确定需要遍历的子树。如果当前节点为父节点的左子节点,并且当前节点到目标点的距离小于当前最近邻距离,则需要遍历右子树;如果当前节点为父节点的右子节点,并且当前节点到目标点的距离小于当前最近邻距离,则需要遍历左子树
  4. 递归地访问确定的子树,重复执行步骤2和步骤3,直到递归到叶子节点为止

KNN特点


KNN是一种非参的、惰性的算法模型

  1. 非参的意思并不是说这个算法不需要参数,而是意味着这个模型不会对数据做出任何的假设。也就是说 KNN 建立的模型结构是根据数据来决定的,这也比较符合现实的情况,毕竟在现实中的情况往往与理论上的假设是不相符的
  2. 惰性又是什么意思呢?想想看,同样是分类算法,逻辑回归需要先对数据进行大量训练(tranning)最后才会得到一个算法模型。而 KNN 算法却不需要,它没有明确的训练数据的过程,或者说这个过程很快

优点:

  1. 简单易用
  2. 精度高
  3. 对异常值不敏感
  4. 无数据输入假定

**缺点:**计算复杂度高、空间复杂度高

  1. 对内存要求较高,因为该算法存储了所有训练数据
  2. 预测阶段可能很慢
  3. 不相关的功能和数据规模敏感
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值