Skip to content

Scikit Learn - KNN 学习

k近邻算法 (k-Nearest Neighbors, k-NN) 是一种简单、通用且广泛使用的机器学习算法。它被归类为非参数算法,这意味着它对底层数据分布不作假设;它也是一种惰性学习(lazy learning,或基于实例 instance-based)算法,这意味着它在训练期间不构建通用的内部模型。相反,它存储所有训练数据 (training data),只在预测时 (prediction time) 进行计算。

k-NN 算法主要包括两个步骤:

  1. 训练/存储阶段 (Training/Storage Phase):存储训练集 (training set) 中的所有数据点及其对应的标签(用于监督学习 supervised learning)。
  2. 预测阶段 (Prediction Phase):对于一个新的、未标记的样本: a. 计算新样本与训练集中所有样本之间的距离 (distance)(例如,欧几里得距离 Euclidean distance)。 b. 找出 k 个最近的邻居 (nearest neighbors)(距离最小的 k 个训练样本)。 c. 对于分类 (classification) 任务,通过这 k 个邻居的多数投票 (majority vote) 来预测类别标签 (class label)。对于回归 (regression) 任务,预测值是这 k 个邻居的目标值 (target values) 的平均值(或中位数 median)。

Scikit-learn 的 sklearn.neighbors 模块实现了 k-NN 算法,可用于无监督学习 (unsupervised learning)(邻居搜索 neighbor searches)和监督学习(分类和回归)任务。

k-NN 的核心在于找到给定点的最近邻。这种邻居搜索能力对于许多算法都至关重要,而不仅仅是 k-NN 本身(例如,某些聚类方法 clustering methods、流形学习 manifold learning)。Scikit-learn 提供了 sklearn.neighbors.NearestNeighbors 来实现此目的。它为高效的邻居搜索算法(如 BallTree、KDTree 和暴力搜索 brute-force search)提供了一个统一的接口。

sklearn.neighbors.NearestNeighbors 用于实现无监督最近邻学习。它为数据集 (dataset) 中的每个样本或新的查询点 (query points) 查找 k 个最近邻。

参数 (Parameter)描述 (Description)
n_neighbors整数 (default=5)。要检索的邻居数量。
radius浮点数 (default=1.0)。用于查找固定距离内的邻居的半径。如果使用此参数,kneighbors 将返回此半径内的所有点。
algorithm字符串 {‘auto’, ‘ball_tree’, ‘kd_tree’, ‘brute’} (default=‘auto’)。用于计算最近邻的算法。‘auto’ 会尝试根据输入数据 결정 최적의 알고리즘。
leaf_size整数 (default=30)。传递给 BallTree 或 KDTree 的叶子大小。影响构建速度、查询速度和内存。
metric字符串或可调用对象 (default=‘minkowski’)。要使用的距离度量。常用值:‘euclidean’(欧几里得)、‘manhattan’(曼哈顿)、‘cosine’(余弦)。有关来自 sklearn.metrics.pairwise_distances 或 scipy.spatial.distance 的完整列表,请参见 Scikit-learn 文档。
p整数 (default=2)。Minkowski 度量的参数。p=1 是曼哈顿距离 (L1),p=2 是欧几里得距离 (L2)。
metric_params字典 (default=None)。度量函数的附加关键字参数。
n_jobs整数或 None (default=None)。用于邻居搜索的并行作业数量。-1 表示使用所有处理器。
from sklearn.neighbors import NearestNeighbors
import numpy as np
# 示例输入数据
input_data = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
# 初始化 NearestNeighbors,查找 3 个最近邻
nrst_neigh = NearestNeighbors(n_neighbors=3, algorithm='ball_tree')
# 使用输入数据拟合模型(构建树)
nrst_neigh.fit(input_data)
# 查找与输入数据点相同的 K 个邻居
# 返回邻居的距离和索引
distances, indices = nrst_neigh.kneighbors(input_data)
print("最近邻的索引(每行对应 input_data 中的一个点):")
print(indices)
print("\n到最近邻的距离:")
print(distances)
# 我们还可以获得一个稀疏图,显示邻居之间的连接
# 对于每个点,'1' 表示在指定的 k 个邻居之内
connectivity_graph = nrst_neigh.kneighbors_graph(input_data).toarray()
print("\n连接图(邻居的邻接矩阵):")
print(connectivity_graph)
最近邻的索引(每行对应 input_data 中的一个点):
[[0 1 3]
[1 0 2]
[2 1 0]
[3 4 0]
[4 3 5]
[5 4 3]]
到最近邻的距离:
[[0. 1. 2.82842712]
[0. 1. 1.41421356]
[0. 1.41421356 2.23606798]
[0. 1. 2.82842712]
[0. 1. 1.41421356]
[0. 1.41421356 2.23606798]]
连接图(邻居的邻接矩阵):
[[1. 1. 0. 1. 0. 0.]
[1. 1. 1. 0. 0. 0.]
[0. 1. 1. 0. 0. 1.]
[1. 0. 0. 1. 1. 0.]
[0. 0. 0. 1. 1. 1.]
[0. 0. 1. 0. 1. 1.]]

