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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012294618/article/details/79678219

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


1、理论讲解

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

1.1、k近邻算法

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

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

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

1.2、距离度量

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

1.3、k值的选择

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

1.4、分类决策规则

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

1.5、kd树

  实现 k 近邻法时,主要考虑的问题是如何对训练数据进行快速 k 近邻搜索,这点在特征空间的维数大及训练数据容量大时尤其必要。
   k 近邻法最简单的实现方法是线性扫描(linear scan),这时要计算输入实例与每一个训练实例的距离,当训练集很大时,计算耗时,不可行。
  为了提高 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
以上为本文的全部参考文献,对原作者表示感谢。

猜你喜欢

转载自blog.csdn.net/u012294618/article/details/79678219