本篇对应全书第三章,讲的是 近邻法。 近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法,输入为实例的特征向量,对应于特征空间中的点,输出为实例的类别,可以取多类。 近邻法不具有显示的学习过程,它实际上利用训练集对特征向量空间进行划分,并作为其分类的模型。 近邻法1968年由Cover和Hart提出。
1、理论讲解
距离度量、k值的选择及分类决策规则是k近邻法的三个基本要素,本节将首先叙述k近邻算法,然后讨论它的三个基本要素。
1.1、k近邻算法
k近邻算法简单、直观:给定一个训练集,对新的输入实例,在训练集中找到与该实例最邻近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类。
输入:训练集
,其中,
为实例的特征向量,
为实例的类别,
;新的输入实例特征向量
;
输出:实例
所属类别
。
(1)根据给定的距离度量,在训练集
中找到与
最邻近的
个点,涵盖这
个点的
的邻域记作
;
(2)在
中根据分类决策规则(如多数表决)决定
的类别
:
近邻法的特殊情况是
的情形,称为最近邻算法。对于输入的实例点
,最近邻法将训练集中与
最近邻点的类作为
的类。
近邻法中,当训练集、距离度量、
值及分类决策规则确定后,对于任何一个新的输入实例,它所属的类唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间里的每个点所属的类。
1.2、距离度量
特征空间中两个实例点的距离是两个实例点相似程度的反映。 近邻法的特征空间一般是 维实数向量空间 ,使用的距离是欧氏距离,但也可以是其它距离,如更一般的 距离或Minkowski距离。
1.3、k值的选择
值的选择会对
近邻法的结果产生重大影响。
如果选择较小的
值,意味着整体模型变复杂,学习的近似误差(approximation error)会减小,估计误差(estimation error)会增大;如果选择较大的
值,意味着整体模型变简单,学习的估计误差会减小,近似误差会增大。因此,
值的选择,反映了对近似误差与估计误差之间的权衡。
在应用中,
值一般取一个较小的数值。通常采用交叉验证法来选取最优的
值。
1.4、分类决策规则
近邻法中的分类决策规则往往是多数表决(对应于0-1损失函数下的经验风险最小化),即由输入实例的 个邻近的训练实例中的多数类决定输入实例的类。
1.5、kd树
实现
近邻法时,主要考虑的问题是如何对训练数据进行快速
近邻搜索,这点在特征空间的维数大及训练数据容量大时尤其必要。
近邻法最简单的实现方法是线性扫描(linear scan),这时要计算输入实例与每一个训练实例的距离,当训练集很大时,计算耗时,不可行。
为了提高
近邻搜索的效率,可以考虑使用特殊的结构存储训练数据,以减少计算距离的次数,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
以上为本文的全部参考文献,对原作者表示感谢。