基于kd树的KNN算法原理以及python实现

k k k近邻算法

算法描述

目标

k k k近邻算法也是一个分类算法,其最终的目的是预测输入的点所属的类别

思想

k k k近邻算法的思想就是将输入的点的类别预测为其周围大多数点的类别,物以类聚

策略

我们在二维的空间下分析策略

如图,有多个实例(点),每个点属于一种类别

在这里插入图片描述

这时我们输入一个需要预测的点,则根据其附近类别最多的点决定将其分为紫色

在这里插入图片描述

算法

根据以上策略,我们不难得出k近邻算法

  • 输入

    训练数据集 T = ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) T={(x_1,y_1),(x_2,y_2),...,(x_N,y_N)} T=(x1,y1),(x2,y2),...,(xN,yN)。其中 x i x_i xi是特征向量, y i y_i yi是实例对应的类别。 y i ∈ Y = { c 1 , c 2 , . . . , c k } y_i\in{Y}=\{c_1,c_2,...,c_k\} yiY={c1,c2,...,ck}

    需要预测的实例特征向量 x x x

  • 输出

    实例 x x x所属的类 y y y

  • 过程

    1. 根据给定的距离度量找到训练集 T T T中与 x x x最临近的 k k k个点,涵盖这 k k k个点的 x x x的邻域记作 N k ( x ) N_k(x) Nk(x)
    2. N k ( x ) N_k(x) Nk(x)中找到个数最多的类别 y y y作为 x x x的类别预测,决策规则如下

    y = a r g m a x c j ∑ x i ∈ N k ( x ) I ( y i = c j ) , i = 1 , 2 , . . . , N ; j = 1 , 2 , . . . , k (1) y=\underset{c_j}{argmax}\sum_{x_i\in{N_k(x)}}I(y_i=c_j),i=1,2,...,N;j=1,2,...,k\tag{1} y=cjargmaxxiNk(x)I(yi=cj),i=1,2,...,N;j=1,2,...,k(1)

这个算法过程中有两个问题

  1. 距离度量是什么
  2. k k k选取几个

距离度量

算法中要找到最邻近的 k k k个点,那么就要有一个距离的度量方法

度量方法有很多种,欧式距离,曼哈顿距离等,这些距离都有一个通式
L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i l − x j l ∣ p ) 1 p (2) L_p(x_i,x_j)=(\sum_{l=1}^n|x_i^{l}-x_j^{l}|^p)^{\frac{1}{p}}\tag{2} Lp(xi,xj)=(l=1nxilxjlp)p1(2)
其中 x i x_i xi x j x_j xj均为特征向量。 x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( n ) ) T x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(n)})^T xi=(xi(1),xi(2),...,xi(n))T

当p=1时,就是曼哈顿距离

当p=2时,就是欧氏距离,也就是我们熟知的直线距离

一般我们选择欧式距离当作度量单位,也就是所谓的直线距离

k的选择

k的选取很重要,不能过大也不能过小

如果k选取过小,则容易被噪声影响。如下,只有与输入实例较近的训练实例会对预测产生影响,如果较劲的实例是噪音,就有可能误分类

在这里插入图片描述

如果k选取过大,这时离输入实例较远的点也对实例产生了影响,有可能产生误差

在这里插入图片描述

一般都先取一个比较小的k值,然后使用交叉验证法选取最优的k值

k d kd kd树优化

引言

由算法描述我们可以知道,实现 k k k近邻算法,我们需要针对训练数据进行快速 k k k近邻搜索,也就是找到离输入点最近的 k k k个点

k k k近邻算法最简单的是线性扫描,计算输入点到每个训练实例的距离,然后比较,但这样训练集大的时候会非常耗时

为了提高这个搜索效率,减少计算距离次数,有很多方法, k d kd kd树就是其中一个

k d kd kd树定义

k d kd kd树是一种对空间中的实例点进行存储以便对其进行快速检索的树形数据结构,是二叉树,表示对 k k k维空间进行划分

这个 k k k不是 k k k临近那个 k k k,而是表示空间的维度

构造 k d kd kd

根据 k d kd kd树的定义,我们很容易得到构造 k d kd kd树的方法

