3.K近邻法

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

其中, xi?x⊆Rn为实例的特征向量, yi? ={c1, c2,…,cK}为实例的类别, i=1,2,…,N; 实例特征向量x;
输出: 实例x所属的类y。
(1) 根据给定的距离度量, 在训练集T中找出与x最邻近的k个点, 涵盖这k个点的x的邻域记作Nk(x);
(2) 在Nk(x)中根据分类决策规则(如多数表决) 决定x的类别y:

上式中, I为指示函数, 即当yi=cj时I为1, 否则I为0。
k近邻法的特殊情况是k=1的情形, 称为最近邻算法。 对于输入的实例点(特征向量) x, 最近邻法将训练数据集中与x最邻近点的类作为x的类。k近邻法没有显式的学习过程。

2. k 近邻模型
k近邻法使用的模型实际上对应于对特征空间的划分。 模型由三个基本要素——距离度量、 k值的选择和分类决策规则决定。

特征空间中两个实例点的距离是两个实例点相似程度的反映。 k近邻模型的特征空间一般是n维实数向量空间Rn。 使用的距离是欧氏距离, 但也可以是其他距离, 如更一般的Lp距离(Lp distance) 或Minkowski距离(Minkowski distance) 。设特征空间x是n维实数向量空间Rn,

xi,xj的Lp距离定义为:这里p≥1。

当p=2时, 称为欧氏距离(Euclidean distance), 即

当p=1时, 称为曼哈顿距离(Manhattan distance) , 即

当p=时, 它是各个坐标距离的最大值, 即

图3.2给出了二维空间中p取不同值时, 与原点的Lp距离为1(Lp=1) 的点的图形。

k值的选择会对k近邻法的结果产生重大影响。
如果选择较小的k值, 就相当于用较小的邻域中的训练实例进行预测, “学习”的近似误差(approximation error) 会减小, 只有与输入实例较近的(相似的) 训练实例才会对预测结果起作用。 但缺点是“学习”的估计误差(estimation error) 会增大, 预测结果会对近邻的实例点非常敏感。 如果邻近的实例点恰巧是噪声, 预测就会出错。 换句话说, k值的减小就意味着整体模型变得复杂, 容易发生过拟合。
如果选择较大的k值, 就相当于用较大邻域中的训练实例进行预测。 其优点是可以减少学习的估计误差。 但缺点是学习的近似误差会增大。 这时与输入实例较远的(不相似的) 训练实例也会对预测起作用, 使预测发生错误。 k值的增大就意味着整体的模型变得简单。
k近邻法中的分类决策规则往往是多数表决, 即由输入实例的k个邻近的训练实例中的多数类决定输入实例的类。
多数表决规则(majority voting rule) 有如下解释: 如果分类的损失函数为0-1损失函数, 分类函数为

那么误分类的概率是

对给定的实例x?x, 其最近邻的k个训练实例点构成集合Nk(x)。 如果涵盖Nk(x)的区域的类别是cj, 那么误分类率是要使误分类率最小即经验风险最小, 就要使

最大, 所以多数表决规则等价于经验风险最小化。

3. k 近邻法的实现: kd 树
kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。kd树是二叉树, 表示对k维空间的一个划分(partition) 。 构造kd树相当于不断地用垂直于坐标轴的超平面将k维空间切分, 构成一系列的k维超矩形区域。 kd树的每个结点对应于一个k维超矩形区域。
构造平衡kd树
输入: k维空间数据集T={x1, x2,…,xN},
其中 , i=1,2,…,N;
输出: kd树。
(1) 开始: 构造根结点, 根结点对应于包含T的k维空间的超矩形区域。
选择x(1)为坐标轴, 以T中所有实例的x(1)坐标的中位数为切分点, 将根结点对应的超矩形区域切分为两个子区域。 切分由通过切分点并与坐标轴x(1)垂直的超平面实现。由根结点生成深度为1的左、 右子结点: 左子结点对应坐标x(1)小于切分点的子区域,右子结点对应于坐标x(1)大于切分点的子区域。将落在切分超平面上的实例点保存在根结点。
(2) 重复: 对深度为j的结点, 选择x(l)为切分的坐标轴, l=j(modk)+1, 以该结点的区域中所有实例的x(l)坐标的中位数为切分点, 将该结点对应的超矩形区域切分为两个子区域。 切分由通过切分点并与坐标轴x(l)垂直的超平面实现。由该结点生成深度为j+1的左、 右子结点: 左子结点对应坐标x(l)小于切分点的子区域, 右子结点对应坐标x(l)大于切分点的子区域。将落在切分超平面上的实例点保存在该结点。
(3) 直到两个子区域没有实例存在时停止。 从而形成kd树的区域划分。
代码:

