工作原理

“近朱者赤,近墨者黑”可以说是 KNN 的工作原理。整个计算过程分为三步:

  1. 计算待分类物体与其他物体之间的距离;
  2. 统计距离最近的 K 个邻居;
  3. 对于 K 个最近的邻居,它们属于哪个分类最多,待分类物体就属于哪一类。

K 值的选择

K 值的选择是很重要的

如果 K 值比较小,就相当于未分类物体与它的邻居非常接近才行。这样产生的一个问题就是,如果邻居点是个噪声点,那么未分类物体的分类也会产生误差,这样 KNN 分类就会产生过拟合。

如果 K 值比较大,相当于距离过远的点也会对未知物体的分类产生影响,虽然这种情况的好处是鲁棒性强,但是不足也很明显,会产生欠拟合情况,也就是没有把未分类物体真正分类出来。

所以 K 值应该是个实践出来的结果,并不是事先而定的。在工程上,一般采用交叉验证的方式选取 K 值。

交叉验证的思路就是,把样本集中的大部分样本作为训练集,剩余的小部分样本用于预测,来验证分类模型的准确性。所以在 KNN 算法中,一般会把 K 值选取在较小的范围内,同时在验证集上准确率最高的那一个最终确定作为 K 值。

距离如何计算

关于距离的计算方式有下面五种方式:

  1. 欧氏距离;
  2. 曼哈顿距离;
  3. 闵可夫斯基距离;
  4. 切比雪夫距离;
  5. 余弦距离。

其中前三种距离是 KNN 中最常用的距离。

KD 树

KNN 的计算过程是大量计算样本点之间的距离。为了减少计算距离次数,提升 KNN 的搜索效率,人们提出了 KD 树(K-Dimensional 的缩写)。KD 树是对数据点在 K 维空间中划分的一种数据结构。在 KD 树的构造中,每个节点都是 k 维数值点的二叉树。既然是二叉树,就可以采用二叉树的增删改查操作,这样就大大提升了搜索效率。

用 KNN 做回归

KNN 不仅可以做分类,还可以做回归。

# 分类
from sklearn.neighbors import KNeighborsClassifier
# 回归
from sklearn.neighbors import KNeighborsRegressor

构造函数 

KNeighborsClassifier(n_neighbors=5, weights=‘uniform’, algorithm=‘auto’, leaf_size=30)

1、n_neighbors:即 KNN 中的 K 值,代表的是邻居的数量,一般使用默认值 5。

2、weights:是用来确定邻居的权重,有三种方式:

  • weights=uniform,代表所有邻居的权重相同;
  • weights=distance,代表权重是距离的倒数,即与距离成反比;
  • 自定义函数,可以自定义不同距离所对应的权重。大部分情况下不需要自己定义函数。

3、algorithm:用来规定计算邻居的方法,它有四种方式:

  • algorithm=auto,根据数据的情况自动选择适合的算法,默认情况选择 auto;
  • algorithm=kd_tree,也叫作 KD 树,是多维空间的数据结构,方便对关键数据进行检索,不过 KD 树适用于维度少的情况,一般维数不超过 20,如果维数大于 20 之后,效率反而会下降;
  • algorithm=ball_tree,也叫作球树,它和 KD 树一样都是多维空间的数据结果,不同于 KD 树,球树更适用于维度大的情况;
  • algorithm=brute,也叫作暴力搜索,它和 KD 树不同的地方是在于采用的是线性扫描,而不是通过构造树结构进行快速检索。当训练集大的时候,效率很低。

4、leaf_size:代表构造 KD 树或球树时的叶子数,默认是 30,调整 leaf_size 会影响到树的构造和搜索速度。

实例

加载数据

from sklearn.datasets import load_digits
# 加载数据
digits = load_digits()
data = digits.data
# 数据探索
print(data.shape)
# 查看第一幅图像
print(digits.images[0])
# 第一幅图像代表的数字含义
print(digits.target[0])
# 将第一幅图像显示出来
plt.gray()
plt.imshow(digits.images[0])
plt.show()

采用 Z-Score 规范化

from sklearn.model_selection import train_test_split
from sklearn import preprocessing
# 分割数据,将25%的数据作为测试集,其余作为训练集(可以指定其他比例的数据作为训练集)
train_x, test_x, train_y, test_y = train_test_split(data, digits.target, test_size=0.25, random_state=33)
# 采用Z-Score规范化
ss = preprocessing.StandardScaler()
train_ss_x = ss.fit_transform(train_x)
test_ss_x = ss.transform(test_x)

构造 KNN 分类器

from sklearn.metrics import accuracy_score
from sklearn.neighbors import KNeighborsClassifier
# 创建KNN分类器
knn = KNeighborsClassifier() 
knn.fit(train_ss_x, train_y) 
predict_y = knn.predict(test_ss_x) 
print("KNN准确率: %.4lf" % accuracy_score(test_y, predict_y))

构造 SVM 分类器、Naive Bayes 分类器和 CART 决策树分类器

# 创建SVM分类器
from sklearn.svm import SVC
svm = SVC()
svm.fit(train_ss_x, train_y)
predict_y=svm.predict(test_ss_x)
print('SVM准确率: %0.4lf' % accuracy_score(test_y, predict_y))
# 采用Min-Max规范化
mm = preprocessing.MinMaxScaler()
train_mm_x = mm.fit_transform(train_x)
test_mm_x = mm.transform(test_x)
# 创建Naive Bayes分类器
from sklearn.naive_bayes import MultinomialNB
mnb = MultinomialNB()
mnb.fit(train_mm_x, train_y) 
predict_y = mnb.predict(test_mm_x) 
print("多项式朴素贝叶斯准确率: %.4lf" % accuracy_score(test_y, predict_y))
# 创建CART决策树分类器
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
dtc.fit(train_mm_x, train_y) 
predict_y = dtc.predict(test_mm_x) 
print("CART决策树准确率: %.4lf" % accuracy_score(test_y, predict_y))

运行结果如下:

KNN准确率: 0.9756
SVM准确率: 0.9867
多项式朴素贝叶斯准确率: 0.8844
CART决策树准确率: 0.8556

注意:

做多项式朴素贝叶斯分类的时候,传入的数据不能有负数。而 Z-Score 会将数值规范化为一个标准的正态分布,即均值为 0,方差为 1,数值会包含负数。因此需要采用 Min-Max 规范化,将数据规范化到[0,1]范围内。