《统计学习方法》系列(3)

本文详细介绍了K近邻算法的基本原理及其应用。包括距离度量、K值选择、分类决策规则等内容,并通过手写代码和Sklearn库实现了算法。此外还讨论了K近邻搜索的优化方法kd树。

  本篇对应全书第三章,讲的是 k k 近邻法。k近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法,输入为实例的特征向量,对应于特征空间中的点,输出为实例的类别,可以取多类。 k k 近邻法不具有显示的学习过程,它实际上利用训练集对特征向量空间进行划分,并作为其分类的模型。k近邻法1968年由Cover和Hart提出。


1、理论讲解

  距离度量、k值的选择及分类决策规则是k近邻法的三个基本要素,本节将首先叙述k近邻算法,然后讨论它的三个基本要素。

1.1、k近邻算法

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

输入:训练集 T={(x1,y1),(x2,y2),,(xN,yN)} T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x N , y N ) } ,其中, xiXRn x i ∈ X ⊆ R n 为实例的特征向量, yiY={c1,c2,,cK} y i ∈ Y = { c 1 , c 2 , … , c K } 为实例的类别, i=1,2,,N i = 1 , 2 , … , N ;新的输入实例特征向量 x x
输出:实例x所属类别 y y
(1)根据给定的距离度量,在训练集T中找到与 x x 最邻近的k个点,涵盖这 k k 个点的x的邻域记作 Nk(x) N k ( x )
(2)在 Nk(x) N k ( x ) 中根据分类决策规则(如多数表决)决定 x x 的类别y

y=argmaxcjxiNk(x)I(yi=cj);i=1,2,,N,j=1,2,,K y = arg ⁡ m a x c j ∑ x i ∈ N k ( x ) I ( y i = c j ) ; i = 1 , 2 , … , N , j = 1 , 2 , … , K

   k k 近邻法的特殊情况是k=1的情形,称为最近邻算法。对于输入的实例点 x x ,最近邻法将训练集中与x最近邻点的类作为 x x 的类。
  k近邻法中,当训练集、距离度量、 k k 值及分类决策规则确定后,对于任何一个新的输入实例,它所属的类唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间里的每个点所属的类。

1.2、距离度量

  特征空间中两个实例点的距离是两个实例点相似程度的反映。k近邻法的特征空间一般是 n n 维实数向量空间Rn,使用的距离是欧氏距离,但也可以是其它距离,如更一般的 Lp L p 距离或Minkowski距离。

1.3、k值的选择

   k k 值的选择会对k近邻法的结果产生重大影响。
  如果选择较小的 k k 值,意味着整体模型变复杂,学习的近似误差(approximation error)会减小,估计误差(estimation error)会增大;如果选择较大的k值,意味着整体模型变简单,学习的估计误差会减小,近似误差会增大。因此, k k 值的选择,反映了对近似误差与估计误差之间的权衡。
  在应用中,k值一般取一个较小的数值。通常采用交叉验证法来选取最优的 k k 值。

1.4、分类决策规则

  k近邻法中的分类决策规则往往是多数表决(对应于0-1损失函数下的经验风险最小化),即由输入实例的 k k 个邻近的训练实例中的多数类决定输入实例的类。

1.5、kd树

  实现k近邻法时,主要考虑的问题是如何对训练数据进行快速 k k 近邻搜索,这点在特征空间的维数大及训练数据容量大时尤其必要。
  k近邻法最简单的实现方法是线性扫描(linear scan),这时要计算输入实例与每一个训练实例的距离,当训练集很大时,计算耗时,不可行。
  为了提高 k k 近邻搜索的效率,可以考虑使用特殊的结构存储训练数据,以减少计算距离的次数,kd树(kd tree)就是一种选择。关于kd树,这里不作进一步解释,读者可阅读参考文献[1][2]。

2、代码实现

2.1、手工实现

from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter


