Lesson 03
K 近邻:用距离投票做预测
KNN 是最直觉的分类与回归算法——不训练参数,而是记住全部数据,预测时找最近的 k 个邻居投票。本章讲清它的原理、对距离和缩放的敏感性、以及 k 值如何影响偏差与方差。
本章细分学习地图
KNN 简单到不需要"训练",但正因如此,它对数据质量、距离定义和特征量纲极度敏感。每个模块都围绕一个实际判断展开。
| 模块 | 拆细学习单元 | 完成后应产出 | 掌握标准 |
|---|---|---|---|
| 直觉与原理 | 惰性学习、非参数模型、多数投票 / 均值。 | 一句话解释 KNN 原理。 | 能说清 KNN 为什么没有 coef_。 |
| 距离与缩放 | 欧氏/曼哈顿/余弦距离;缩放前后邻居变化。 | 缩放实验对比。 | 能判断哪些特征需要先缩放。 |
| k 值 | k 小 → 过拟合、k 大 → 欠拟合;交叉验证选 k。 | k-准确率折线图。 | 能解释为什么 k=1 和 k=n 都不好。 |
| sklearn 实战 | 分类 + 回归 Pipeline,含 StandardScaler。 | 完整可运行代码。 | 能独立构建 KNN Pipeline 并评估。 |
学习目标
KNN 预测流程
本章完成度
已完成 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 值控制偏差-方差的直观体现。
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。
- 能用
KNeighborsClassifier和KNeighborsRegressor完成完整实战。