我们一个实例,即特征向量有多个特征,每个特征代表空间的一个维度,一个坐标轴
x = ( x ( 1 ) , x ( 2 ) , . . . , x ( k ) ) T x=(x^{(1)},x^{(2)},...,x^{(k)})^T x=(x(1),x(2),...,x(k))T
构造 k d kd kd树就是不断用垂直于坐标轴的超平面将 k k k维空间进行切分,构成一系列 k k k维超矩形区域, k d kd kd树每个节点对应一个 k k k维度的超矩形区域

算法描述
  • 输入: k k k维空间数据集 T = { x 1 , x 2 , . . . , x N } T=\{x_1,x_2,...,x_N\} T={x1,x2,...,xN}

    其中 x i = ( x i ( 1 ) , x i ( 2 ) , . . . x i ( k ) ) T ,    i = 1 , 2 , . . . N x_i=(x_i^{(1)},x_i^{(2)},...x_i^{(k)})^T,\ \ i=1,2,...N xi=(xi(1),xi(2),...xi(k))T,  i=1,2,...N

  • 输出: k d kd kd

  • 过程

    1. 开始: 构造根节点,根结点对应包含T(所有实例)的K维空间的超矩形区域

      选择 x ( 1 ) x^{(1)} x(1)为坐标轴, T T T中所有实例的 x ( 1 ) x^{(1)} x(1)坐标的中位数作为切分点,用切分点与坐标轴 x ( 1 ) x^{(1)} x(1)垂直的超平面将根节点对应的超矩形区域分成两个区域

      x ( 1 ) x^{(1)} x(1)比切分点的 x ( 1 ) x^{(1)} x(1)小的切到左边,大的切到右边

      落在切分超平面上的点就是根节点

      如果有多余的切分点,那么这里切分点只留一个其他的分到左边或者右边

    2. 重复: 对生成的子节点重复上面的切分步骤,直到划分的两个子区域没有实例

      对深度为 j j j的节点,选 x l x^l xl为切分坐标轴, l = j ( m o d k ) + 1 l=j(modk)+1 l=j(modk)+1

      落在超平面上的节点保存在该节点

构造例子

给定一个二位空间数据集如下,构造一个 k d kd kd
T = { ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 7 ) T , ( 8 , 1 ) T , ( 7 , 2 ) T } T=\{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\} T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}

  1. 首先我们选择 x ( 1 ) x^{(1)} x(1)轴对T进行划分,中位数是7,以点 ( 7 , 2 ) T (7,2)^T (7,2)T作为切分点,沿着垂直于 x ( 1 ) x^{(1)} x(1)轴的超平面将矩形区域分为两个超矩形区域。

    在这里插入图片描述

  2. 对于左边部分,其深度为1,选择 x ( 2 ) x^{(2)} x(2)轴进行划分,中位数是4,点 ( 5 , 4 ) T (5,4)^T (5,4)T作为切分点,此时 ( 5 , 4 ) T (5,4)^T (5,4)T对应了左边整个超矩形

    对应的概念在后面很重要

在这里插入图片描述

  1. 一直划分下去,最后构造的 k d kd kd树结果如下

在这里插入图片描述

用树的形式表示如下

在这里插入图片描述

搜索 k d kd kd

目的

搜索 k d kd kd树,最终目的是为了帮助我们找到 k k k个与输入点邻近的点,但是为了方便理解,这里先讲解一下如何搜索最邻近的点

算法描述-最邻近点
  • 输入:已构造的 k d kd kd树;目标点 x x x

  • 输出: x x x的最邻近点

  • 过程

    1. k d kd kd中找出其对应超矩形区域包含目标点 x x x的叶结点

      从根结点开始,递归向下访问

      k d kd kd树构造时类似,如果目标点当前维度小于切分点的坐标,则移动到左结点,大于则移动到右结点,直到找到叶结点
      在这里插入图片描述

    2. 将找到的叶结点标记为"当前最近点"

      在这片区域内,这个叶结点是与目标节点最近的(叶结点所对应的区域只有一个点,就是他自己)

      但是可能这个区域旁边的区域的结点会比这个结点离目标节点更近,所以该结点是"当前最近点",而不是最近点

    3. 递归向上回退,对于回退到的每个结点

      • 如果该结点保存的实例比"当前最近点"距离目标点更近,则以该点做为"当前最近点"

      • 且由于我们是从一个区域回退到该点,所以还应检查该点另一个子结点对应的区域(如果有的话):

        只需检查另一个子结点对应的区域是否与以目标点为球心、以目标点与"当前最近点"间的距离为半径的超球体相交"

        相交直接用距离比较就行

        如果相交,则有可能在另一个子结点对应的区域内存在距目标点更近的点,需要移动到另一个子结点进行近邻搜索

        如果不相交,则继续向上回退

        因为不相交时,那片区域一定不会有比"当前最近点"更近的点了

      在这里插入图片描述
      在这里插入图片描述

      在这里插入图片描述

    • 回退到根结点时搜索结束,最后的"当前最近点"为 x x x的最近邻点
