KNN 算法是什么

Tip

显然,从定义来看,KNN 算法并不需要训练

KNN 允许我们根据一堆已知类别的样本判断一个新的样本 $\mathbf x$ 的类别,判断的原理是综合考虑距离它最近的 $K$ 个样本的类别

那么可想而知,有这么几个核心的问题值得研究

  • $K$ 应该是多少?
  • 如何度量“距离”?
  • 如何综合最近的 $K$ 个样本的类别的结果?

$K$ 越大,参考的样本就越多,理论上会越准确,但实际上考虑太多的样本会把距离不近的样本也包括进来

$K$ 太小的话,又会导致 KNN 的预测的方差比较大

所以,$K$ 不能太大也不能太小,那么在实际上操作的时候:可以让 $K$ 从小到大变化,观察验证集上的表现。用 $K$ 作为横轴,验证集上的表现作为纵轴画出曲线,这个曲线的拐点就是最佳的 $K$,这就是所谓的“肘部法则”

也可以考虑用交叉验证法选择最佳的 $K$

Tip

值得一提的是,$K$ 最好是奇数,这样比较不会出现 $K$ 个邻居里面每一个类别的数量都相等,导致 KNN 无法给出预测

假设每一个样本都用长度为 $d$ 的向量表示,那么样本之间的距离计算可以考虑用欧几里得距离

$$ \texttt{distance}(\mathbf x,\mathbf y)=\sqrt{\sum_{i=1}^d(x_i-y_i)^2} $$

在找到新样本的 $K$ 个最近的邻居之后,让这 $K$ 个邻居进行投票,投票数最多的类别就是新样本的类别

Scikit-Learn 库提供了 API 用于创建 KNN 分类器,在开始之前,我们先生成一个数据集用于演示

python

import numpy as np
np.random.seed(40)
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

X, y = make_classification(
    n_samples=100,
    n_features=2,
    n_informative=2,
    n_redundant=0,
    n_clusters_per_class=1,
    random_state=40,
)
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.2, random_state=40
)

X_valid, X_test, y_valid, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, random_state=40
)

为了对数据集有更好的了解,这里可以画一个散点图。结果如下所示

接下来创建一个 KNN 分类器,在训练集 X_train 上“训练”一个 KNN 模型

python

from sklearn.neighbors import KNeighborsClassifier
neigh = KNeighborsClassifier(n_neighbors=5)
neigh.fit(X_train, y_train)
Warning

这里其实没有训练!

现在假设有一个新的样本 [0, 0],就可以用这个模型判断它的类别

python

test_point = [0, 0]
neigh.predict([test_point])
# array([1])

同样的,可以画出散点图看一下,其中的黑色 $\times$ 表示邻居,红色的 $\times$ 是用于测试的样本 [0, 0]

根据结果来看,4 个邻居是类别 1,1 个邻居是类别 0,所以 [0, 0] 的类别是 1

不推荐,因为样本不平衡会导致用 KNN 算法得到的分类几乎总是样本多的类别

一个有趣的问题是:$K$ 越大 KNN 越复杂?还是 $K$ 越小?答案:$K$ 越小模型越复杂,也因此更容易“过拟合”

这句话可以反过来理解,$K$ 最大的情况下等于所有样本的个数 $N$,此时只要是新样本,会总是被判断为 $N$ 个样本中最多的类别,这样的模型显然太简单了

显然是的,不然计算距离的时候,数值比较大的特征对距离的影响更大

可以,比如取 $K$ 个邻居的平均值,但一般 KNN 还是用来做分类问题

朴素的 KNN 采用投票法,即每一个邻居节点的贡献度是一样的,一个改进方向是:修改每一个邻居节点的贡献度,比如越近的邻居贡献度越大。一个常用的权重是 $1/d$,这里的 $d$ 是到邻居到新样本的距离