K-近邻(K-NN)算法的原理与实现
字数 1627 2025-11-30 00:01:22
K-近邻(K-NN)算法的原理与实现
K-近邻(K-Nearest Neighbors, K-NN)是一种基本且直观的机器学习算法,既可用于分类任务,也可用于回归任务。其核心思想是:一个样本的类别或数值可以由其“邻居”的多数意见或平均值来决定。
一、算法核心思想与关键概念
- 基本假设:相似的数据点在特征空间中距离较近。所谓“物以类聚”。
- 惰性学习:K-NN是一种“惰性学习”算法。它不像逻辑回归或决策树那样在训练阶段就建立一个明确的模型(即一个函数)。相反,它只是把所有的训练数据“记下来”。真正的“学习”过程发生在预测阶段。这使得其训练很快,但预测可能较慢。
- 关键参数 K:
K是一个正整数,代表我们要考虑的“邻居”的数量。- K值太小(如K=1):模型会变得非常复杂,容易受到训练数据中噪声数据的干扰,导致过拟合。
- K值太大:模型会变得过于简单,可能会忽略数据中一些有用的局部模式,导致欠拟合。通常通过交叉验证来选择一个合适的K值。
二、算法步骤(以分类问题为例)
假设我们有一个已标注好类别的训练数据集,现在来了一个新的、未知类别的数据点(查询点),我们要预测它的类别。
第一步:选择距离度量方法
我们需要一个数学公式来计算新数据点与训练数据中每个点的“相似度”或“距离”。最常用的方法是欧氏距离。
- 对于两个n维向量点 \(A(x_1, x_2, ..., x_n)\) 和 \(B(y_1, y_2, ..., y_n)\),它们的欧氏距离为:
\[ d(A, B) = \sqrt{(x_1 - y_1)^2 + (x_2 - y_2)^2 + ... + (x_n - y_n)^2} \]
- 其他距离度量方法还包括曼哈顿距离、闵可夫斯基距离、余弦相似度等,需根据数据特性选择。
第二步:计算距离
遍历整个训练数据集,计算查询点与每一个训练样本点之间的距离。
第三步:找出K个最近邻
将所有计算出的距离从小到大进行排序,然后选择距离最小的前K个训练样本点。这K个点就是查询点的“K个最近邻”。
第四步:进行投票决策
查看这K个最近邻样本点的类别标签。采用“多数表决”的原则,将K个邻居中出现次数最多的那个类别,预测为查询点的类别。
举例说明:
假设K=5,一个新点的5个最近邻中,有3个属于“类别A”,2个属于“类别B”。那么,算法就会预测这个新点属于“类别A”。
三、算法实现(Python伪代码)
下面是一个简化版的K-NN分类器实现,帮助你理解其核心逻辑。
import numpy as np
from collections import Counter
class KNNClassifier:
def __init__(self, k=3):
self.k = k
self.X_train = None # 训练特征
self.y_train = None # 训练标签
def fit(self, X, y):
"""训练模型(惰性学习,只是存储数据)"""
self.X_train = X
self.y_train = y
def predict(self, X):
"""预测新数据点的类别"""
# X 可以是一个点,也可以是一组点。这里假设X是一组点。
predictions = [self._predict_single(x) for x in X]
return np.array(predictions)
def _predict_single(self, x):
"""预测单个数据点的类别"""
# 1. 计算x与所有训练数据的距离
distances = [self._euclidean_distance(x, x_train) for x_train in self.X_train]
# 2. 获取距离最近的k个样本的索引
k_indices = np.argsort(distances)[:self.k] # argsort返回的是索引值,从小到大排序
# 3. 获取这k个邻居的标签
k_nearest_labels = [self.y_train[i] for i in k_indices]
# 4. 多数表决,返回出现次数最多的标签
most_common = Counter(k_nearest_labels).most_common(1)
return most_common[0][0] # 返回出现次数最多的那个元组的标签
def _euclidean_distance(self, point1, point2):
"""计算两点之间的欧氏距离"""
return np.sqrt(np.sum((point1 - point2) ** 2))
# 使用示例
if __name__ == "__main__":
# 假设有一些训练数据
X_train = np.array([[1, 2], [2, 3], [3, 1], [6, 5], [7, 7], [8, 6]])
y_train = np.array([0, 0, 0, 1, 1, 1]) # 两个类别:0和1
# 创建KNN分类器,设置K=3
knn = KNNClassifier(k=3)
knn.fit(X_train, y_train)
# 预测一个新点
X_new = np.array([[5, 4]])
prediction = knn.predict(X_new)
print(f"预测类别为: {prediction}") # 输出可能是1,因为[5,4]更接近类别1的点
四、K-NN用于回归问题
K-NN也可以用于回归任务。步骤几乎完全相同,唯一的区别在于第四步:
- 分类:对K个邻居的标签进行投票。
- 回归:对K个邻居的连续值标签取平均值(或加权平均)作为预测值。
五、K-NN的优缺点
优点:
- 原理简单,易于理解和实现。
- 无需训练过程,适合增量学习(新数据可直接加入数据集)。
- 对数据分布没有假设,适用于各种复杂的数据模式。
缺点:
- 预测速度慢:当训练集很大时,每次预测都需要计算与所有样本的距离,计算成本高。可以使用KD-Tree、Ball-Tree等数据结构来加速近邻搜索。
- 对特征尺度敏感:如果某个特征的数值范围远大于其他特征,它在距离计算中的权重就会占主导地位。因此,数据标准化/归一化是使用K-NN前至关重要的一步。
- 对高维数据效果不佳:在高维空间中,点与点之间的距离会变得很相似,导致“维度灾难”,区分度下降。
- 对不平衡数据敏感:如果某个类别的样本数量远多于其他类别,那么新样本的K个邻居中很可能总是多数类占优。
总结
K-NN是一种基于实例的、惰性的学习算法。其核心是“距离度量”和“K值选择”。虽然简单,但在许多实际问题中非常有效,是机器学习入门必学的经典算法之一。在实际应用中,务必注意数据预处理(特别是归一化)和模型参数(K值)的调优。