李航统计学习方法(三)----k近邻算法

k近邻算法

给定一个训练数据集,对新的输入实例,在训练数据集中找到跟它最近的k个实例,根据这k个实例的类判断它自己的类(一般采用多数表决的方法)。

image.png

k近邻模型

模型有3个要素——距离度量方法、k值的选择和分类决策规则。

模型

当3要素确定的时候,对任何实例(训练或输入),它所属的类都是确定的,相当于将特征空间分为一些子空间。


image.png

距离度量
对n维实数向量空间Rn,经常用Lp距离或曼哈顿Minkowski距离。

Lp距离定义如下:


image

当p=2时,称为欧氏距离:

image

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

image

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

image

用图表示如下:


image.png

k值的选择

k较小,容易被噪声影响,发生过拟合。

k较大,较远的训练实例也会对预测起作用,容易发生错误。

分类决策规则

使用0-1损失函数衡量,那么误分类率是:

image

Nk是近邻集合,要使左边最小,右边的

image

必须最大,所以多数表决=经验最小化。

k近邻法的实现:kd树

算法核心在于怎么快速搜索k个近邻出来,朴素做法是线性扫描,不可取,这里介绍的方法是kd树。

构造kd树

对数据集T中的子集S初始化S=T,取当前节点node=root取维数的序数i=0,对S递归执行:

