第三章 k近邻法

k 近邻法 (k-nearest neighbor, k-NN)是一种基本分类与回归方法。本书只讨论分类问题中的 k k k 近邻法。 k k k 近邻法的输入为实例的特征向量,对应于特征空间的点:输出为实例的类别,可以取多类。 k k k 近邻法假设给定一个训练数据集, 其中的实例类别己定。分类时,对新的实例,根据其 k k k 个最近邻的训练实例的类别,通过多数表决等方式进行预测 。 因此, k k k 近邻法不具有显式的学习过程。 k k k 近邻法实际上利用训练数据集对特征向量空间进行划分,井作为其分类的"模型" , k k k 值的选择、距离度量及分类决策规则是 k k k 近邻法的三个基本要素。


3.1 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),\dots,(x_N,y_N)\} T={(x1,y1),(x2,y2),,(xN,yN)}

其中, x i ∈ X ⊆ R n x_i \in \mathcal{X} \subseteq \mathcal{R}^n xiXRn为实例的特征向量, y i ∈ Y = { c 1 , c 2 , … , c K } y_i \in \mathcal{Y}=\{c_1,c_2,\dots,c_K\} yiY={c1,c2,,cK}为实例的类别

输出:实例 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) 中根据分类决策规则(如多数表决)决定 x x x 的类别 y y y

y = arg ⁡ max ⁡ c j ∑ x i ∈ N k ( x ) I ( y i = c j ) , i = 1 , 2 , ⋯   , N ; j = 1 , 2 , ⋯   , K y=\arg \max _{c_{j}} \sum_{x_{i} \in N_{k}(x)} I\left(y_{i}=c_{j}\right), \quad i=1,2, \cdots, N ; j=1,2, \cdots, K y=argcjmaxxiNk(x)I(yi=cj),i=1,2,,N;j=1,2,,K

I I I为指示函数,即当 y i = c j y_i=c_j yi=cj I I I 为 1 否则 I I I 为 0。

3.2 k k k近邻模型

3.2.1 模型

k k k 近邻法中,当训练集、距离度量(如欧氏距离)、 k k k 值及分类决策规则(如多数表 决)确定后,对于任何一个新的输入实例,它所属的类唯一地确定。 这相当于根据上 述要素将特征空间划分为一些子空间,确定子空间里的每个点所属的类。 这一事实从 最近邻算法中可以看得很清楚。

特征空间中,对每个训练实例点 x i x_i xi,距离该点比其他点更近的所有点组成一个区
域,叫作单元 (cell) 。 每个训练实例点拥有一个单元,所有训练实例点的单元构成对特征空间的一个划分。最近邻法将实例 x i x_i xi 的类 y i y_i yi 作为其单元中所有点的类标记。 这样,每个单元的实例点的类别是确定的。 图 3.1 是二维特征空间划分的一个例子。

这里 p ≥ 1 p \ge 1 p1。当 p = 2 p=2 p=2,称为欧氏距离

L 2 ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_{2}\left(x_{i}, x_{j}\right)=\left(\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right|^{2}\right)^{\frac{1}{2}} L2(xi,xj)=(l=1nxi(l)xj(l)2)21

p = 1 p=1 p=1时,称为曼哈顿距离

L 1 ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ L_{1}\left(x_{i}, x_{j}\right)=\sum_{l=1}^{n}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| L1(xi,xj)=l=1nxi(l)xj(l)

p = ∞ p=\infty p=时,它是各个坐标距离的最大值

L ∞ ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ L_{\infty}\left(x_{i}, x_{j}\right)=\max _{l}\left|x_{i}^{(l)}-x_{j}^{(l)}\right| L(xi,xj)=lmaxxi(l)xj(l)

3.2.3 k k k值的选择

如果选择较小的 k k k 值,就相当于用较小的邻域中的训练实例进行预测,"学习"的近似误差会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是"学习"的估计误差会增大,预测结果会对近邻的实例点非常敏感。如果邻近的实例点恰巧是噪声,预测就会出错。换句话说, k k k 值的减小就意味着整体模型变得复杂,容易发生过拟合。

如果选择较大的 k k k 值,就相当于用较大邻域中的训练实例进行预测。其优点是可以减少学习的估计误差,但缺点是学习的近似误差会增大。这时与输入实例较远的(不相似的)训练实例也会对预测起作用,使预测发生错误。 k k k 值的增大就意味着整体的模型变得简单。

