K 近邻算法

声明:

1,本篇为个人对《2012.李航.统计学习方法.pdf》的学习总结,不得用作商用,欢迎转载,但请注明出处(即:本帖地址)。

2,因为本人在学习初始时有非常多数学知识都已忘记,所以为了弄懂当中的内容查阅了非常多资料。所以里面应该会有引用其它帖子的小部分内容,假设原作者看到能够私信我,我会将您的帖子的地址付到以下。

3。假设有内容错误或不准确欢迎大家指正。

4。假设能帮到你。那真是太好了。

描写叙述

给定一个训练数据集,对新的输入实例。在训练数据集中找到与该实例最邻近的K个实例,若这K个实例的多数属于某个类。就把输入实例分入这个类。

K值得选择

         K值得选择会对K近邻算法的结果产生重大影响:

若K值较小:

预測结果会对近邻实例点十分敏感,若近邻点恰巧为噪声,那预測就会出错。

若K值较大:

长处是可降低预计误差,但近似误差会增大,由于与输入实例较远(不相似的)训练实例会起作用。

若K = N:

那不管输入什么。都将预为測训练实例中最多的那个类,不可取。

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

KD树

Kd树是存储K维空间数据的树结构。

PS:这里的K和K近邻算法的K意义不同。

1,KD树构造方法的描写叙述:

a,  构造根节点,使根节点相应用于K维空间中包括全部实例点的超矩形区域。

b,  通过以下的递归方法,不断切分K维空间,生成子节点:

B.1,在超矩形区域上选择一个坐标轴和该坐标上中位数作为该坐标轴的切分点。

B.2,以经过该点且垂直于该坐标轴做一个超平面。该超平面将当前的超矩形区域切分成左右两个子区域(此时:2个子区域相应2个子节点,其父节点就是刚才的切分点)

c,该过程直到子区域内无实例时终止(终止时的节点为子节点)

在上述过程中将实例集合保存在对应的节点上。

2。样例:构造KD树

     描写叙述

         输入:

K维空间数据集T = {x1,x2, …, xn},当中,xi = (xi(1), xi(2),…, xi(k)),i = 1, 2, …, n

         输出:

KD树

         解:

1。  构造根节点(根节点相应于包括T的K维空间的超矩形区域)

选择x(1)为坐标轴。以T中全部实例的x(1)坐标的中位数为切分点,这样。经过该切分点且垂直与x(1)的超平面就将超矩形区域切分成2个子区域。

而该切分点就是根节点。

2,  反复例如以下步骤直到两个子区域无实例时停止:

对深度为J的节点选择x(l)为切分的坐标轴,l = j(modk) + 1,以该节点区域中全部实例的x(l)坐标的中位数为切分点。将该节点相应的超平面切分成两个子区域。

而该切分点就是节点。

     样例

         题目:构造T={(2, 3),(5, 4), (9, 6), (4, 7), (8, 1), (7, 2)}的平衡KD树.

         解:

                    

1。  在X轴上选X坐标是全部点中位数的点:(7,2)

用经过(7, 2)且垂直于X轴的超平面(图中的①)将矩形区域切分成两部分,这时(7, 2)即为根节点

2。  由于上一步是在X轴上选中位数,所以(7,2)把T分成了两部分:

左节点:(2, 3)     (4, 7)  (5, 4)

右节点:(8, 1)     (9, 6)

而上一步既然在X轴上选中位数。那这一步就以Y轴为标准在刚才的节点上选中位数。

于是左节点选取(5, 4)为中位数。那么就做经过(5,4)且垂直于Y轴的超平面②。

同理右节点选取(9, 6)为中位数,然后就做经过(9,6)且垂直于Y轴的超平面②。

这时(5, 4)和(9, 6)就是(7, 2)的左右节点:

(7,2)

/     \

(5, 4) (9, 6)