找出S的第i维的中位数对应的点,通过该点,且垂直于第i维坐标轴做一个超平面。该点加入node的子节点。该超平面将空间分为两个部分,对这两个部分分别重复此操作(S=S',++i,node=current),直到不可再分。


image.png
   T = [[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]]
     
    class node:
        def __init__(self, point):
            self.left = None
            self.right = None
            self.point = point
            pass
        
    def median(lst):
        m = len(lst) / 2
        return lst[m], m
     
    def build_kdtree(data, d):
        data = sorted(data, key=lambda x: x[d])
        p, m = median(data)
        tree = node(p)
     
        del data[m]
        print data, p
     
        if m > 0: tree.left = build_kdtree(data[:m], not d)
        if len(data) > 1: tree.right = build_kdtree(data[m:], not d)
        return tree
     
    kd_tree = build_kdtree(T, 0)
    print kd_tree

可视化

可视化的话则要费点功夫保存中间结果,并恰当地展示出来

    # -*- coding:utf-8 -*-
    # Filename: kdtree.py
    # Author:hankcs
    # Date: 2015/2/4 15:01
    import copy
    import itertools
    from matplotlib import pyplot as plt
    from matplotlib.patches import Rectangle
    from matplotlib import animation
     
    T = [[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]]
     
     
    def draw_point(data):
        X, Y = [], []
        for p in data:
            X.append(p[0])
            Y.append(p[1])
        plt.plot(X, Y, 'bo')
     
     
    def draw_line(xy_list):
        for xy in xy_list:
            x, y = xy
            plt.plot(x, y, 'g', lw=2)
     
     
    def draw_square(square_list):
        currentAxis = plt.gca()
        colors = itertools.cycle(["r", "b", "g", "c", "m", "y", '#EB70AA', '#0099FF'])
        for square in square_list:
            currentAxis.add_patch(
                Rectangle((square[0][0], square[0][1]), square[1][0] - square[0][0], square[1][1] - square[0][1],
                          color=next(colors)))
     
     
    def median(lst):
        m = len(lst) / 2
        return lst[m], m
     
     
    history_quare = []
     
     
    def build_kdtree(data, d, square):
        history_quare.append(square)
        data = sorted(data, key=lambda x: x[d])
        p, m = median(data)
     
        del data[m]
        print data, p
     
        if m >= 0:
            sub_square = copy.deepcopy(square)
            if d == 0:
                sub_square[1][0] = p[0]
            else:
                sub_square[1][1] = p[1]
            history_quare.append(sub_square)
            if m > 0: build_kdtree(data[:m], not d, sub_square)
        if len(data) > 1:
            sub_square = copy.deepcopy(square)
            if d == 0:
                sub_square[0][0] = p[0]
            else:
                sub_square[0][1] = p[1]
            build_kdtree(data[m:], not d, sub_square)
     
     
    build_kdtree(T, 0, [[0, 0], [10, 10]])
    print history_quare
     
     
    # draw an animation to show how it works, the data comes from history
    # first set up the figure, the axis, and the plot element we want to animate
    fig = plt.figure()
    ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
    line, = ax.plot([], [], 'g', lw=2)
    label = ax.text([], [], '')
     
    # initialization function: plot the background of each frame
    def init():
        plt.axis([0, 10, 0, 10])
        plt.grid(True)
        plt.xlabel('x_1')
        plt.ylabel('x_2')
        plt.title('build kd tree (www.hankcs.com)')
        draw_point(T)
     
     
    currentAxis = plt.gca()
    colors = itertools.cycle(["#FF6633", "g", "#3366FF", "c", "m", "y", '#EB70AA', '#0099FF', '#66FFFF'])
     
    # animation function.  this is called sequentially
    def animate(i):
        square = history_quare[i]
        currentAxis.add_patch(
            Rectangle((square[0][0], square[0][1]), square[1][0] - square[0][0], square[1][1] - square[0][1],
                      color=next(colors)))
        return
     
    # call the animator.  blit=true means only re-draw the parts that have changed.
    anim = animation.FuncAnimation(fig, animate, init_func=init, frames=len(history_quare), interval=1000, repeat=False,
                                   blit=False)
    plt.show()
    anim.save('kdtree_build.gif', fps=2, writer='imagemagick')
image.png

搜索kd树

上面的代码其实并没有搜索kd树,现在来实现搜索。

搜索跟二叉树一样来,是一个递归的过程。先找到目标点的插入位置,然后往上走,逐步用自己到目标点的距离画个超球体,用超球体圈住的点来更新最近邻(或k最近邻)。以最近邻为例,实现如下(本实现由于测试数据简单,没有做超球体与超立体相交的逻辑):

    # -*- coding:utf-8 -*-
    # Filename: search_kdtree.py
    # Author:hankcs
    # Date: 2015/2/4 15:01
     
    T = [[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]]
     
     
    class node:
        def __init__(self, point):
            self.left = None
            self.right = None
            self.point = point
            self.parent = None
            pass
     
        def set_left(self, left):
            if left == None: pass
            left.parent = self
            self.left = left
     
        def set_right(self, right):
            if right == None: pass
            right.parent = self
            self.right = right
     
     
    def median(lst):
        m = len(lst) / 2
        return lst[m], m
     
     
    def build_kdtree(data, d):
        data = sorted(data, key=lambda x: x[d])
        p, m = median(data)
        tree = node(p)
     
        del data[m]
     
        if m > 0: tree.set_left(build_kdtree(data[:m], not d))
        if len(data) > 1: tree.set_right(build_kdtree(data[m:], not d))
        return tree
     
     
    def distance(a, b):
        print a, b
        return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5
     
     
    def search_kdtree(tree, d, target):
        if target[d] < tree.point[d]:
            if tree.left != None:
                return search_kdtree(tree.left, not d, target)
        else:
            if tree.right != None:
                return search_kdtree(tree.right, not d, target)
     
        def update_best(t, best):
            if t == None: return
            t = t.point
            d = distance(t, target)
            if d < best[1]:
                best[1] = d
                best[0] = t
     
     
        best = [tree.point, 100000.0]
        while (tree.parent != None):
            update_best(tree.parent.left, best)
            update_best(tree.parent.right, best)
            tree = tree.parent
        return best[0]
     
     
    kd_tree = build_kdtree(T, 0)
    print search_kdtree(kd_tree, 0, [9, 4])

输出

[8, 1] [9, 4]
[5, 4] [9, 4]  
[9, 6] [9, 4]
[9, 6]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容