在应用中 , k k k 值一般取一个比较小的数值。通常采用交叉验证法来选取最优的 k k k 值。

3.3 k k k近邻法的实现: k d kd kd

3.3.1 构造 k d kd kd

k d kd kd 树是一种对 k k k 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。 k d kd kd 树是二叉树,表示对 k k k 维空间的一个划分。构造 k d kd kd 树相当于不 断地用垂直于坐标轴的超平面将 k k k 维空间切分,构成一系列的 k k k 维超矩形区域。 k d kd kd 树的每个结点对应于一个 k k k 维超矩形区域。

构造 k d kd kd 树的方法如下:构造根结点,使根结点对应于 k k k 维空间中包含所有实例点的超矩形区域;通过下面的递归方法,不断地对 k k k 维空间进行切分,生成子结点。 在超矩形区域(结点)上选择一个坐标轴和在此坐标轴上的一个切分点,确定一个超平面,这个超平面通过选定的切分点并垂直于选定的坐标轴,将当前超矩形区域切分为左右两个子区域(子节点);这时,实例被分到两个子区域。这个过程直到子区域内没有实例时终止(终止时的结点为叶结点)。在此过程中,将实例保存在相应的结点上。

通常,依次选择坐标轴对空间切分,选择训练实例点在选定坐标轴上的中位数为切分点,这样得到的 k d kd kd 树是平衡的。注意,平衡的 k d kd kd树搜索时的效率未必是最优的。

3.3.2 搜索 k d kd kd

给定一个目标点,搜索其最近邻。首先找到包含目标点的叶结点:然后从该叶结点出发,依次回退到父结点;不断查找与目标点最邻近的结点,当确定不可能存在更近的结点时终止。 这样搜索就被限制在空间的局部区域上,效率大为提高。

包含目标点的叶结点对应包含目标点的最小超矩形区域。以此叶结点的实例点作为当前最近点。目标点的最近邻一定在以目标点为中心并通过当前最近点的超球体的内部(参阅图 3.5) 。然后退回当前结点的父结点,如果父结点的另一子结点的超矩形区域与超球体相交,那么在相交的区域内寻找与目标点更近的实例点。如果存在这样的点,将此点作为新的当前最近点。算法转到更上一级的父结点,继续上述过程。如果父结点的另一子结点的超矩形区域与超球体不相交,或不存在比当前最近点更近的点,则停止搜索 。

import numpy as np

class Node:
    def __init__(self, data, sp=0, left=None, right=None):
        self.data = data
        # 0是按特征1排序,1是按特征2排序
        self.sp = sp
        self.left = left
        self.right = right

    def __lt__(self, other):
        return self.data < other.data

class KDTree:
    def __init__(self, data):
        self.dim = data.shape[1]
        self.root = self.createTree(data, 0)
        self.nearest_node = None
        self.nearest_dist = np.inf  # 设置无穷大

    def createTree(self, dataset, sp):
        if len(dataset) == 0:
            return None

        # 按特征列进行排序
        dataset_sorted = dataset[np.argsort(dataset[:, sp])]
        # 获取中位数索引
        mid = len(dataset) // 2
        # 生成节点
        left = self.createTree(dataset_sorted[:mid], (sp + 1) % self.dim)
        right = self.createTree(dataset_sorted[mid + 1:], (sp + 1) % self.dim)
        parentNode = Node(dataset_sorted[mid], sp, left, right)

        return parentNode

    def nearest(self, x):
        def visit(node):
            if node is not None:
                dis = node.data[node.sp] - x[node.sp]
                # 访问子节点
                visit(node.left if dis > 0 else node.right)
                # 查看当前节点到目标节点的距离 二范数求距离
                curr_dis = np.linalg.norm(x - node.data, 2)
                # 更新节点
                if curr_dis < self.nearest_dist:
                    self.nearest_dist = curr_dis
                    self.nearest_node = node
                # 比较目标节点到当前节点距离是否超过当前超平面,超过了就需要到另一个子树中
                if self.nearest_dist > abs(dis):  # 要到另一面查找 所以判断条件与上面相反
                    visit(node.left if dis < 0 else node.right)

        # 从根节点开始查找
        node = self.root
        visit(node)
        return self.nearest_node.data, self.nearest_dist

data = np.array([[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]])
kdtree = KDTree(data)
node, dist = kdtree.nearest(np.array([6, 5]))
print(node, dist)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值