过一遍例子

上面的算法过程中的图是辅助理解,下面来过一遍例子,训练实例如下,输入是 ( 2.2 , 4 , 5 ) T (2.2,4,5)^T (2.2,4,5)T
T = { ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 7 ) T , ( 8 , 1 ) T , ( 7 , 2 ) T } T=\{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\} T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}

  1. 首先根据划分搜索到第一个结点为 ( 4 , 7 ) T (4,7)^T (4,7)T,将其当作"当前最近点"

在这里插入图片描述

  1. 回退到其父节点 ( 5 , 4 ) T (5,4)^T (5,4)T,其与目标点的距离比"当前最近点"到目标点的距离大,不操作

在这里插入图片描述

  1. 考虑 ( 5 , 4 ) T (5,4)^T (5,4)T的另一个子节点 ( 2 , 3 ) T (2,3)^T (2,3)T,其对应区域与超球体相交,该区域有可能有更近的点,对其进行近邻搜索

在这里插入图片描述

找到比"当前最近点"距离目标点更近的点,更新"当前最近点"

在这里插入图片描述

  1. 回退到点 ( 7 , 2 ) T (7,2)^T (7,2)T,其与目标点的距离比"当前最近点"到目标点的距离大,不操作

    在这里插入图片描述

  2. 检查点 ( 9 , 6 ) T (9,6)^T (9,6)T对应的区域,与超球体不相交,里面不可能有比"当前最近点"更近的点,不搜索

在这里插入图片描述

  1. 回退到根节点,最终得到最近点 ( 2 , 3 ) T (2,3)^T (2,3)T
时间复杂度

由上述算法我们可以发现,若一个节点另一个子区域与超球体不相交,则可以直接省掉一刻子树,大大缩短了搜搜时间

如果实例点随机分布, k d kd kd树的平均搜索复杂度是 O ( l o g N ) O(logN) O(logN)

k d kd kd 树更适用于训练实例数远大于空间维数时的近邻搜索,当空间维数接近训练实例数时,它的效率会迅速几乎接近线性扫描

算法描述- k k k近邻点
思想

在了解了如何寻找最近邻点之后,下面来看看怎么找 k k k个近邻点

一种简单的方法是直接进行k次最邻近搜索,下面看看另一种方法怎么做

其基本思想是,维护一个数组,存放与目标点最相邻的k个点
每次回退到一个结点时:

  • 如果数组没满,那么直接将结点放入数组
  • 如果数组满了,那么用数组中距离目标点最远的结点的到目标点的距离当前结点到目标点的距离比较判断是否用当前结点更新数组
  • 如果数组没满,直接进入该结点的另一个子节点的区域
  • 如果数组满了,则用数组中距离目标点最远的结点的与目标点形成超球体和该节点另一个子节点的区域比较是否相交,相交则进入另一篇区域

    相交说明数组中至少有一个点会被另一片区域中的点替代

最后剩下的数组里的 k k k个点就是答案

算法
  • 输入:已构造的 k d kd kd树;目标点 x x x

  • 输出: x x x k k k个邻近点

  • 过程

    1. k d kd kd中找出其对应超矩形区域包含目标点 x x x的叶结点

    2. 维护一个大小为 k k k的大顶堆,将找到的叶结点插入到堆中

      堆的目的是降低时间复杂度

    3. 递归向上回退,对于回退到的每个结点

      • 检查堆是否已满

        如果此时堆没满,将该结点插入堆中,更新堆;

        如果堆满了,若该结点到目标点的距离比堆顶的点到目标点的距离小,将堆顶从堆中删除,将该结点插入到堆中,更新堆

      • 检查该点另一个子节点对应的区域

        • 如果堆没满,直接移动到另一个子结点进行近邻搜索
        • 如果堆满了,只需检查另一个子结点对应的区域是否与``以目标点为球心、以目标点与堆顶点间的距离为半径的超球体相交"`
          • 如果相交,需要移动到另一个子结点进行近邻搜索
          • 如果不相交,则继续向上回退
    4. 最后得到的堆就是 k k k个近邻的点

