KNN算法

  跟着Udacity课程学完了无人驾驶第一个term,本来想深入研究一下机器视觉,但是视觉方向不能很好地与工作项目契合,所以决定先巩固一下传统机器学习算法。这个系列基于《机器学习实战》和《统计学系方法》展开,既有理论推导,又有代码实例,这样督促自己形成完备的知识体系。

简介

  KNN(k-nearest neighbour),也成为k近邻算法,是一种最简单实用的机器学习分类算法。该算法本质是通过距离函数在向量间进行相似性检索,从而确定待分类点类别。之所以称之为最简单的分类算法,是因为k近邻算法没有显式的学习过程,其关键参数的含义较其他算法容易理解。

  KNN核心思想是“相近相似”,一般认为两个物体距离越近其关系越紧密,如果两个向量距离很近那么它们两个大概率属于同一类。有很多算法核心思想都是基于这个理论,比如反距离权重差值法等。

算法模型

语言描述

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

数学描述

  • 输入

  训练数据集

T={(x1,y1),(x2,y2),...,(xN,yN)}(1)

其中, xiXRn 为实例的特征向量, yiY={c1,c2,...,ck} 为实例类别, i=1,2,...,N

  • 输出

  实例 x 所属的类y

  1. 根据给定的距离度量,在训练集 T 中找出与x最邻近的 k 个点,涵盖这k个点的 x 邻域计作Nk(x)
  2. Nk(x) 中根据分类规则,一般为多数表决,来决定 x 的类别y
    y=argmaxcjxiNk(x)I(yi=cj),i=1,2,...,N,j=1,2,...,K(2)

    其中, I 为指示函数,即当yi=ci I 为1,否则I为0。

关键参数

距离度量

  特征空间中两个实例点的距离是两个实例点相似程度的反映,k近邻模型特征空间一般采用 n 维实数向量空间Rn,使用的距离是欧式距离,除此之外还可以使用其他距离。

闵可夫斯基距离(MinKowski distance)

  闵可夫斯基距离也叫 Lp 距离,假设特征空间 X n维实数向量空间 Rn xi,xjX xi=(x1i,x2i,...,xni)T xj=(x1j,x2j,...,xnj)T ,则 xi xj Lp 距离的定义为:

Lp(xi,xj)=(l=1n|x(l)ix(l)j|p)1p(3)

欧式距离

  当闵可夫斯基距离中的参数 p=2 时,称为欧式距离,即

L2(xi,xj)=(l=1n(x(l)ix(l)j)2)12(4)

曼哈顿距离

  当闵可夫斯基距离中的参数 p=1 时,称为曼哈顿距离,即

L1(xi,xj)=l=1n|x(l)ix(l)j|(5)

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

L(xi,xj)=maxl|x(l)ix(l)j|(6)

  其实闵可夫斯基距离的定义与p-范数概念一致,向量的范数可以理解为向量的长度,或者是两个点之间的距离。

