38·监督学习-分类入门

K 近邻算法(KNN)

classificationinstance-based

第 38 课:K 近邻算法(KNN)

1. 学习目标

通过本节课的学习,你将能够:

  • 理解 K 近邻算法的核心思想和“近朱者赤”的决策逻辑。
  • 掌握 KNN 分类算法的工作流程和关键参数(K值、距离度量)。
  • 使用 Python 的 scikit-learn 库构建、训练和评估一个 KNN 分类模型。
  • 应用 特征缩放(标准化)来优化 KNN 模型的性能。
  • 评估 不同 K 值对模型效果的影响,并选择合适的 K。

2. 核心概念

什么是 KNN? K 近邻算法(K-Nearest Neighbors)是一种简单、直观的监督学习算法。它既可以用于分类,也可以用于回归。今天我们聚焦于分类任务。

它的核心思想非常朴素,就像一句古话:“物以类聚,人以群分”。想象一下,在一个房间里,你不知道自己属于哪个群体,但你知道你身边最常接触的 K 个朋友分别属于哪些群体。那么,你最有可能属于人数最多的那个群体。

算法步骤(分类):

  1. 存储:算法在训练阶段不会构建明确的模型,只是简单地“记住”所有训练数据(样本及其对应的标签)。这是它被称为“实例-based”或“懒惰学习”算法的原因。
  2. 计算:当需要预测一个新样本的类别时,算法会计算该样本与所有训练样本之间的“距离”。
  3. 找邻居:找出距离新样本最近的 K 个训练样本(即“K 个邻居”)。
  4. 投票:查看这 K 个邻居的类别标签。新样本的类别被预测为这 K 个邻居中出现次数最多的类别(即“多数表决”)。

关键参数与决策:

  • K 值:邻居的数量。这是一个需要预先设定的超参数。
    • K 值太小(如 K=1),模型容易受到噪声点的影响,导致“过拟合”。
    • K 值太大(如 K 接近所有样本数),模型会变得非常简单,可能忽略数据中的模式,导致“欠拟合”。
    • 通常通过交叉验证来寻找一个合适的 K 值(奇数通常更好,以避免平票)。
  • 距离度量:如何衡量样本之间的“相似度”或“远近”?最常用的是欧氏距离(二维/三维空间中的直线距离)。对于特征,也常用曼哈顿距离、闵可夫斯基距离等。
  • 特征缩放:由于 KNN 完全依赖于距离计算,如果不同特征的量纲(单位)和范围差异巨大,那么“距离”将主要被数值范围大的特征主导。因此,在使用 KNN 之前,对数据进行标准化(如使用 StandardScaler)是至关重要的一步

3. 代码示例

我们将使用 scikit-learn 中的鸢尾花(Iris)数据集演示一个完整的 KNN 分类流程。

# 导入必要的库
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.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# 1. 加载数据
iris = load_iris()
X = iris.data  # 特征矩阵 (150个样本,4个特征)
y = iris.target # 目标向量 (类别标签: 0, 1, 2)

# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# stratify=y 确保训练集和测试集中各类别的比例与原始数据一致

# 3. 特征缩放(关键步骤!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # 在训练集上计算均值和方差,并转换
X_test_scaled = scaler.transform(X_test)       # 使用训练集的参数转换测试集

# 4. 创建并训练 KNN 模型
# 选择 K=5 作为起点(一个经验上的小奇数)
knn = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
knn.fit(X_train_scaled, y_train)

# 5. 在测试集上进行预测
y_pred = knn.predict(X_test_scaled)

# 6. 评估模型
print("测试集准确率:", accuracy_score(y_test, y_pred))
print("\n分类报告:\n", classification_report(y_test, y_pred, target_names=iris.target_names))
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))

# 7. (可选)可视化不同 K 值对准确率的影响
k_range = range(1, 31)
train_scores = []
test_scores = []

for k in k_range:
    knn_temp = KNeighborsClassifier(n_neighbors=k, metric='euclidean')
    knn_temp.fit(X_train_scaled, y_train)
    train_scores.append(knn_temp.score(X_train_scaled, y_train))
    test_scores.append(knn_temp.score(X_test_scaled, y_test))

plt.figure(figsize=(10, 6))
plt.plot(k_range, train_scores, label='Training Accuracy')
plt.plot(k_range, test_scores, label='Testing Accuracy')
plt.xlabel('Number of Neighbors (K)')
plt.ylabel('Accuracy')
plt.title('KNN Varying number of neighbors')
plt.legend()
plt.grid(True)
plt.show()

4. 实践练习

练习 1(基础):手写数字识别入门 使用 scikit-learn 自带的 load_digits 数据集(8x8 像素的手写数字图像,共10类)。

  • 加载数据,划分训练集/测试集。
  • 对特征进行标准化。
  • 使用 KNeighborsClassifier (默认 K=5) 进行训练和预测。
  • 输出模型在测试集上的准确率。

练习 2(进阶):参数调优 继续使用 load_digits 数据集。

  • 编写一个循环,测试 K 从 1 到 20(奇数)时,模型在测试集上的准确率。
  • 找出并打印出使得测试集准确率最高的 K 值及其对应的准确率。
  • 绘制 K 值与准确率的关系图。

练习 3(应用):特征工程的影响

  • 重复练习 1 的步骤,但跳过特征标准化步骤。
  • 对比使用标准化和不使用标准化两种情况下,模型的测试集准确率。
  • 思考并简述:为什么标准化对 KNN 特别重要?

5. 常见错误

  1. 忘记特征缩放:这是 KNN 最常见的错误。如果不缩放,模型会严重偏向数值范围大的特征,导致性能不佳。
  2. K 值选择不当
    • 直接使用默认的 K=5 而不进行任何验证。
    • 选择偶数的 K 值,在二分类问题中可能导致平票(虽然 scikit-learn 有自己的平票处理策略,但最好是避免)。
  3. 数据泄露:在进行特征缩放(如标准化)时,错误地在整个数据集(包含测试集)上计算均值和方差。正确做法是:fit 仅在训练集上进行,然后用相同的参数去 transform 测试集。
  4. 忽略计算成本:KNN 的预测阶段需要计算与所有训练样本的距离,当训练集非常大时,预测会非常慢。这是它“懒惰”学习的一个代价。
  5. 维度灾难:当特征维度非常高时,基于距离的度量会变得不那么可靠(所有点之间的距离可能变得相似),KNN 的性能会下降。此时可能需要进行降维处理。

6. 小结

  • KNN 核心:通过查找新样本在特征空间中最近的 K 个邻居,根据邻居的类别进行多数表决来预测。
  • 关键三要素
    1. K 值:控制模型复杂度,需通过交叉验证选择。
    2. 距离度量:通常使用欧氏距离。
    3. 特征缩放必须进行,以消除特征量纲的影响。
  • 实现流程:加载数据 -> 划分数据集 -> 特征缩放 -> 创建模型 -> 训练(只是存储数据) -> 预测 -> 评估。
  • 优点:简单直观,易于理解和实现;无需训练过程(“懒惰”);对异常值不敏感(除非 K 值很小)。
  • 缺点:预测计算量大;存储开销大;在高维空间中性能下降;对数据不平衡敏感(如果某个类别的样本特别多,它更容易成为“邻居”)。
  • 下一步:下一课我们将学习另一种强大的分类算法——决策树,它通过一系列问题来对样本进行划分,思路与 KNN 截然不同。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「决策树:原理、构建与剪枝」 以巩固所学知识。