例子

训练数据集如下, k k k为2,输入点为 ( 2.1 , 4.7 ) T (2.1,4.7)^T (2.1,4.7)T
T = { ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 7 ) T , ( 8 , 1 ) T , ( 7 , 2 ) T } T=\{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\} T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}

  1. 一开始搜索到 ( 4 , 7 ) T (4,7)^T (4,7)T,堆大小为1,入堆(堆就不画了)

在这里插入图片描述

  1. 回退到其父节点,堆大小为1,未满, ( 5 , 4 ) T (5,4)^T (5,4)T入堆,并更新堆,此时最大距离点改变

在这里插入图片描述

  1. 由于堆已满且超球体与 ( 5 , 4 ) T (5,4)^T (5,4)T的另一个子节点对应区域相交,则到另一个子节点对应的区域搜索;

在这里插入图片描述

发现 ( 2 , 3 ) T (2,3)^T (2,3)T ( 5 , 4 ) T (5,4)^T (5,4)T距离目标点近,更新堆

在这里插入图片描述

  1. 回退到 ( 7 , 2 ) T (7,2)^T (7,2)T,由于堆已满,且 ( 7 , 2 ) T (7,2)^T (7,2)T到目标点的距离大于堆顶到目标点的距离,不操作

在这里插入图片描述

  1. 检查 ( 9 , 6 ) T (9,6)^T (9,6)T对应的区域,由于堆已满且该区域与超球体不相交,不移动到 ( 9 , 6 ) T (9,6)^T (9,6)T区域搜索
    在这里插入图片描述

  2. 至此搜索结束,搜索到的最终结点是 ( 5 , 4 ) T , ( 2 , 3 ) T {(5,4)^T,(2,3)^T} (5,4)T,(2,3)T

python代码实现

kd树代码-k近邻

import queue

import numpy as np
import heapq
import pandas as pd

'''kd树的结点类'''

class KDNode:
    '''
    kd树构造函数
    :param dim: 结点分割的维度
    :param value: 当前结点对应实例
    :param label: 当前结点对应实例的类别
    :param left: 结点左孩子
    :param right: 结点右孩子
    :param dist: 当前结点到目标点的距离
    '''

    def __init__(self, dim, value, label, left, right):
        self.dim = dim
        self.value = value
        self.label = label
        self.left = left
        self.right = right
        self.dist = 1.7976931348623157e+308 # 初始化为最大值,这个不重要,会被覆盖的

    '''反着重写结点的比较函数,用于制造大根堆,因为heapq只能搞小根堆'''
    def __lt__(self, other):
        if self.dist>other.dist:
            return True
        else:
            return False




'''kd树'''