k 值的选择

  k值的选择对k近邻的结果产生较大影响。

  • k=1

      当 k=1 时,k近邻成为最近邻,只有与输入实例最近的点才会影响预测,即最近的实例点的类别就是输入实例的类别。

    • 较小 k

      只有与输入实例较近的训练实例才会对结果起作用,预测结果会对临近的实例点非常敏感,如果一旦临近的实例点恰好是噪声,那么预测就会出错。这样做的好处是训练误差会减小,但是估计误差会增大,简而言之就是容易出现过拟合。

    • 较大k

      相当于选择较大邻域的训练实例进行预测,这时与输入实例较远的训练实例也会对预测起作用。这么做优点是可以减少估计误差,但是训练误差会增大。

    • k=N

      当 k=N 时,即k与类别个数一致,那么无论输入实例是什么,都会把输入实例的类别判断为训练实例中类别最多的的类,这样是不对的。

      应用中, k 值一般取一个比较小的值,通过交叉验证的方式选取最优的k值。

    分类决策规则

      k近邻算法的分类决策规则一般采用多数表决,即由输入实例的k个临近的训练实例中的多数类决定输入实例的类别。

    多数表决规则(majority voting rule)的数学描述

      如果分类的损失函数为0-1损失函数,分类函数为

    f:Rn{c1,c2,...,cK}(7)

    那么误分类的概率是

    P(Yf(X))=1P(Y=f(X))(8)

    对给定的实例 xX ,其最邻近的 k 个训练实例点构成集合Nk(x),假设输入实例的类别是 cj ,那么误分类率是:
    1kxiNk(x)I(yicj)=11kxiNk(x)I(yi=cj)(9)

    要让误分类率最小,就让 xiNk(x)I(yi=cj) 最大,即 cj 取个数最多的类别。

    python代码示例

      示例为《机器学习实战》手写体的例子

    #encoding:utf8
    import numpy as np
    import glob
    import os
    
    #knn算法主体
    def knn_classifier(x_train,y_train,x_test,k):
        #计算向量拘留
        dis = distance(x_train,x_test)
        #根据距离排序
        sort_index = dis.argsort()
        classCount = {}
        #取前k个邻近的点
        for i in range(k):
            #获取第i个点的类别
            label = y_train[sort_index[i]]
            classCount[label] = classCount.get(label,0) + 1
        #进行多数表决投票
        classCount = sorted(classCount.items(),lambda x,y:cmp(x[1],y[1]),reverse=True)
        return classCount[0][0]
    
    
    #欧式距离计算函数
    def distance(x_train,x_test):
        datasize = x_train.shape[0]
        #tile可以把一个向量重复叠加为一个矩阵
        diff = np.tile(x_test,(datasize,1)) - x_train
        squareDiff = diff ** 2
        squareSum = squareDiff.sum(axis = 1)
        dis = np.sqrt(squareSum)
        return dis
    
    #把手写体32*32的像素矩阵转化为1*2014的向量
    def img2Vector(filename):
        returnVector = np.zeros((1,1024))
        file = open(filename)
        for i in range(32):
            lineString = file.readline()
            for j in range(32):
                returnVector[0,32 * i + j] = int(lineString[j])
        return returnVector
    
    
    def load_train_data():
        train_data_file_path = glob.glob('./digits/trainingDigits/*.txt')
        train_label = []
        filenum = len(train_data_file_path)
        train_data = np.zeros((filenum,1024))
        for i in range(filenum):
            file_path = train_data_file_path[i]
            label = os.path.basename(file_path).split('_')[0]
            train_label.append(label)
            train_data[i:] = img2Vector(file_path)
        return train_data,train_label
    
    def hand_writing_class_test():
        train_data,train_label = load_train_data()
        test_data_file_path = glob.glob('./digits/testDigits/*.txt')
        error = 0.0
        count = 0
        for file_path in test_data_file_path:
            file = open(file_path)
            test_label = os.path.basename(file.name).split('_')[0]
            test_data = img2Vector(file_path)
            predict_label = knn_classifier(train_data,train_label,test_data,3)
            count += 1
            if predict_label!=test_label:
                print "predict_label: ",predict_label,", test_label: ",test_label
                error += 1
        print 'error rate: ',error/count
    
    def main():
        hand_writing_class_test()
    
    if __name__=='__main__':
        main()

      输出为分类错误的示例类别及最终的分类错误率

    predict_label:  1 , test_label:  8
    predict_label:  3 , test_label:  8
    predict_label:  7 , test_label:  9
    predict_label:  9 , test_label:  3
    predict_label:  1 , test_label:  8
    predict_label:  1 , test_label:  9
    predict_label:  1 , test_label:  8
    predict_label:  9 , test_label:  3
    predict_label:  7 , test_label:  1
    predict_label:  6 , test_label:  5
    predict_label:  3 , test_label:  5
    predict_label:  6 , test_label:  8
    error rate:  0.0126849894292
    [Finished in 23.7s]
    

    代码示例详见KNN代码示例

    总结

      简单介绍了knn算法的基本思想和关键参数,并根据书中的例子进行了实践,过程并不复杂。但是,这个代码示例用穷举法一一计算输入实例与训练数据之间的距离,复杂度较高,缺点比较明显。针对这个问题,下篇文章将介绍kd树方法。

    参考

    • 统计学习方法,李航,清华大学出版社

    • 机器学习实战,Peter Harrington,人民邮电出版社

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值