3,  同理,循环回X轴,以X轴为准为(5, 4)和(9, 6)的左右节点选取中位数。并将其作为(5, 4)和(9, 6)的子节点

4。  循环上面的步骤,直至无实例。(当然。对于本例在第三步时就有结果了)

5,  最后KD树例如以下:

(7,2)

/     \

(5, 4) (9, 6)

/         \        /

(2, 3)(4, 7)(8, 1)

使用KD树—用KD树做近邻搜索

描写叙述

         输入:

已知的KD树。目标点X

输出:

X的近期邻

         解:

1,  在KD树中找出包括X的叶子节点

从根节点出发,递归向下訪问KD树,若X小于节点坐标,则移动到左子节点。反之移动到右子节点,直到节点为叶子节点。

2。  以此叶子节点为“当前近期邻点”。

3,  递归向上回退,在每一个节点上做下面操作:

A)     若该节点比刚才的“当前近期邻点”距离X更近,则更新此节点为“当前近期邻点”

B)     “当前近期邻点”一定存在于该节点的一个子节点的相应区域。

于是检查该子节点的父节点的另外一个节点相应的区域,看是否有更近的点。

即:

检查父节点的还有一个节点相应的区域中是否与“以X为球心,以X与‘当前近期邻点’的距离为半径的超球体”相交。

C)    若相交:

可能在还有一个子节点相应的区域内存在距离X更近的点,于是移动到还有一个子节点,并递归的进行近期邻查找。

若不想交:

向上回退。

D)     当回退到根节点时,查找结束。最后的“当前近期邻点”就是X的近期邻点。

    样例:

         对以下的KD树,求S点的近期邻

                

    解:

1。  找到包括S的叶子节点D,以D作为“当前近期邻点”。

以S为圆心,S到D的距离为半径画圆。

2。  返回父节点B,在B的还有一个子节点F的区域内做近期邻查找,而F的区域与圆不想交。所以不可能有近期邻点。

3,  继续返回上一级父节点A,在A的还有一子节点C的区域内查找,发现该区域与圆相交。

4。  在该区域内遍历点,发现E点在圆内(比S到D更近)。

5。  更新E为“当前近期邻点”。

6。  反复上述过程直至返回根节点。

7,  终于得到点E为近期邻点。

时间复杂度

若实例点随机分布,则KD树搜索的时间复杂度为O(logN)。N为训练实例数。

K近邻算法更适用于

KD树更适用于训练实例数远大于空间维度的K近邻搜索。

若空间维数接近训练实例数时,它的效率会迅速下降,差点儿接近线性扫描。

代码演示样例:

<pre name="code" class="python">#-*-coding:utf-8-*-
# LANG=en_US.UTF-8
# k 近邻算法
# 文件名称:k_nearest_neighbour.py

import sys
import math

list_T = [
    ( 2, 3 ),
    ( 5, 4 ),
    ( 9, 6 ),
    ( 4, 7 ),
    ( 8, 1 ),
    ( 7, 2 ),
        ]

# 二叉树结点
class BinaryTreeNode( object ):
    def __init__( self, data=None, left=None, right=None, father=None ):
        self.data = data
        self.left = left
        self.right = left
        self.father = father

# 二叉树遍历
class BTree(object):
    def __init__(self,root=0):
        self.root = root

    # 中序遍历
    def inOrder(self,treenode):
        if treenode is None:
            return

        self.inOrder(treenode.left)
        print treenode.data
        self.inOrder(treenode.right)