# kd-tree每个结点中主要包含的数据结构如下 
class KdNode(object):
    def __init__(self, dom_elt, split, left, right):
        self.dom_elt = dom_elt  # k维向量节点(k维空间中的一个样本点)
        self.split = split      # 整数(进行分割维度的序号)
        self.left = left        # 该结点分割超平面左子空间构成的kd-tree
        self.right = right      # 该结点分割超平面右子空间构成的kd-tree
 
class KdTree(object):
    def __init__(self, data):
        k = len(data[0])  # 数据维度
        
        def CreateNode(split, data_set): # 按第split维划分数据集exset创建KdNode
            if not data_set:    # 数据集为空
                return None
            # key参数的值为一个函数,此函数只有一个参数且返回一个值用来进行比较
            # operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为需要获取的数据在对象中的序号
            #data_set.sort(key=itemgetter(split)) # 按要进行分割的那一维数据排序
            data_set.sort(key=lambda x: x[split])
            split_pos = len(data_set) // 2      # //为Python中的整数除法
            median = data_set[split_pos]        # 中位数分割点             
            split_next = (split + 1) % k        # cycle coordinates
            
            # 递归的创建kd树
            return KdNode(median, split, 
                          CreateNode(split_next, data_set[:split_pos]),     # 创建左子树
                          CreateNode(split_next, data_set[split_pos + 1:])) # 创建右子树
                                
        self.root = CreateNode(0, data)         # 从第0维分量开始构建kd树,返回根节点

# KDTree的前序遍历
def preorder(root):  
    print (root.dom_elt)  
    if root.left:      # 节点不为空
        preorder(root.left)  
    if root.right:  
        preorder(root.right)

用kd树的最近邻搜索
输入: 已构造的kd树; 目标点x;
输出: x的最近邻。
(1) 在kd树中找出包含目标点x的叶结点: 从根结点出发, 递归地向下访问kd树。 若目标点x当前维的坐标小于切分点的坐标, 则移动到左子结点, 否则移动到右子结点。 直到子结点为叶结点为止。
(2) 以此叶结点为“当前最近点”。
(3) 递归地向上回退, 在每个结点进行以下操作:
(a) 如果该结点保存的实例点比当前最近点距离目标点更近, 则以该实例点为“当前最近点”。
(b) 当前最近点一定存在于该结点一个子结点对应的区域。 检查该子结点的父结点的另一子结点对应的区域是否有更近的点。 具体地, 检查另一子结点对应的区域是否与以目标点为球心、 以目标点与“当前最近点”间的距离为半径的超球体相交。
如果相交, 可能在另一个子结点对应的区域内存在距目标点更近的点, 移动到另一个子结点。 接着, 递归地进行最近邻搜索;
如果不相交, 向上回退。
(4) 当回退到根结点时, 搜索结束。 最后的“当前最近点”即为x的最近邻点。
如果实例点是随机分布的, kd树搜索的平均计算复杂度是O(logN), 这里N是训练实
例数。 kd树更适用于训练实例数远大于空间维数时的k近邻搜索。 当空间维数接近训练实
例数时, 它的效率会迅速下降, 几乎接近线性扫描。
代码:

# 对构建好的kd树进行搜索,寻找与目标点最近的样本点:
from math import sqrt
from collections import namedtuple