第一个邻居总是点本身(距离为 0),因为查询集 (query set) 与训练集相同。然后,需要高效邻居查找的其他算法可以使用此 NearestNeighbors 对象。

监督 k-NN 利用邻居信息进行预测任务 (prediction tasks):

  • 分类 (Classification):用于具有离散标签 (discrete labels) 的数据。通过 k 个最近邻中的多数投票分配类别标签。
  • 回归 (Regression):用于具有连续标签 (continuous labels) 的数据。预测值是 k 个最近邻的目标值的平均值(或中位数等其他聚合值 aggregate)。

Scikit-learn 提供两种主要的 k-NN 分类器:

  • KNeighborsClassifier:根据 k 个最近邻进行分类。k 是用户指定的整数。
  • RadiusNeighborsClassifier:根据每个查询点周围固定半径 r 内的邻居数量进行分类。r 是用户指定的浮点数。

这些分类器的关键参数包括 n_neighbors(用于 KNeighborsClassifier)、radius(用于 RadiusNeighborsClassifier)、weights(‘uniform’ 表示等权重 equal weighting,‘distance’ 表示距离越近权重越大 distance weighting)、algorithm、leaf_size、metric、p 和 n_jobs。

示例:在 Iris 数据集上使用 KNeighborsClassifier

Section titled “示例:在 Iris 数据集上使用 KNeighborsClassifier”
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
# 加载 Iris 数据集
iris = load_iris()
X, y = iris.data, iris.target
# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# 特征缩放(对于像 KNN 这样的基于距离的算法很重要)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 初始化并训练 KNeighborsClassifier
knn_clf = KNeighborsClassifier(n_neighbors=5, weights='distance') # 使用 5 个邻居,按距离加权
knn_clf.fit(X_train_scaled, y_train)
# 进行预测
y_pred = knn_clf.predict(X_test_scaled)
# 评估准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"KNeighborsClassifier 准确率: {accuracy:.4f}")
KNeighborsClassifier 准确率: 0.9778

选择最优的 k 至关重要,通常通过交叉验证 (cross-validation) 来完成。较小的 k 值可能导致决策边界 (decision boundary) 嘈杂(高方差 high variance),而较大的 k 值可能导致过度平滑 (oversmooth)(高偏差 high bias)。

类似地,Scikit-learn 提供两种主要的 k-NN 回归器:

  • KNeighborsRegressor:根据 k 个最近邻的目标值的平均值(或其他聚合值)进行预测。
  • RadiusNeighborsRegressor:根据固定半径 r 内的邻居进行预测。

参数与其分类器对应物类似。

from sklearn.datasets import fetch_california_housing # 回归数据集
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error
import numpy as np
# 加载加州住房数据集
housing = fetch_california_housing()
X, y = housing.data, housing.target
# 为了加快示例速度,使用数据子集
X_subset, y_subset = X[:2000], y[:2000]
# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X_subset, y_subset, test_size=0.3, random_state=42)
# 特征缩放
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 初始化并训练 KNeighborsRegressor
knn_reg = KNeighborsRegressor(n_neighbors=7, weights='distance') # 使用 7 个邻居
knn_reg.fit(X_train_scaled, y_train)
# 进行预测
y_pred_reg = knn_reg.predict(X_test_scaled)
# 使用均方误差 (MSE) 评估
mse = mean_squared_error(y_test, y_pred_reg)
print(f"KNeighborsRegressor MSE: {mse:.4f}")
print(f"KNeighborsRegressor RMSE: {np.sqrt(mse):.4f}")
# 对新样本进行预测示例(必须进行类似的缩放)
sample_new_data = X_test_scaled[0].reshape(1, -1) # 取第一个测试样本
predicted_value = knn_reg.predict(sample_new_data)
print(f"预测值 (Predicted value) for a sample: {predicted_value[0]:.4f}, 真实值 (True value): {y_test[0]:.4f}")

输出(值取决于数据集和分割方式)

Section titled “输出(值取决于数据集和分割方式)”
KNeighborsRegressor MSE: 0.5568
KNeighborsRegressor RMSE: 0.7462
Predicted value for a sample: 0.9695, True value: 0.4770

参数 weights='distance' 通常通过给予距离更近的邻居更大的影响来提高性能。k 的选择和距离度量对于 k-NN 回归器来说同样是重要的考虑因素。

有关更多详细信息,请参阅 Scikit-learn 用户指南 (User Guide):https://scikit-learn.org/stable/modules/neighbors.html