机器学习算法1-KNN分类任务

Posted by Hilda on August 1, 2025

回顾:

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)

image-20250802000051902

【4】预测:

1
2
y_pred = knn.predict(X_test)
y_pred

image-20250802000200386

【5】计算准确率:

1
accuracy_score(y_test, y_pred)

image-20250802000250119

拓展:寻找最佳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}")

因为数据量太小了,所以看不出什么效果:

image-20250802000609772

鸢尾花数据集数据量较小,可能会导致在测试集上的准确率总是达到满分,从而难以直观地观察到 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}")

image-20250802001102869

拓展:改变权重 (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}")

image-20250802001323521

2.算法原理回顾

  • 核心思想:对于一个待分类的新样本,KNN算法通过计算它与训练集中所有样本的距离,找出距离最近的 k 个邻居。然后,根据这 k 个邻居的类别,通过投票的方式决定新样本的类别。
  • 距离度量:常见的距离度量有欧氏距离(Euclidean Distance)、曼哈顿距离(Manhattan Distance)和Minkowski距离等。
  • K值选择k 值太小,模型容易过拟合,对噪声敏感;k 值太大,模型可能欠拟合,无法捕捉到数据中的局部特征。

2.1 实用技巧

  • 特征归一化:KNN算法基于距离度量,因此不同特征的尺度差异会严重影响结果。在应用KNN之前,对所有特征进行归一化(如使用 StandardScalerMinMaxScaler)是必不可少的步骤。
  • 对于大规模数据集,传统的蛮力搜索(Brute Force)计算量巨大。sklearnKNeighborsClassifier 可以通过 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()

image-20250802173020112