回顾:
KNN的两个核心特性是非参数性和惰性学习,它们共同定义了该算法的行为和适用性:
- 非参数性:KNN不假设数据服从任何特定的底层分布。这使其具有高度灵活性,适用于数据可能不遵循理论分布的实际场景 。与线性回归等参数模型相比,这是一个显著的优势,因为线性回归需要对数据关系做出特定假设 。
- 惰性学习:如前所述,KNN在预测时才进行所有计算,没有显式的训练阶段 。这意味着模型简单地存储整个训练数据集,并在每次查询时进行计算 。
sklearn提供了很多用于机器学习的数据集,首先拿鸢尾花的数据集,练习KNN算法在分类任务上的表现。
1.鸢尾花分类任务
【1】准备数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 导入所需的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
from matplotlib.colors import ListedColormap
# 加载数据
from sklearn.datasets import load_iris # 鸢尾花数据集
data = load_iris() # 是一个字典
# data的键中,关键的有:data, target(0, 1, 2), target_names(['setosa', 'versicolor', 'virginica'])
# feature_names: ['sepal length (cm)','sepal width (cm)','petal length (cm)','petal width (cm)']
data_all = data["data"] # 特征数据
target = data["target"] # 标签数据
# display(data_all.shape, target.shape) # (150, 4) (150,)
【2】方法一:最基本的KNN分类器
1
2
3
4
5
# 数据分出训练数据和测试数据(0.3)
X_train, X_test, y_train, y_test = train_test_split(data_all, target, test_size=0.3, random_state=42)
# 创建KNN算法实例
knn = KNeighborsClassifier() # 默认k=5,等权重
【3】训练:
1
knn.fit(X_train, y_train)
【4】预测:
1
2
y_pred = knn.predict(X_test)
y_pred
【5】计算准确率:
1
accuracy_score(y_test, y_pred)
拓展:寻找最佳K值(n_neighbors)
n_neighbors
的选择对KNN模型的性能至关重要。不同的 k
值可能导致不同的准确率。我们可以通过循环遍历不同的 k
值来找到最佳的 k
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 将数据集分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(data_all, target, test_size=0.3, random_state=42)
# 存储不同K值对应的准确率
k_values = range(1, 21)
accuracies = []
for k in k_values:
# 实例化KNN分类器
knn = KNeighborsClassifier(n_neighbors=k)
# 拟合模型
knn.fit(X_train, y_train)
# 预测并计算准确率
y_pred = knn.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
accuracies.append(accuracy)
# 绘制K值与准确率的关系图
plt.figure(figsize=(10, 6))
plt.plot(k_values, accuracies, marker='o')
plt.title('K值与准确率')
plt.xlabel('K值')
plt.ylabel('准确率')
plt.xticks(k_values)
plt.grid(True)
plt.show()
# 找到准确率最高的K值
best_k_index = np.argmax(accuracies)
best_k = k_values[best_k_index]
best_accuracy = accuracies[best_k_index]
print(f"最佳K值: {best_k}, 对应的准确率: {best_accuracy:.2f}")
因为数据量太小了,所以看不出什么效果:
鸢尾花数据集数据量较小,可能会导致在测试集上的准确率总是达到满分,从而难以直观地观察到 k
值对模型性能的影响。
接下来用一个包含500个样本的合成数据集来替代,这个数据集更适合用来演示 k
值变化时,模型准确率可能出现的波动。下面是使用新数据集的Canvas,它重新实现了寻找最佳 k
值的过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 导入所需的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
# 使用make_blobs生成一个包含500个样本的合成数据集
# centers=4 表示生成4个类别的数据
# cluster_std=2.0 增加了簇的标准差,使数据点更分散,类别重叠更多
# random_state=42 保证每次生成的数据相同
X, y = make_blobs(n_samples=500, centers=4, cluster_std=2.0, random_state=42)
# 查看数据集信息
print(f"数据集的特征数据形状: {X.shape}")
print(f"数据集的标签数据形状: {y.shape}")
# 将数据集分为训练集和测试集
# test_size=0.3 表示30%的数据作为测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 存储不同K值对应的准确率
k_values = range(1, 21)
accuracies = []
for k in k_values:
# 实例化KNN分类器,n_neighbors为当前的k值
knn = KNeighborsClassifier(n_neighbors=k)
# 使用训练数据拟合模型
knn.fit(X_train, y_train)
# 使用测试数据进行预测
y_pred = knn.predict(X_test)
# 计算并存储准确率
accuracy = accuracy_score(y_test, y_pred)
accuracies.append(accuracy)
# 绘制K值与准确率的关系图
plt.figure(figsize=(10, 6))
plt.plot(k_values, accuracies, marker='o')
plt.title('K值与准确率')
plt.xlabel('K值')
plt.ylabel('准确率')
plt.xticks(k_values)
plt.grid(True)
plt.show()
# 找到准确率最高的K值
best_k_index = np.argmax(accuracies)
best_k = k_values[best_k_index]
best_accuracy = accuracies[best_k_index]
print(f"\n最佳K值: {best_k}, 对应的准确率: {best_accuracy:.2f}")
拓展:改变权重 (weight) 和距离度量 (metric)
KNeighborsClassifier
允许我们通过 weights
参数调整邻居的权重,并通过 metric
参数改变距离度量方式,从而优化模型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 将数据集分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 使用 "distance" 权重,即距离越近的邻居权重越大
# 使用 "euclidean" 距离,即欧氏距离
knn_distance = KNeighborsClassifier(n_neighbors=4, weights='distance', metric='euclidean')
knn_distance.fit(X_train, y_train)
y_pred_distance = knn_distance.predict(X_test)
accuracy_distance = accuracy_score(y_test, y_pred_distance)
print(f"使用 'distance' 权重和欧氏距离的准确率: {accuracy_distance:.2f}")
# 使用 "uniform" 权重,即所有邻居权重相同
# 使用 "manhattan" 距离,即曼哈顿距离
knn_uniform = KNeighborsClassifier(n_neighbors=5, weights='uniform', metric='manhattan')
knn_uniform.fit(X_train, y_train)
y_pred_uniform = knn_uniform.predict(X_test)
accuracy_uniform = accuracy_score(y_test, y_pred_uniform)
print(f"使用 'uniform' 权重和曼哈顿距离的准确率: {accuracy_uniform:.2f}")
2.算法原理回顾
- 核心思想:对于一个待分类的新样本,KNN算法通过计算它与训练集中所有样本的距离,找出距离最近的
k
个邻居。然后,根据这k
个邻居的类别,通过投票的方式决定新样本的类别。 - 距离度量:常见的距离度量有欧氏距离(Euclidean Distance)、曼哈顿距离(Manhattan Distance)和Minkowski距离等。
- K值选择:
k
值太小,模型容易过拟合,对噪声敏感;k
值太大,模型可能欠拟合,无法捕捉到数据中的局部特征。
2.1 实用技巧
- 特征归一化:KNN算法基于距离度量,因此不同特征的尺度差异会严重影响结果。在应用KNN之前,对所有特征进行归一化(如使用
StandardScaler
或MinMaxScaler
)是必不可少的步骤。 - 对于大规模数据集,传统的蛮力搜索(Brute Force)计算量巨大。
sklearn
的KNeighborsClassifier
可以通过algorithm
参数使用KD树(kd_tree
)或Ball树(ball_tree
)来加速近邻搜索,显著提高效率。
3.KNN决策边界可视化
为了观察KNN是如何通过某些特征来对样本进行分类的,可以画出其边界,但是数据可能有很多维度(特征),人类的视觉能力主要局限于二维或三维空间。为了直观地看到分类器是如何在空间中划分不同类别的,我们需要将数据的维度降至2维,才能绘制边界。
KNN的决策边界通常是不规则的、分段线性的,这与决策树等算法的轴对齐边界不同。它的形状直接由训练数据的分布决定。
画图的思路:首先在二维特征空间中创建一个覆盖所有数据点的密集网格,然后使用训练好的 KNN 模型对网格中的每一个点进行批量预测,将每个点的分类结果记录下来。最后,通过matplotlib将这些预测结果以不同颜色填充到对应的网格区域上,从而直观地展示出模型划分出的边界。同时,将原始数据点叠加在彩色背景之上,就能清晰地看到边界是如何围绕数据点形成的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 1. 使用最佳K值重新训练一个KNN模型
knn_best = KNeighborsClassifier(n_neighbors=best_k)
knn_best.fit(X, y) # 这里使用所有数据进行训练,以获得更完整的边界
# 2. 定义绘图区域的边界
# 我们需要创建一个覆盖整个数据点的网格,以便对网格中的每个点进行预测
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
display(x_min, x_max, y_min, y_max)
# 3. 创建密集网格点
# np.meshgrid() 用于生成一个二维网格,就像背景中的像素点
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
# 4. 对网格中的所有点进行预测
# np.c_[xx.ravel(), yy.ravel()] 将网格点从二维展平为一维,并组合成(x,y)坐标对
# knn_best.predict() 对这些坐标进行批量预测,得到每个点的类别
Z = knn_best.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape) # 将预测结果的形状恢复成网格的形状
# display(Z)
# 5. 绘制决策边界和原始数据点
plt.figure(figsize=(12, 8))
# plt.pcolormesh() 绘制彩色区域,即决策边界
# 颜色代表了KNN模型对该区域内任何点的预测类别
plt.pcolormesh(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.3)
# plt.scatter() 绘制原始数据点,并用颜色表示其真实类别
# 这样我们可以对比决策边界与实际数据点的关系
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral, edgecolors='k')
plt.title(f'KNN 决策边界 (最佳K值: {best_k})', fontsize=16)
plt.xlabel('特征 1', fontsize=12)
plt.ylabel('特征 2', fontsize=12)
plt.xlim(x_min, x_max)
plt.ylim(y_min, y_max)
plt.show()