# 高速排序算法
# 1,取当前元素集的第一个元素为 key。i = 0,j = len(当前元素集)
# 2,j-- 直到找到小于 key 的元素,然后 L[i] 与 L[j] 交换
# 3,i++ 直到找到大于 key 的元素,然后 L[i] 与 L[j] 交换
# 4,当 i == j 时停止
# 5。L[i] = key
# 此时当前元素集被第 i 个元素分成了左右两部分,左边的都比 key 小。右边的都比 key 大
# 6。对左右两部分反复上面 5 步直到再无切割
def quick_sort( T, left, right, rank):
    tmp_i = left
    tmp_j = right

    if left >= right:
        return

    key = T[left][rank]; key_item = T[left]
    while tmp_i != tmp_j:
        while tmp_i < tmp_j and T[tmp_j][rank] > key:
            tmp_j -= 1
        T[tmp_i] = T[tmp_j]

        while tmp_i < tmp_j and T[tmp_i][rank] < key:
            tmp_i += 1
        T[tmp_j] = T[tmp_i]

    T[tmp_i] = key_item

    quick_sort( T, left, tmp_i-1, rank )
    quick_sort( T, tmp_i+1, right, rank )

    return T

# 制作 kd 树
#   原队列: [(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)]
#   1,以x轴为基准排列  : [(2, 3), (4, 7), (5, 4), (7, 2), (8, 1), (9, 6)]
#   2,取中间的数为根,这时会产生左节点集和右节点集,即:经过(7, 2)的垂直于 x 轴的超平面 1 将整个矩形区域分成了左右两部分:
#                                       (7, 2)
#                                       /    #                                      /      #                                     /        #               [(2, 3), (4, 7), (5, 4)]      [(8, 1), (9, 6)]
#   3,对左右子树集以 y 轴为基准排列:
#               [(2, 3), (5, 4), (4, 7)]      [(8, 1), (9, 6)]
#   4,取中间的数为父节点,这时会产生左节点集和右节点集,即:经过 (5, 4) 和 (9, 6) 的垂直于超平面 2 (或者说 y 轴)的超平面将上面的两个左右区域又分成了两部分
#                               (5, 4)         (9, 6)
#                                /  \            /
#                               /    \          /
#                           (2, 3)  (4, 7)  (8, 1)
#   循环上面 4 步,直到没有结点。

#   终于 kd 树例如以下图所看到的:
#                            (7, 2)
#                            /    #                           /      #                      (5, 4)      (9, 6)
#                       /  \         /
#                      /    \       /
#                  (2, 3)  (4, 7)  (8, 1)
def make_kd_tree( T ):

    # 获取中间的数
    def get_middle_item( _input ):
        middle_item_num = len( _input ) / 2
        middle_item = _input[middle_item_num]

        return middle_item, middle_item_num

    # kd 树的迭代函数
    #   參数:二叉树的结点,上一步的结点集。当前迭代时结点集的最小序号,最大序号,秩
    def iter_for_kd_tree( root, tmp_T, left, right, rank ):
        # 依据 left 和 right 截取 tmp_T,tmp_T 为上一步的结点集,如:
        # 在第一次迭代后,若当前循环的是左结点集,那 left = 0,right = middle_item_num
        # 于是本次就是在 [(2, 3), (5, 4), (4, 7)] 这个结点集中选择中位点并继续了。

tmp_T = tmp_T[left: right]

        # 若当前的结点集中已无节点,就返回 None
        if len(tmp_T) == 0: return

        # 若当前结点中仅仅有一个元素,那就创建并返回用该元素创建的二叉树结点
        if len(tmp_T) == 1:
            return BinaryTreeNode( tmp_T[0] )

        # 对当前的结点集以当前的秩为基准进行排列
        quick_sort( tmp_T, 0, len(tmp_T)-1, rank )
        # 更新秩,为下次排列做准a
        rank = (rank + 1) % len(T[0])
        # 获取当前结点集的中间元素和中间元素的坐标(该坐标用于将当前结点集分离成两部分)
        middle_item, middle_item_num = get_middle_item( tmp_T )
        # 使用该中间元素创建一个二叉树结点
        root = BinaryTreeNode( middle_item )
        # 将 "root 的左子结点。当前的结点集,左边结点集的最小坐标。左边结点集的最大坐标,秩" 传入本函数进行迭代
        # 返回的结点保存到 root 的左子结点
        root.left = iter_for_kd_tree( root.left, tmp_T, 0, middle_item_num, rank )
        # root 的左子结点的父结点指向 root 自己
        if root.left != None: root.left.father = root
        # 同上。保存到 root 的右子结点
        root.right = iter_for_kd_tree( root.right, tmp_T, middle_item_num+1, len(tmp_T), rank )
        if root.right != None: root.right.father = root
        # 返回根
        return root

    rank = 0 # 第一次在 x 轴上找中位点
    return iter_for_kd_tree( BinaryTreeNode(), T, 0, len(T), rank )

