• K近邻法


    K近邻算法

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

      算法详解:

      输入:训练数据集

                

      其中,为实例的特征向量,为实例的类别, 实例特征向量 ;

      输出:实例所属的类 ;

      (1)根据给定的距离度量,在训练集中找出与最邻近的个点,涵盖这个点的的邻域记作 ;

      (2)在中根据分类决策规则(如多数表决)决定的类别

                 

      注:为指示函数,即当为1,否则为0

     

    K近邻模型

      模型三要素:距离度量方法k值的选择分类决策规则

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

                                                                

    距离度量

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

    Lp距离定义如下:

                  

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

                    

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

       

                     

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

                    

      用图表示如下:

                         

      

    k值的选择

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

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

    分类决策规则

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

                  

     

        是近邻集合,要使左边最小,右边的必须最大,所以多数表决=经验最小化 。

    k近邻法的实现:kd树

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

    构造kd树

    对数据集T中的子集S初始化S=T,取当前节点node=root取维数的序数i=0,对S递归执行:找出S的第i维的中位数对应的点,通过该点,且垂直于第i维坐标轴做一个超平面。该点加入node的子节点。该超平面将空间分为两个部分,对这两个部分分别重复此操作(S=S',++i,node=current),直到不可再分。

     

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

          

         构造一个平衡kd树 。

     

    Python代码实现:

      

    # -*- coding:utf-8 -*-
    
    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')
    View Code

    搜索kd树

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

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

     

        # -*- coding:utf-8 -*-
         
        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])
    View Code

     

     输出:

    [8, 1] [9, 4]
    [5, 4] [9, 4]
    [9, 6] [9, 4]
    [9, 6]

    可见对于点[9, 4],在n=6的数据集中,kdtree算法一共只进行了3次计算。

    K-近邻算法实现手写数字识别     

      1 #-*- coding: utf-8 -*-
      2 
      3 from numpy import *
      4 from os import listdir
      5 import operator
      6 
      7 # 读取数据到矩阵
      8 def img2vector(filename):
      9     # 创建向量
     10     returnVect = zeros((1,1024))
     11     
     12     # 打开数据文件,读取每行内容
     13     fr = open(filename)
     14 
     15     for i  in range(32):
     16         # 读取每一行
     17         lineStr = fr.readline()
     18         
     19         # 将每行前32字符转成int存入向量
     20         for j in range(32):
     21             returnVect[0,32*i+j] = int(lineStr[j])
     22 
     23     return returnVect
     24 
     25 # kNN算法实现    
     26 def classify0(inX, dataSet, labels, k):
     27     # 获取样本数据数量
     28     dataSetSize = dataSet.shape[0]
     29     
     30     # 矩阵运算,计算测试数据与每个样本数据对应数据项的差值
     31     diffMat = tile(inX, (dataSetSize,1)) - dataSet
     32     
     33     # sqDistances 上一步骤结果平方和
     34     sqDiffMat = diffMat**2
     35     sqDistances = sqDiffMat.sum(axis=1)
     36     
     37     # 取平方根,得到距离向量
     38     distances = sqDistances**0.5
     39     
     40     # 按照距离从低到高排序
     41     sortedDistIndicies = distances.argsort()     
     42     classCount={}          
     43     
     44     # 依次取出最近的样本数据
     45     for i in range(k):
     46         # 记录该样本数据所属的类别
     47         voteIlabel = labels[sortedDistIndicies[i]]
     48         classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
     49     
     50     # 对类别出现的频次进行排序,从高到低
     51     sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
     52     
     53     # 返回出现频次最高的类别
     54     return sortedClassCount[0][0]
     55 
     56 # 算法测试    
     57 def handwritingClassTest():
     58     # 样本数据的类标签列表
     59     hwLabels = []
     60     
     61     # 样本数据文件列表
     62     trainingFileList = listdir('digits/trainingDigits')
     63     m = len(trainingFileList)
     64     
     65     # 初始化样本数据矩阵(M*1024)
     66     trainingMat = zeros((m,1024))
     67     
     68     # 依次读取所有样本数据到数据矩阵
     69     for i in range(m):
     70         # 提取文件名中的数字
     71         fileNameStr = trainingFileList[i]
     72         fileStr = fileNameStr.split('.')[0]
     73         classNumStr = int(fileStr.split('_')[0])
     74         hwLabels.append(classNumStr)
     75         
     76         # 将样本数据存入矩阵
     77         trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)
     78     
     79     # 循环读取测试数据
     80     testFileList = listdir('digits/testDigits')
     81     
     82     # 初始化错误率
     83     errorCount = 0.0
     84     mTest = len(testFileList)
     85     
     86     # 循环测试每个测试数据文件
     87     for i in range(mTest):
     88         # 提取文件名中的数字
     89         fileNameStr = testFileList[i]
     90         fileStr = fileNameStr.split('.')[0]
     91         classNumStr = int(fileStr.split('_')[0])
     92         
     93         # 提取数据向量
     94         vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
     95         
     96         # 对数据文件进行分类
     97         classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
     98         
     99         # 打印KNN算法分类结果和真实的分类
    100         print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
    101         
    102         # 判断KNN算法结果是否准确
    103         if (classifierResult != classNumStr): errorCount += 1.0
    104     
    105     # 打印错误率
    106     print "
    the total number of errors is: %d" % errorCount
    107     print "
    the total error rate is: %f" % (errorCount/float(mTest))
    108 
    109 # 执行算法测试
    110 handwritingClassTest()
    View Code

  • 相关阅读:
    20180604_Myeclipse下配置SVN报错问题 svn
    20180603_升级Win10后,远程连接桌面连接,出现身份验证错误!
    20180603_navicat 连接sqlserver提示要安装 sql server native client
    VB.net程序实现分页
    多线程Demo
    多线程Demo VB.net
    SQLServer数据库子结构
    SQLServer数据库常用命令
    传播路径图调查2013年初
    拾遗
  • 原文地址:https://www.cnblogs.com/ktao/p/7363905.html
Copyright © 2020-2023  润新知