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=ciI为1,否则I为0。

关键参数

距离度量

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

闵可夫斯基距离(MinKowski distance)

  闵可夫斯基距离也叫Lp距离,假设特征空间Xn维实数向量空间Rnxi,xjXxi=(x1i,x2i,...,xni)Txj=(x1j,x2j,...,xnj)T,则xixjLp距离的定义为:

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,人民邮电出版社

阅读更多

没有更多推荐了,返回首页