class KDTree:
    '''
    初始化参数并生成kd树
    其中实例和标签(类别)分别输入
    :param values: 实例
    :param labels: 类别
    '''

    def __init__(self, values, labels):
        self.values = np.array(values)
        self.labels = np.array(labels)
        self.dim_len = len(self.values[0]) if len(self.values) > 0 else 0  # 特征向量的维度,命名避免与k近邻的k混淆
        # 创建kd树
        self.root = self.create_KDTree(self.values, self.labels, 0)
        self.k = 0  # knn搜索个数
        self.knn_heap = []  # 临时存放knn结果的堆,注意这里默认是小顶堆,下面要用相反数

    '''
    递归创建kd树
    :param values: 实例
    :param labels: 类别
    :return: 该树的根节点
    '''

    def create_KDTree(self, values, labels, depth):
        if len(labels) == 0:
            return None

        dim = depth % self.dim_len  # 当前划分维度,注意这里不用+1,因为数组从0开始

        # 对实例和类别按实例某特征排序,不懂可参考 http://t.csdn.cn/8ZItF
        sort_index = values[:, dim].argsort()
        values = values[sort_index]
        labels = labels[sort_index]

        mid = len(labels) // 2  # 双除号向下取整
        node = KDNode(dim, values[mid], labels[mid], None, None)
        node.left = self.create_KDTree(values[0:mid], labels[0:mid], depth + 1)  # 递归创建左子树
        node.right = self.create_KDTree(values[mid + 1:], labels[mid + 1:], depth + 1)  # 递归创建右子树
        return node

    '''距离度量,这里使用欧氏距离'''

    def dist(self, p1, p2):
        return np.sqrt(np.sum((p1 - p2) ** 2))

    """
    k近邻搜索的初始化
    主要作用是对搜索进行兜底
    :param target: 目标点
    :param k: 需要搜索近邻点的数量
    :return: 返回找到的实例和实例对应的标签组成的元组
    """

    def search_KNN(self, target, k):
        # 兜底
        if self.root is None:
            raise Exception('KD树不可为空')
        if k > len(self.values):
            raise ValueError('k值需小于等于实例数量')
        if len(target) != len(self.root.value):
            raise ValueError('目标点的维度和实例的维度大小需要一致')

        # 初始化并开始搜索
        self.k = k
        self.knn_heap = []
        self.search_KNN_core(self.root, target)
        res_values = []
        res_labels = []
        # 将结果转换一下
        for i in range(len(self.knn_heap)):
            res_values.append(self.knn_heap[i].value)
            res_labels.append(self.knn_heap[i].label)

        # print(res_labels)
        return (np.array(res_values),np.array(res_labels))

    '''
    k近邻搜索核心逻辑代码,由search_KNN调用
    :param root: 当前便利到的结点
    :param target: 目标点
    '''

    def search_KNN_core(self, node, target):
        if node is None:
            return []

        value = node.value
        dim = node.dim
        # 先往其中一个区域搜索
        if (target[dim] < value[dim]):
            ath_child = node.right  # 另一片区域对应结点
            if node.left is not None:
                self.search_KNN_core(node.left, target)
        else:
            ath_child = node.left  # 另一片区域对应结点
            if node.right is not None:
                self.search_KNN_core(node.right, target)

        # 处理本结点
        node.dist = self.dist(value, target)  # 结算本结点到目标节点的距离
        # 判断是否需要更新堆
        if len(self.knn_heap) < self.k:  # 堆没满直接进堆
            heapq.heappush(self.knn_heap, node)
        else:  # 堆若满则需要判断更新
            fathest_node_in_k = heapq.heappop(self.knn_heap)  # 已经找到的实例中距离目标点最远的实例
            if node.dist < fathest_node_in_k.dist:
                heapq.heappush(self.knn_heap, node)
            else:
                heapq.heappush(self.knn_heap, fathest_node_in_k)

        if ath_child is not None:
            fathest_node_in_k = heapq.heappop(self.knn_heap) # 获取堆顶供下面使用
            heapq.heappush(self.knn_heap,fathest_node_in_k)
            # 如果另一片区域与(以目标点为球心,以搜索到的集合中距离目标点最远的点到目标点的距离为半径)的超球体相交,则进入另一个子节点对应的区域搜索
            # 如果堆没满,也进入另一篇区域
            if len(self.knn_heap) < self.k or abs(ath_child.value[dim] - target[dim]) < fathest_node_in_k.dist:
                self.search_KNN_core(ath_child, target)

    '''先序输出,测试用'''
    def print_KDTree(self):
        stk = []
        p = self.root

        while len(stk) != 0 or p is not None:
            # 走到子树最左边
            while p is not None:
                stk.append(p)
                p = p.left

            if len(stk) != 0:
                cur_node = stk[len(stk) - 1]
                stk.pop()
                print(cur_node.value)
                # 若有则进入右子树,进行新一轮循环
                if cur_node.right is not None:
                    p = cur_node.right

# 一些测试的代码
# if __name__ == '__main__':
#     # 创建kd树
#     df = pd.DataFrame(pd.read_excel('./data/knn.xlsx'))
#     values = df.values[:, :-1]
#     labels = df.values[:, -1]
#     kdtree = KDTree(values, labels)
#     # kdtree.print_KDTree()
#     kdtree.search_KNN([2.1,4.7], 5)

基于kd树的knn

真正用到knn还是用sklearn,这里只是写一下锻炼一下python

这个选k值的过程有点胡来,因为刚接触MLqaq

from statistics import mode

import numpy
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.metrics import precision_score

from kd_tree import KDTree

plt.rcParams['font.sans-serif'] = ['SimHei']


