SL Scikit-Learn Learn机器学习教程

Lesson 03

K 近邻:用距离投票做预测

KNN 是最直觉的分类与回归算法——不训练参数,而是记住全部数据,预测时找最近的 k 个邻居投票。本章讲清它的原理、对距离和缩放的敏感性、以及 k 值如何影响偏差与方差。

  • 预计时长40 分钟
  • 难度入门
  • 前置章节第 01 章

本章细分学习地图

KNN 简单到不需要"训练",但正因如此,它对数据质量、距离定义和特征量纲极度敏感。每个模块都围绕一个实际判断展开。

模块拆细学习单元完成后应产出掌握标准
直觉与原理惰性学习、非参数模型、多数投票 / 均值。一句话解释 KNN 原理。能说清 KNN 为什么没有 coef_
距离与缩放欧氏/曼哈顿/余弦距离;缩放前后邻居变化。缩放实验对比。能判断哪些特征需要先缩放。
k 值k 小 → 过拟合、k 大 → 欠拟合;交叉验证选 k。k-准确率折线图。能解释为什么 k=1 和 k=n 都不好。
sklearn 实战分类 + 回归 Pipeline,含 StandardScaler。完整可运行代码。能独立构建 KNN Pipeline 并评估。

学习目标

KNN 预测流程

1存储训练数据
2计算距离
3找 k 个邻居
4投票/均值

本章完成度

已完成 0/4

本章核心判断

KNN 的预测质量完全取决于"距离是否有意义"。如果特征量纲不统一、无关特征太多或数据量太大,KNN 的表现和效率都会迅速下降。

直觉与原理

KNN 不学参数。它在训练阶段只是把数据存起来(惰性学习),预测时计算新样本和所有训练样本的距离,找出最近的 k 个邻居,然后:

  • 分类:k 个邻居中出现最多的类别就是预测结果(多数投票)。
  • 回归:k 个邻居的目标值取平均作为预测结果。

惰性学习 Lazy Learning

训练阶段零计算开销,所有计算推迟到预测阶段。数据量大时预测会很慢。

非参数模型

不对数据分布做假设,没有 coef_intercept_。决策边界的形状完全由数据决定。

与线性回归的对比

线性回归学的是一组固定的参数 w、b,预测时只需一次计算;KNN 不学参数,每次预测都要遍历数据。两者适用场景完全不同。

距离度量与特征缩放

KNN 对距离定义极度敏感。如果特征量纲不同(如年龄 0~100,收入 0~1000000),大数值特征会主导距离计算。

距离度量公式直觉适用场景
欧氏距离直线距离,各维度等权。连续数值特征(需先缩放)。
曼哈顿距离沿坐标轴的绝对差之和。高维稀疏数据或网格路径场景。
余弦相似度关注方向而非长度。文本向量、TF-IDF 特征。
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score

X, y = load_iris(return_X_y=True)

# 不缩放
raw_scores = cross_val_score(KNeighborsClassifier(n_neighbors=5), X, y, cv=5)
print(f"不缩放: {raw_scores.mean():.3f}")

# 缩放后
pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("knn", KNeighborsClassifier(n_neighbors=5)),
])
scaled_scores = cross_val_score(pipe, X, y, cv=5)
print(f"缩放后: {scaled_scores.mean():.3f}")
经验法则

只要模型依赖距离(KNN、SVM、KMeans),就应该先缩放。用 Pipeline 把缩放和模型绑在一起,避免数据泄漏。

k 值与偏差-方差权衡

k 是 KNN 唯一的核心超参数,它直接控制模型复杂度:

k 值偏差方差表现
k = 1决策边界极复杂,容易过拟合噪声。
k 适中适中适中通常用交叉验证找到的最佳平衡点。
k = n永远预测多数类或全局均值,严重欠拟合。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

X, y = load_iris(return_X_y=True)
pipe = Pipeline([("scaler", StandardScaler()), ("knn", KNeighborsClassifier())])

k_range = range(1, 26)
scores = []
for k in k_range:
    pipe.set_params(knn__n_neighbors=k)
    score = cross_val_score(pipe, X, y, cv=5).mean()
    scores.append(score)
    print(f"k={k:2d}  准确率={score:.3f}")

best_k = k_range[np.argmax(scores)]
print(f"\n最佳 k={best_k}, 准确率={max(scores):.3f}")
分类任务中 k 取奇数

二分类时 k 取奇数可避免平票。多分类时平票的概率更低,但仍推荐用奇数减少歧义。

互动实验:k 值如何影响分类边界

下面是两类训练样本(蓝色 / 绿色)。把鼠标移到画布上放置一个新样本,拖动滑块调整 k,观察背景中的决策边界、参与投票的邻居连线如何随 k 变化。

把鼠标移到画布上试试。

观察重点

k 较小时决策边界会紧贴局部样本、出现锯齿状的小区域(对噪声敏感);k 变大后边界变得更平滑,但也更容易忽略局部结构(偏向多数类)。这正是 k 值控制偏差-方差的直观体现。

KNN 回归实战

KNN 不仅能分类,也能回归。回归时取 k 个邻居目标值的均值作为预测。

from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import root_mean_squared_error, r2_score

X, y = make_regression(n_samples=200, n_features=1, noise=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

pipe = Pipeline([
    ("scaler", StandardScaler()),
    ("knn", KNeighborsRegressor(n_neighbors=7)),
])
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)

print(f"R²:   {r2_score(y_test, y_pred):.4f}")
print(f"RMSE: {root_mean_squared_error(y_test, y_pred):.2f}")
KNN 的优势KNN 的局限
原理简单,无需假设数据分布。预测慢,内存占用高(存全部数据)。
决策边界可以是任意形状。高维数据下距离变得不可靠(维度灾难)。
新增数据无需重新训练。对无关特征和量纲差异极度敏感。

练习题

第 1 题:为什么 KNN 被称为"惰性学习"?这对预测速度有什么影响?

参考思路:KNN 训练阶段不学参数,只存储数据(惰性)。预测时需要计算新样本与所有训练样本的距离,因此预测速度随数据量线性增长,数据量大时很慢。

第 2 题:特征 A 范围 [0, 1],特征 B 范围 [0, 100000],不缩放会怎样?

参考思路:欧氏距离几乎完全由特征 B 主导,特征 A 的信息被忽略。缩放后两者对距离的贡献平衡,邻居关系更合理。

第 3 题:用鸢尾花数据集,分别取 k=1, 5, 50 做 5 折交叉验证,观察准确率变化并解释。

参考思路:k=1 过拟合(方差高),k=5 通常最优,k=50 欠拟合(偏差高,趋向预测多数类)。画出 k-准确率曲线可以直观看出偏差-方差权衡。

检查点

离开本章前,请确认:

  • 能解释 KNN 的预测过程:存数据 → 算距离 → 找邻居 → 投票/均值。
  • 能说清为什么 KNN 对缩放敏感,以及为什么要用 Pipeline 包裹 Scaler + KNN。
  • 能用偏差-方差框架分析 k=1 和 k=n 的问题,知道用交叉验证选 k。
  • 能用 KNeighborsClassifierKNeighborsRegressor 完成完整实战。