# 定义一个namedtuple,分别存放最近坐标点、最近距离和访问过的节点数
result = namedtuple("Result_tuple", "nearest_point  nearest_dist  nodes_visited")
  
def find_nearest(tree, point):
    k = len(point) # 数据维度
    def travel(kd_node, target, max_dist):
        if kd_node is None:     
            return result([0] * k, float("inf"), 0) # python中用float("inf")和float("-inf")表示正负无穷
 
        nodes_visited = 1
        
        s = kd_node.split        # 进行分割的维度
        pivot = kd_node.dom_elt  # 进行分割的“轴”
        
        if target[s] <= pivot[s]:           # 如果目标点第s维小于分割轴的对应值(目标离左子树更近)
            nearer_node  = kd_node.left     # 下一个访问节点为左子树根节点
            further_node = kd_node.right    # 同时记录下右子树
        else:                               # 目标离右子树更近
            nearer_node  = kd_node.right    # 下一个访问节点为右子树根节点
            further_node = kd_node.left
 
        temp1 = travel(nearer_node, target, max_dist)  # 进行遍历找到包含目标点的区域
        
        nearest = temp1.nearest_point       # 以此叶结点作为“当前最近点”
        dist = temp1.nearest_dist           # 更新最近距离
        
        nodes_visited += temp1.nodes_visited  
 
        if dist < max_dist:     
            max_dist = dist    # 最近点将在以目标点为球心,max_dist为半径的超球体内
            
        temp_dist = abs(pivot[s] - target[s])    # 第s维上目标点与分割超平面的距离
        if  max_dist < temp_dist:                # 判断超球体是否与超平面相交
            return result(nearest, dist, nodes_visited) # 不相交则可以直接返回,不用继续判断
            
        #----------------------------------------------------------------------  
        # 计算目标点与分割点的欧氏距离  
        temp_dist = sqrt(sum((p1 - p2) ** 2 for p1, p2 in zip(pivot, target)))     
        
        if temp_dist < dist:         # 如果“更近”
            nearest = pivot          # 更新最近点
            dist = temp_dist         # 更新最近距离
            max_dist = dist          # 更新超球体半径
        
        # 检查另一个子结点对应的区域是否有更近的点
        temp2 = travel(further_node, target, max_dist) 
        
        nodes_visited += temp2.nodes_visited
        if temp2.nearest_dist < dist:        # 如果另一个子结点内存在更近距离
            nearest = temp2.nearest_point    # 更新最近点
            dist = temp2.nearest_dist        # 更新最近距离
 
        return result(nearest, dist, nodes_visited)
 
    return travel(tree.root, point, float("inf"))  # 从根节点开始递归

p = inf为闵式距离minkowski_distance

例3.2 给定一个二维空间的数据集:

构造一个平衡kd树。

data = [[2,3],[5,4],[9,6],[4,7],[8,1],[7,2]]
kd = KdTree(data)
preorder(kd.root)

[7, 2]
[5, 4]
[2, 3]
[4, 7]
[9, 6]
[8, 1]
找出距离点[3,4.5]最近的点:

ret = find_nearest(kd, [3,4.5])
print (ret)

Result_tuple(nearest_point=[2, 3], nearest_dist=1.8027756377319946, nodes_visited=4)

本代码同样适用三维,使用方式一样。

原文地址:https://www.cnblogs.com/xutianlun/p/12237498.html

时间: 2024-07-28 22:40:16

3.K近邻法的相关文章

K近邻法(KNN)原理小结

K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用.比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出了.这里就运用了KNN的思想.KNN方法既可以做分类,也可以做回归,这点和决策树算法相同. KNN做回归和分类的主要区别在于最后做预测时候的决策方式不同.KNN做分类预测时,一般是选择多数表决法,即训练集里和预测的样本特征最近的K个样本,预测为里面有最多类别数的类别.而KNN做回归时,一般是选择平均

统计学习方法 (第3章)K近邻法 学习笔记