# 使用 kd 树,进行 k 近邻算法
def use_kd_tree( T, root, target ):

    # 得到两点间的距离
    def get_distance( x, y ):
        distance = (x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1])
        return math.sqrt( distance )

    # 中序遍历 kd 树。得到包括 target 的叶子结点
    def inOrder( node, rank ):
        # 假设该结点没有左子结点和右子结点,那该结点就是叶子结点了
        if not node.left and not node.right:
            return node

        # 保存当前的秩
        tmp_rank = rank
        # 更新秩
        rank = (rank + 1) % len(T[0])

        # 从根结点出发,假设目标点在当前秩的坐标 < node 在当前秩的坐标
        if target[tmp_rank] <= node.data[tmp_rank]:
            # 移动到左子结点
            node = inOrder( node.left, rank )
        else:
            # 反之移动到右子结点
            node = inOrder( node.right, rank )

        return node

    # 得到近期的点
    def find_close_node( node, target, close_node ):

        # 遍历到根结点就 ok 了
        if not node.father: return

        # 计算当前近期邻点距离
        min_distance = get_distance( node.data, target )
        # 计算 target 距离当前最邻近点的父节点的距离
        new_distance = get_distance( node.father.data, target )

        # 假设距离父节点更近
        if min_distance >= new_distance:
            min_distance = new_distance
            # 将父节点保存成“当前近期邻点”
            close_node = node.father.data

            # 推断父节点的另外一个结点距离 target 是否更近,记得话将其保存成“当前近期邻点”
            if node.father.left != node:
                new_distance = get_distance( node.father.left.data, target )
                if min_distance >= new_distance:
                    close_node = node.father.left.data
            else:
                new_distance = get_distance( node.father.right.data, target )
                if min_distance >= new_distance:
                    close_node = node.father.right.data

        find_close_node( node.father, target, close_node )
        return close_node

    rank = 0
    node = inOrder( root, rank )
    close_node = node.data
    find_close_node( node, target, close_node )
    return close_node

root = make_kd_tree( list_T )

target = (4, 3)
print use_kd_tree( list_T, root, target )

#bt = BTree( root )
#bt.inOrder( bt.root )
				
时间: 2024-10-16 23:07:25

K 近邻算法的相关文章

『cs231n』作业1问题1选讲_通过代码理解K近邻算法&amp;交叉验证选择超参数参数

通过K近邻算法探究numpy向量运算提速 茴香豆的"茴"字有... ... 使用三种计算图片距离的方式实现K近邻算法: 1.最为基础的双循环 2.利用numpy的broadca机制实现单循环 3.利用broadcast和矩阵的数学性质实现无循环 图片被拉伸为一维数组 X_train:(train_num, 一维数组) X:(test_num, 一维数组) 方法验证 import numpy as np a = np.array([[1,1,1],[2,2,2],[3,3,3]]) b

从K近邻算法、距离度量谈到KD树、SIFT+BBF算法