class KNN:
    def __init__(self, values, labels):
        self.values = np.array(values)
        self.labels = np.array(labels)
        self.k = 1  # 初始化k值
        self.min_correct_rate = 0.83  # 能接受的最小准确率,自行调整
        self.train()  # 训练出k值
        self.kd_tree = KDTree(self.values, self.labels)  # 创建全数据kd树

    '''训练主要目的是为了选取k值'''

    def train(self):
        train_len = int(len(self.values) * 0.7)  # 70%做训练集
        # 训练集
        train_values = self.values[:train_len]
        train_labels = self.labels[:train_len]

        # 验证集
        verify_values = self.values[:train_len + 1]
        verify_labels = self.labels[:train_len + 1]

        # 创建kd树
        self.kd_tree = KDTree(train_values, train_labels)

        # 调参
        correct_rate = 0
        while correct_rate < self.min_correct_rate:
            res_labels = self.predict(verify_values)
            # 计算准确率
            correct_rate = self.cal_correct_rate(verify_labels, res_labels)
            self.k += 1

        print("训练完成,验证集准确率为:{0},k为:{1}".format(correct_rate, self.k))

    '''
    knn入口
    :param target: 目标点,一个或者多个
    '''

    def predict(self, target):
        if target is None:
            return

        target = np.array(target)
        shape = target.shape
        if len(shape) == 1:  # 只有一个实例
            return self.predict_core(target)
        else:
            res = []
            for i in range(shape[0]):
                res.append(self.predict_core(target[i]))
            res = np.array(res)
            return res

    '''
    knn的核心方法
    :param target: 这里target只能是一个实例
    '''

    def predict_core(self, target):
        # 获取k个最邻近点对应的标签
        knn_labels = self.kd_tree.search_KNN(target, self.k)[1]
        # 取出k个点中最多的类别最为答案
        return mode(knn_labels)

    '''
    precision_score老报错,自己写一个计算准确率
    origin可以互换predict
    '''

    def cal_correct_rate(self, origin, predict):
        Len = len(origin)
        count = 0
        for i in range(Len):
            if origin[i] == predict[i]:
                count += 1
        return count / Len