class KNeighborsClassifier:
        def __init__(self, X_train, y_train, n_neighbors = 5, p = 2):
                self.X_train = X_train
                self.y_train = y_train                                                                
                self.n_neighbors = n_neighbors                                                        
                self.p = p                                                                            

        def cal_dist(self, X1, X2):                                                                   
                dist = np.linalg.norm(X1-X2, ord = self.p)                                            
                return dist                                                                           

        def predict(self, X_test):                                                                    
                label_list = []                                                                       
                for X in X_test:                                                                      
                        dist_list = []                                                                
                        for X_i, y_i in zip(self.X_train, self.y_train):                              
                                dist = self.cal_dist(X, X_i)                                          
                                dist_list.append((dist, y_i))                                         
                        dist_list.sort()                                                              
                        knn_list = dist_list[: self.n_neighbors]                                      
                        label = Counter([_[1] for _ in knn_list]).most_common(1)[0][0]                
                        label_list.append(label)                                                      
                return np.array(label_list)                                                           

        def score(self, X_test, y_test):                                                              
                total_num = len(X_test)                                                               
                pre = (self.predict(X_test) == y_test).sum()                                          
                score = pre/total_num                                                                 
                return score                                                                          


if __name__ == "__main__":                                                                            
        iris = load_iris()                                                                            
        X = iris.data[:100, :2]                                                                       
        y = iris.target[:100]                                                                         
        y[y == 0] = -1                                                                                
        xlabel = iris.feature_names[0]                                                                
        ylabel = iris.feature_names[1]                                                                

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)  

        X_0 = X_train[y_train == -1]                                                                  
        X_1 = X_train[y_train == 1]                                                                   

        plt.figure("knn-mine")                                                                        
        plt.scatter(X_0[:, 0], X_0[:, 1], label = '-1')                                               
        plt.scatter(X_1[:, 0], X_1[:, 1], label = '1')                                                
        plt.xlabel(xlabel)                                                                            
        plt.ylabel(ylabel)                                                                            
        plt.legend()        

        clf = KNeighborsClassifier(X_train, y_train)
        score = clf.score(X_test, y_test)
        print "score : %s" % score

        y_pre = clf.predict(X_test)
        X_test_pre_0 = X_test[y_pre == -1]
        X_test_pre_1 = X_test[y_pre == 1]
        plt.scatter(X_test_pre_0[:, 0], X_test_pre_0[:, 1], color = 'r', label = 'pre -1')
        plt.scatter(X_test_pre_1[:, 0], X_test_pre_1[:, 1], color = 'k', label = 'pre 1')
        plt.legend()
        plt.show()

2.2、sklearn实现

from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import train_test_split


if __name__ == "__main__":
        iris = load_iris()
        X = iris.data[:100, :2]
        y = iris.target[:100]
        y[y == 0] = -1
        xlabel = iris.feature_names[0]
        ylabel = iris.feature_names[1]

        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)

        X_0 = X_train[y_train == -1]
        X_1 = X_train[y_train == 1]

        plt.figure("knn-sklearn")
        plt.scatter(X_0[:, 0], X_0[:, 1], label = '-1')
        plt.scatter(X_1[:, 0], X_1[:, 1], label = '1')
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        plt.legend()

        clf = KNeighborsClassifier()
        clf.fit(X_train, y_train)
        score = clf.score(X_test, y_test)
        print "score : %s" % score

        y_pre = clf.predict(X_test)
        X_test_pre_0 = X_test[y_pre == -1]
        X_test_pre_1 = X_test[y_pre == 1]
        plt.scatter(X_test_pre_0[:, 0], X_test_pre_0[:, 1], color = 'r', label = 'pre -1')
        plt.scatter(X_test_pre_1[:, 0], X_test_pre_1[:, 1], color = 'k', label = 'pre 1')
        plt.legend()
        plt.show()

代码已上传至github:https://github.com/xiongzwfire/statistical-learning-method


参考文献

[1] https://www.joinquant.com/post/2227
[2] https://www.joinquant.com/post/2843
[3] https://leileiluoluo.com/posts/kdtree-algorithm-and-implementation.html
[4] https://www.joinquant.com/post/3227?f=study&m=math
[5] https://github.com/wzyonggege/statistical-learning-method
以上为本文的全部参考文献,对原作者表示感谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值