从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 从K近邻算法.距离度量谈到KD树.SIFT+BBF算法 前言 前两日,在微博上说:“到今天为止,我至少亏欠了3篇文章待写:1.KD树:2.神经网络:3.编程艺术第28章.你看到,blog内的文章与你于别处所见的任何都不同.于是,等啊等,等一台电脑,只好等待..”.得益于田,借了我一台电脑(借他电脑的时候,我连表示感谢,他说“能找到工作全靠你的博客,这点儿小忙还说,不地道”,有的时候,稍许感受到受人信任也是一种压力,愿我不辜负大家对我的信任)

K近邻算法

1.1.什么是K近邻算法 何谓K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,单从名字来猜想,可以简单粗暴的认为是:K个最近的邻居,当K=1时,算法便成了最近邻算法,即寻找最近的那个邻居.为何要找邻居?打个比方来说,假设你来到一个陌生的村庄,现在你要找到与你有着相似特征的人群融入他们,所谓入伙. 用官方的话来说,所谓K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居),这K个实例的多数属

K近邻算法-KNN

何谓K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,单从名字来猜想,可以简单粗暴的认为是:K个最近的邻居,当K=1时,算法便成了最近邻算法,即寻找最近的那个邻居.为何要找邻居?打个比方来说,假设你来到一个陌生的村庄,现在你要找到与你有着相似特征的人群融入他们,所谓入伙. 用官方的话来说,所谓K近邻算法,即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居),这K个实例的多数属于某个类,就把该输入实例分

k近邻算法理论(一)

时间 :2014.07.05 地点:基地 ----------------------------------------------------------------------------------- 一.简述 K近邻法(k-nearest neighbor,kNN)是一种基本分类与回归方法.k近邻的输入为实例的特征向量,对应特征空间中的点,输出为实例的类别.k近邻算法的基本思想是:给定训练数据集,实例类别已定,在对目标实例进行分类时,我们根据与目标实例k个最近邻居的训练实例的类别,通过

机器学习实战笔记--k近邻算法

1 #encoding:utf-8 2 from numpy import * 3 import operator 4 import matplotlib 5 import matplotlib.pyplot as plt 6 7 from os import listdir 8 9 def makePhoto(returnMat,classLabelVector): #创建散点图 10 fig = plt.figure() 11 ax = fig.add_subplot(111) #例如参数为

基本分类方法——KNN(K近邻)算法

在这篇文章 http://www.cnblogs.com/charlesblc/p/6193867.html 讲SVM的过程中,提到了KNN算法.有点熟悉,上网一查,居然就是K近邻算法,机器学习的入门算法. 参考内容如下:http://www.cnblogs.com/charlesblc/p/6193867.html 1.kNN算法又称为k近邻分类(k-nearest neighbor classification)算法. 最简单平凡的分类器也许是那种死记硬背式的分类器,记住所有的训练数据,对于

使用K近邻算法实现手写体识别系统

目录 1. 应用介绍 1.1实验环境介绍 1.2应用背景介绍 2. 数据来源及预处理 2.1数据来源及格式 2.2数据预处理 3. 算法设计与实现 3.1手写体识别系统算法实现过程 3.2 K近邻算法实现 3.3手写体识别系统实现 3.4算法改进与优化 4. 系统运行过程与结果展示 1.应用介绍 1.1实验环境介绍 本次实验主要使用Python语言开发完成,Python的版本为2.7,并且使用numpy函数库做一些数值计算和处理. 1.2应用背景介绍 本次实验实现的是简易的手写体识别系统,即根据

算法入门系列2:k近邻算法

用官方的话来说,所谓K近邻算法(k-Nearest Neighbor,KNN),即是给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的K个实例(也就是上面所说的K个邻居), 这K个实例的多数属于某个类,就把该输入实例分类到这个类中.这是一个有监督的学习算法 例如下图,红色和蓝色代表已知的训练好的的数据,这个时候来一个示例,也就是图中的绿色圆块,这个绿色圆块属于哪一类呢? 如果K=3,绿色圆点的最近的3个邻居是2个红色小三角形和1个蓝色小正方形,少数从属于多数,基于统计的方法,