KNeighborsClassifier——K 近邻分类器详解
KNeighborsClassifier——K 近邻分类器详解
1. 前言
在机器学习的分类算法中,K 近邻(K-Nearest Neighbors, KNN)是最直观、最容易理解的算法之一。它不需要训练过程,而是直接基于”相似样本具有相似标签”这一朴素假设进行分类,属于典型的惰性学习(Lazy Learning)算法。
KNeighborsClassifier 是 sklearn 中 KNN 分类器的官方实现。本文将深入讲解其原理、参数、实战用法及常见坑点。
2. 什么是 KNN
KNN 的核心思想可以用一句话概括:判断一个样本的类别,看它周围最近的 K 个邻居中哪个类别最多。
算法的完整流程:
- 计算距离:计算待分类样本与训练集中每个样本之间的距离;
- 找 K 近邻:选出距离最近的 K 个训练样本;
- 投票表决:根据 K 个邻居的类别进行多数投票,票数最多的类别即为预测结果。
KNN 没有显式的”训练”阶段——它只是将训练数据存储起来,等到预测时才进行计算。因此也被称为基于实例的学习(Instance-Based Learning)。
3. 核心原理
3.1 距离计算
对于两个 $n$ 维向量 $x = (x_1, x_2, \dots, x_n)$ 和 $y = (y_1, y_2, \dots, y_n)$,KNN 默认使用闵可夫斯基距离,其定义为:
$$
D_p(x, y) = \left( \sum_{i=1}^{n} |x_i - y_i|^p \right)^{1/p}
$$
其中 $p$ 是距离参数:
- $p = 1$:曼哈顿距离
- $p = 2$(默认值):欧氏距离
KNN 默认使用 $p=2$ 即欧氏距离:
$$
d(x, y) = \sqrt{\sum_{i=1}^{n} (x_i - y_i)^2}
$$
3.2 多数表决
选定 K 个最近邻居后,分类结果由投票决定:
$$
\hat{y} = \arg\max_{c} \sum_{i=1}^{K} \mathbb{1}(y_i = c)
$$
其中 $\mathbb{1}(\cdot)$ 是指示函数,$y_i$ 是第 $i$ 个邻居的标签。即统计每个类别在 K 个邻居中出现的次数,取出现次数最多的类别。
3.3 加权投票
当设置 weights='distance' 时,每个邻居的投票权重与其距离成反比——距离越近的邻居对分类结果的贡献越大:
$$
\hat{y} = \arg\max_{c} \sum_{i=1}^{K} \frac{1}{d(x, x_i)} \cdot \mathbb{1}(y_i = c)
$$
这种做法可以减少远距离邻居对投票结果的干扰,尤其适合样本分布不均的场景。
4. 核心参数详解
1 | from sklearn.neighbors import KNeighborsClassifier |
4.1 n_neighbors —— K 值
最重要的超参数,决定了参与投票的邻居数量。
- K 太小(如 K=1):模型对噪声敏感,容易过拟合。单个异常点可能导致错误分类;
- K 太大:决策边界过于平滑,可能忽略数据中的局部特征,导致欠拟合;
- 经验法则:通常取奇数(避免二分类平票),初始值可取 $K \approx \sqrt{N}$($N$ 为训练样本数),再通过交叉验证调优。
4.2 weights —— 投票权重
| 值 | 行为 |
|---|---|
'uniform'(默认) |
所有 K 个邻居权重相同,纯多数投票 |
'distance' |
权重与距离成反比,近邻影响更大 |
| 自定义 callable | 传入自定义权重函数 |
1 | # uniform: K 个邻居一视同仁 |
4.3 algorithm —— 搜索算法
控制如何在高维空间中高效搜索最近邻:
| 值 | 说明 |
|---|---|
'auto'(默认) |
根据训练数据自动选择最优算法 |
'brute' |
暴力搜索,逐个计算所有样本的距离——$O(n \cdot d)$ |
'kd_tree' |
KD 树——适合低维数据($d < 20$),$O(d \log n)$ |
'ball_tree' |
Ball 树——适合中高维数据,$O(d \log n)$ |
1 | # 数据量小时 brute 可能更快(没有建树开销) |
4.4 p 与 metric —— 距离度量
默认使用闵可夫斯基距离,通过 p 控制具体类型:
1 | # 欧氏距离(默认 p=2) |
4.5 leaf_size —— 叶节点大小
控制 BallTree / KDTree 中叶节点的最大样本数:
- 较大值:建树更快,但查询略慢(更多样本需要暴力比对);
- 较小值:建树略慢,查询更快;
- 默认值
30在大多数场景下表现良好,一般无需修改。
4.6 n_jobs —— 并行处理
KNN 的预测阶段需要计算与所有训练样本的距离,这部分可以利用多核并行加速。n_jobs=-1 使用全部 CPU 核心。
5. 拟合后的属性与方法
fit() 完成后,KNeighborsClassifier 对象会暴露一系列属性和方法,用于查询模型状态、进行预测和获取近邻信息。理解这些接口的含义和区别是正确使用 KNN 的前提。
5.1 模型属性(Attributes)
以下属性在 fit() 之后可用,均为以单下划线结尾的实例属性(sklearn 约定:fit 后生成的属性以 _ 结尾)。
| 属性 | 类型 | 说明 |
|---|---|---|
classes_ |
ndarray of shape (n_classes,) | 训练数据中的类别标签数组 |
effective_metric_ |
str 或 callable | 实际使用的距离度量方式。若初始化时传入 metric='minkowski',此处会被解析为 'minkowski' 字符串;若传入自定义函数,此处保存该函数引用 |
effective_metric_params_ |
dict | 实际传递给距离函数的额外参数。当 metric='minkowski' 时,此字典包含 {'p': 2}(或你指定的 p 值) |
n_features_in_ |
int | fit() 时看到的特征数量 |
feature_names_in_ |
ndarray of shape (n_features_in_,) | fit() 时看到的特征名称(仅当输入为 DataFrame 时有值) |
n_samples_fit_ |
int | fit() 时使用的训练样本数量 |
outputs_2d_ |
bool | 输出是否为二维。当 fit() 时传入的 y 是多维数组时为 True |
关键区分:上述属性都是 KNeighborsClassifier 实例的顶层属性,直接通过 knn.classes_、knn.effective_metric_ 等方式访问。这与 GridSearchCV 的 cv_results_ 不同——cv_results_ 本身是一个 dict,它的 key(如 mean_test_score)不是 GridSearchCV 的顶层属性,必须通过 grid.cv_results_['mean_test_score'] 访问。
1 | knn = KNeighborsClassifier(n_neighbors=5, metric='minkowski', p=2) |
5.2 核心方法(Methods)
fit(X, y) —— 存储训练数据
KNN 的 fit() 并不做实际”训练”,只是将训练数据存入内部结构(构建搜索树或直接存储原始数据),因此几乎瞬间完成。
1 | knn = KNeighborsClassifier(n_neighbors=5) |
predict(X) —— 预测类别标签
返回每个输入样本的预测类别:
1 | y_pred = knn.predict(X_test) |
predict_proba(X) —— 预测各类别概率
返回每个样本在各个类别上的概率分布(K 个邻居中各类别所占比例):
1 | proba = knn.predict_proba(X_test[:3]) |
注意:当 weights='distance' 时,概率按距离加权计算,而非简单计数。
kneighbors(X, n_neighbors, return_distance) —— 查询 K 近邻
KNN 最核心的方法之一,返回每个查询样本的 K 个最近邻的距离和索引。n_neighbors 若不指定则使用初始化时的 n_neighbors 值。
1 | # 同时返回距离和邻居索引 |
用途举例——查看测试样本的最近邻类别:
1 | distances, indices = knn.kneighbors(X_test[:3]) |
kneighbors_graph(X, n_neighbors, mode) —— 构建 K 近邻图
返回样本间的稀疏邻接矩阵(scipy sparse matrix),mode='connectivity' 返回 0/1 矩阵,mode='distance' 返回距离矩阵:
1 | # 返回稀疏连接矩阵 |
这在谱聚类、流形学习等需要构建样本间关系图的场景中非常有用。
score(X, y) —— 计算准确率
等价于 accuracy_score(即 accuracy_score(y, predict(X))),是分类器最常用的快捷评估方法:
1 | print(knn.score(X_test, y_test)) # 返回 float,测试集准确率 |
get_params(deep) 与 set_params(**params) —— 参数读写
继承自 sklearn BaseEstimator 的基础方法,用于读取和修改模型参数:
1 | # 获取当前参数 |
5.3 方法速查表
| 方法 | 返回类型 | 说明 |
|---|---|---|
fit(X, y) |
self(当前对象) | 存储训练数据,构建搜索结构 |
predict(X) |
ndarray (n_samples,) | 预测类别标签 |
predict_proba(X) |
ndarray (n_samples, n_classes) | 预测各类别概率 |
kneighbors(X, ...) |
tuple (distances, indices) | 查询 K 个最近邻的距离与索引 |
kneighbors_graph(X, ...) |
sparse matrix | 构建 K 近邻稀疏邻接矩阵 |
score(X, y) |
float | 计算预测准确率 |
get_params() |
dict | 获取当前所有参数 |
set_params(**params) |
self | 修改参数(需重新 fit) |
5.4 基础实战——鸢尾花分类
结合上述属性和方法,一个完整的 KNN 分类流程如下:
1 | from sklearn.datasets import load_iris |
6. K 值的影响深入分析
6.1 偏差-方差权衡
| K 值 | 模型复杂度 | 偏差 | 方差 | 表现 |
|---|---|---|---|---|
| K=1 | 极高 | 极低 | 极高 | 对噪声极度敏感,容易过拟合 |
| K 适中 | 中等 | 中等 | 中等 | 平衡偏差与方差,泛化能力好 |
| K=N(全部样本) | 极低 | 极高 | 极低 | 忽略所有特征,始终预测多数类 |
6.2 经验公式
常用的经验公式(仅供参考,最终应以交叉验证为准):
- $K \approx \sqrt{N}$,$N$ 为训练样本数;
- $K$ 通常取奇数,避免二分类中平票;
- 对于多分类任务,$K$ 不应是类别数的整数倍(避免系统性平票)。
7. KNN 的优缺点
优点
- 简单直观:算法原理易于理解,无需复杂的数学推导;
- 无需训练:没有训练阶段,新增样本无需重新训练;
- 天然支持多分类:投票机制天然适用于多类别场景;
- 可解释性强:可以明确指出”因为最近的哪几个邻居是某个类别”。
缺点
- 计算开销大:每次预测都需要计算与全部训练样本的距离,复杂度 $O(N \cdot d)$;
- 内存占用高:需要存储全部训练数据;
- 维度灾难:高维空间中距离变得不再有区分度,KNN 效果急剧下降;
- 对尺度敏感:特征量纲不一致会严重影响距离计算结果;
- 对不平衡数据敏感:多数类在投票中占据天然优势;
- 无法处理缺失值:缺失值会导致无法计算距离。
8. 常见问题与避坑指南
8.1 忘记标准化数据
KNN 基于距离计算,如果特征尺度差异巨大,结果将严重失真。
1 | # 错误!特征未标准化 |
8.2 高维数据下 KNN 失效
当特征维度很高时(如文本 TF-IDF 特征),所有样本之间的距离趋向于相同值,KNN 失去区分能力。对于高维稀疏数据,考虑使用:
- 降维(PCA、t-SNE)后再用 KNN;
- 改用对高维友好的模型(如 SVM、随机森林)。
8.3 类别不平衡
当某类样本数远大于其他类时,多数类会在 K 个邻居中占据优势。
应对策略:
1 | # 使用 distance 权重,让近邻的影响大于远邻 |
8.4 大数据集下的性能问题
KNN 预测时需要扫描全部训练样本,百万级数据集下预测极慢。
应对策略:
- 使用 KDTree 或 BallTree 算法(
algorithm='ball_tree'); - 对训练集做采样或原型选择(如浓缩近邻算法);
- 考虑改用支持向量机或随机森林等有显式决策边界的模型。
8.5 K 值选择不当
K=1 时模型会完美拟合训练数据(训练集准确率 100%),但泛化能力差。不要被训练集满分迷惑——务必使用交叉验证或独立验证集来评估模型。
9. 进阶技巧
9.1 自定义距离函数
1 | from scipy.spatial.distance import cosine |
9.2 概率预测
KNN 不仅能给出分类标签,还可以给出每个类别的预测概率:
1 | knn = KNeighborsClassifier(n_neighbors=5) |
9.3 与 GridSearchCV 结合
1 | from sklearn.model_selection import GridSearchCV |
10. 小结
- KNN 是基于”近朱者赤,近墨者黑”思想的最直观分类算法,无需训练过程;
- K 值是最关键的超参数:太小容易过拟合,太大会欠拟合,应通过交叉验证选择;
- 数据标准化是 KNN 的硬性前提——不标准化会严重影响距离计算的结果;
- 距离度量可根据场景选择:欧氏距离适合低维连续特征,曼哈顿距离对异常值更鲁棒;
- KNN 不适合高维稀疏数据和大规模数据集,这些场景下应考虑其他算法;
- 结合
Pipeline和GridSearchCV可以将预处理和参数调优整合到统一的流程中。