第3章 K近邻法 k近邻算法简单.直观:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类.当K=1时,又称为最近邻算法,这时候就是将训练数据集中与x最邻近点作为x的类. 3.1 k近邻模型 模型由三个基本要素--距离度量.k值得选择.和分类决策规则决定. 3.1.1 距离度量 p=2时,称为欧式距离,p=1时,称为曼哈顿距离. 3.1.2 k值的选择 k 值的选择会对k 近邻法的结果产生重大影响.如果选择较小的k

李航统计学习方法——算法2——k近邻法

一.K近邻算法 k近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法,输入实例的特征向量,输出实例的类别,其中类别可取多类 二.k近邻模型 2.1 距离度量 距离定义: (1)当p=1,称为曼哈顿距离 (2)当p=2,称为欧式距离 (3)当p取无穷大时,它是各个坐标距离的最大值 max|xi-xj| 注意:p值的选择会影响分类结果,例如二维空间的三个点 x1=(1,1),x2=(5,1), x3=(4,4) 由于x1和x2只有第二维上不同,不管p值如何变化,Lp始终

《统计学习方法》第三章,k 近邻法

? k 近邻法来分类,用到了 kd 树的建立和搜索 ● 代码 1 import numpy as np 2 import matplotlib.pyplot as plt 3 from mpl_toolkits.mplot3d import Axes3D 4 from mpl_toolkits.mplot3d.art3d import Poly3DCollection 5 from matplotlib.patches import Rectangle 6 import operator 7 i

scikit-learn K近邻法类库使用小结

在K近邻法(KNN)原理小结这篇文章,我们讨论了KNN的原理和优缺点,这里我们就从实践出发,对scikit-learn 中KNN相关的类库使用做一个小结.主要关注于类库调参时的一个经验总结. 一.scikit-learn 中KNN相关的类库概述 在scikit-learn 中,与近邻法这一大类相关的类库都在sklearn.neighbors包之中.KNN分类树的类是KNeighborsClassifier,KNN回归树的类是KNeighborsRegressor.除此之外,还有KNN的扩展,即限

统计学习方法与Python实现(二)——k近邻法

统计学习方法与Python实现(二)——k近邻法 iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.定义 k近邻法假设给定一个训练数据集,其中的实例类别已定.分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决的方式进行预测.k近邻法不具有显式的学习过程,而实际上是利用训练数据集对特征空间进行划分,并作为其分类的模型.k近邻法的三个基本要素是 k值的选择.距离度量和分类决策规则. k近邻法的模型是将特征空间划分成一些称为单元的子空间,并且

k近邻法(k-nearest neighbor, k-NN)

近邻法(-nearest neighbor, -NN)是一种基本的分类方法. 近邻法假设给定一个数据集,其中的样例类别已定.分类时,对新的样例,根据这个新样例的个最近邻的训练样例的类别,通过多数表决等方式进行预测. 因此,近邻法不具有显式的学习过程.值的选择.距离度量及分类决策规则是近邻法的三个基本要素. 近邻法于1968年由Cover和Hart提出. 给定训练集并且训练集,一共个样本,个维度,用表示数据集中的第个样本,用表示标记(类别)向量,代表第个样本的标记. 我们这时候要预测一个测试样例的

K近邻法

K近邻算法 给定一个训练数据集,对新的输入实例,在训练数据集中找到跟它最近的k个实例,根据这k个实例的类判断它自己的类(一般采用多数表决的方法). 算法详解: 输入:训练数据集 其中,为实例的特征向量,

k近邻法的C++实现:kd树

1.k近邻算法的思想 给定一个训练集,对于新的输入实例,在训练集中找到与该实例最近的k个实例,这k个实例中的多数属于某个类,就把该输入实例分为这个类. 因为要找到最近的k个实例,所以计算输入实例与训练集中实例之间的距离是关键! k近邻算法最简单的方法是线性扫描,这时要计算输入实例与每一个训练实例的距离,当训练集很大时,非常耗时,这种方法不可行,为了提高k近邻的搜索效率,常常考虑使用特殊的存储结构存储训练数据,以减少计算距离的次数,具体方法很多,这里介绍实现经典的kd树方法. 2.构造kd树 kd