本篇对应全书第三章,讲的是 k k 近邻法。近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法,输入为实例的特征向量,对应于特征空间中的点,输出为实例的类别,可以取多类。 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
)
}
,其中,
xi∈X⊆Rn
x
i
∈
X
⊆
R
n
为实例的特征向量,
yi∈Y={c1,c2,…,cK}
y
i
∈
Y
=
{
c
1
,
c
2
,
…
,
c
K
}
为实例的类别,
i=1,2,…,N
i
=
1
,
2
,
…
,
N
;新的输入实例特征向量
x
x
;
输出:实例所属类别
y
y
。
(1)根据给定的距离度量,在训练集中找到与
x
x
最邻近的个点,涵盖这
k
k
个点的的邻域记作
Nk(x)
N
k
(
x
)
;
(2)在
Nk(x)
N
k
(
x
)
中根据分类决策规则(如多数表决)决定
x
x
的类别:
k
k
近邻法的特殊情况是的情形,称为最近邻算法。对于输入的实例点
x
x
,最近邻法将训练集中与最近邻点的类作为
x
x
的类。
近邻法中,当训练集、距离度量、
k
k
值及分类决策规则确定后,对于任何一个新的输入实例,它所属的类唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间里的每个点所属的类。
1.2、距离度量
特征空间中两个实例点的距离是两个实例点相似程度的反映。近邻法的特征空间一般是 n n 维实数向量空间,使用的距离是欧氏距离,但也可以是其它距离,如更一般的 Lp L p 距离或Minkowski距离。
1.3、k值的选择
k
k
值的选择会对近邻法的结果产生重大影响。
如果选择较小的
k
k
值,意味着整体模型变复杂,学习的近似误差(approximation error)会减小,估计误差(estimation error)会增大;如果选择较大的值,意味着整体模型变简单,学习的估计误差会减小,近似误差会增大。因此,
k
k
值的选择,反映了对近似误差与估计误差之间的权衡。
在应用中,值一般取一个较小的数值。通常采用交叉验证法来选取最优的
k
k
值。
1.4、分类决策规则
近邻法中的分类决策规则往往是多数表决(对应于0-1损失函数下的经验风险最小化),即由输入实例的 k k 个邻近的训练实例中的多数类决定输入实例的类。
1.5、kd树
实现近邻法时,主要考虑的问题是如何对训练数据进行快速
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
以上为本文的全部参考文献,对原作者表示感谢。