if __name__ == '__main__':
    # 读取数据
    df = pd.DataFrame(pd.read_csv('./data/knn_data_2')) # 二维鸢尾花数据
    # df = pd.DataFrame(pd.read_csv('./data/knn_data_3'))  # 三维鸢尾花

    values = df.values[:, :3]
    labels = df.values[:, -1]
    train_len = int(len(values) * 0.9)

    # 测试集
    test_values = values[:train_len]
    test_labels = labels[:train_len]
    # 训练集
    train_values = values[:train_len + 1]
    train_labels = labels[:train_len + 1]

    # 二维数据下的散点图
    # x1_min, x1_max = values[:, 0].min() - 0.5, values[:, 0].max() + 0.5  # 第一维坐标最大最小值
    # x2_min, x2_max = values[:, 1].min() - 0.5, values[:, 1].max() + 0.5  # 第二维坐标最大最小值
    # plt.scatter(values[:, 0], values[:, 1], c=labels, edgecolor="k")
    # plt.xlim(x1_min, x1_max)
    # plt.ylim(x2_min, x2_max)
    # plt.xlabel("特征1")
    # plt.ylabel("特征2")
    # plt.show()

    knn = KNN(train_values, train_labels)
    res = knn.predict(test_values)
    correct_rate = knn.cal_correct_rate(test_labels, res)
    print('测试集预测准确率为:{}'.format(correct_rate))

  • 0
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: knn算法(k-近邻算法)是一种分类和回归的算法,被广泛应用于模式识别、数据挖掘、图形识别等领域。其原理是根据数据本身的特征,将测试集中的每一个样本按照特征相似度进行分类。其中,k的值表示选择多少个最相似的邻居作为判断依据,通常采用欧氏距离来计算相似度。 在knn算法实现过程中,需要先将数据集分为训练集和测试集。接着,通过计算测试集中每一个样本与训练集中每一个样本的欧氏距离(或曼哈顿距离等),选择距离最近的k个邻居。最后,采用“多数表决”的方式选择样本类别,并将该类别赋给测试集中的样本。 在Python中,可以使用scikit-learn库实现knn算法。以下是一个简单的代码示例: from sklearn.neighbors import KNeighborsClassifier # 创建训练集和测试集 train_x = [[0], [1], [2], [3]] train_y = [0, 0, 1, 1] test_x = [[1.5]] # 创建knn分类器(k=2) knn = KNeighborsClassifier(n_neighbors=2) # 拟合模型 knn.fit(train_x, train_y) # 进行预测 print(knn.predict(test_x)) 以上代码中,第一行引用了scikit-learn库下的KNeighborsClassifier类,用于创建一个knn分类器。接着,分别创建了训练集和测试集,并针对训练集中的两类样本对应标签进行了标记。接下来,创建k值为2的knn分类器,并使用fit()方法对训练集进行拟合。最后,通过predict()方法进行实际的预测,并输出测试样本的分类结果。 总体来说,knn算法是一种简单易用的分类和回归算法,具有可解释性强、不受算法实现形式的特点,同时能够适应各种数据类型和特征。在Python中,采用scikit-learn库实现knn算法也非常方便。 ### 回答2: KNN算法是一种基于实例的学习方法,通过计算样本之间的距离来确定新样本的类别。KNN算法是一种简单而有效的分类方法,尤其适用于小数据集。算法原理是基于这样一种思想:样本空间中的每个样本都可以用它最近的K个邻居来代表。其中K是一个正整数,是预定的参数。当K=1时,为最近邻分类算法,即只考虑最近的一个邻居。 具体实现步骤: 1.读入数据集,并将其分为训练集和测试集。 2.对数据集进行归一化处理。 3.对每个测试实例,计算其与训练集中所有实例之间的距离。 4.按照距离的大小降序排列。 5.选取前K个距离最小的实例,得到它们所属的类别中出现次数最多的那个类别作为该测试实例的预测类别。 6.计算预测结果与实际结果的差异。 在Python实现KNN算法需要用到一些基本的库:Numpy和Scikit-learn。具体步骤如下: 1.导入Numpy库。 2.导入数据集并将其分为训练集和测试集。 3.使用Scikit-learn库中的MinMaxScaler函数进行数据归一化处理。 4.使用Scikit-learn库中的KNeighborsClassifier函数进行训练,设定参数k和metric。 5.使用Scikit-learn库中的predict函数进行预测,得到预测结果。 6.计算预测结果与实际结果的差异,得到预测准确率。 KNN算法的优点是简单易懂,精度高;缺点是计算复杂度高,对数据的大小敏感。当数据维度较高时,其计算复杂度会变得极高,而且KNN算法对数据的距离非常敏感,如果数据特征选取不当,会导致预测精度大幅下降。因此,在使用KNN算法的时候需要注意数据特征的选取和K值的选择。 ### 回答3: K近邻(k-NN)算法是最简单的基于实例的学习算法之一,它的主要思想是使用距离度量来对特征空间中的样本进行分类。KNN算法中的K代表选择邻居的数量,邻居是指在训练集中与测试数据距离最近的样本点。KNN算法的基本步骤如下: 1. 计算测试数据与所有训练数据之间的距离。 2. 根据距离度量,对距离最近的K个样本进行投票。 3. 根据投票结果,决定测试数据属于哪一类别。 KNN算法的优点是简单易用,能够处理多分类和回归问题;缺点是计算量大,对训练数据敏感,需要进行归一化处理,并需要选择合适的距离度量和K值。 Python实现KNN算法需要使用Scikit-learn或Numpy等相关库。下面给出一个简单的Python代码实现,该代码实现了一个基于欧氏距离的KNN分类器: ``` import numpy as np from sklearn.neighbors import KNeighborsClassifier # 生成训练数据 X_train = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) y_train = np.array([0, 0, 1, 1]) # 创建KNN分类器,选择K=3 clf = KNeighborsClassifier(n_neighbors=3) # 训练分类器 clf.fit(X_train, y_train) # 测试数据,预测其所属类别 X_test = np.array([[2, 3], [4, 5], [6, 7]]) y_test = clf.predict(X_test) print(y_test) ``` 该代码中,通过Numpy库生成了一个4个样本点的训练数据集,其中前两个样本属于类别0,后两个样本属于类别1。同时,也生成了3个测试数据点。然后使用Scikit-learn库中的KNN分类器,在训练数据上训练模型,选择K=3。最后,对测试数据进行分类,并输出分类结果。 以上就是KNN算法的基本原理Python实现,希望对读者有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值