K近邻算法(KNN)的基本思想是通过计算待分类样本与训练集中所有样本之间的距离,选取距离最近的 K 个样本,根据这些样本的标签进行分类或回归。KNN 属于非参数学习算法,因为它不假设数据的分布形式,主要依赖距离度量来进行决策。
优点
缺点
异常值的影响。
对于分类问题,KNN 模型可以表示为:
f ( x ) = argmax c ∑ i = 1 K 1 ( y i = c ) f(\mathbf{x}) = \underset{c}{\text{argmax}} \sum_{i=1}^K \mathbf{1}(y_i = c) f(x)=cargmaxi=1∑K1(yi=c)
其中:
对于回归问题,KNN 模型的预测可以通过 K 个邻近样本的标签值的平均来实现:
y ^ = 1 K ∑ i = 1 K y i \hat{y} = \frac{1}{K} \sum_{i=1}^K y_i y^=K1i=1∑Kyi
或使用加权平均:
y ^ = ∑ i = 1 K w i ⋅ y i ∑ i = 1 K w i \hat{y} = \frac{\sum_{i=1}^K w_i \cdot y_i}{\sum_{i=1}^K w_i} y^=∑i=1Kwi∑i=1Kwi⋅yi
其中 w i w_i wi 是样本 y i y_i yi 的权重,通常可以根据距离的倒数或其他方式定义。
KNN 算法中,距离度量是核心部分,决定了如何选择邻近样本。常用的距离度量方法包括:
欧氏距离(Euclidean Distance):
欧氏距离是最常见的距离度量方法,适用于特征尺度相同的场景。对于两个样本 x i \mathbf{x}_i xi 和 x j \mathbf{x}_j xj,欧氏距离定义为:
d ( x i , x j ) = ∑ k = 1 n ( x i k − x j k ) 2 d(\mathbf{x}_i, \mathbf{x}_j) = \sqrt{\sum_{k=1}^n (x_{ik} - x_{jk})^2} d(xi,xj)=k=1∑n(xik−xjk)2
其中:
曼哈顿距离(Manhattan Distance):
曼哈顿距离适用于高维空间且特征不相关的情况。曼哈顿距离定义为:
d ( x i , x j ) = ∑ k = 1 n ∣ x i k − x j k ∣ d(\mathbf{x}_i, \mathbf{x}_j) = \sum_{k=1}^n |x_{ik} - x_{jk}| d(xi,xj)=k=1∑n∣xik−xjk∣
曼哈顿距离计算的是两个样本在各个维度上的绝对差值之和。
闵可夫斯基距离(Minkowski Distance):
闵可夫斯基距离是欧氏距离和曼哈顿距离的泛化形式,定义为:
d ( x i , x j ) = ( ∑ k = 1 n ∣ x i k − x j k ∣ p ) 1 / p d(\mathbf{x}_i, \mathbf{x}_j) = \left(\sum_{k=1}^n |x_{ik} - x_{jk}|^p\right)^{1/p} d(xi,xj)=(k=1∑n∣xik−xjk∣p)1/p
其中:
KNN 的决策规则基于多数投票或加权投票的原则:
分类问题:
回归问题:
w i = 1 d ( x i , x ) w_i = \frac{1}{d(\mathbf{x}_i, \mathbf{x})} wi=d(xi,x)1
K 值是 KNN 算法中的重要参数,决定了选择多少个邻近样本参与投票。K 值的选择通常通过交叉验证来确定。一般来说:
为了加速 KNN 的搜索过程,可以使用 KD 树进行优化。KD 树是一种对 k 维空间数据进行分割的树形数据结构,通过这种数据结构可以有效地减少搜索空间,从而提高查找效率。
KD树的构建过程:
KD树的搜索过程:
优缺点:
KNN 的时间复杂度较高,特别是在大规模数据集上,因为需要计算待分类样本与训练集中所有样本之间的距离。对于 N 个训练样本,每次分类的时间复杂度为 O ( N × D ) O(N \times D) O(N×D),其中 D 为特征维度。这使得 KNN 在大规模数据集或高维空间中不太适用。
以下是KNN算法的简单实现:
import numpy as np
from collections import Counter
def knn_predict(X_train, y_train, X_test, k=3):
predictions = []
for x_test in X_test:
# 计算测试样本与所有训练样本之间的距离
distances = np.linalg.norm(X_train - x_test, axis=1)
# 获取距离最近的 k 个样本的索引
k_indices = np.argsort(distances)[:k]
# 获取这些样本的标签
k_nearest_labels = y_train[k_indices]
# 投票选择出现频率最高的标签
most_common = Counter(k_nearest_labels).most_common(1)
predictions.append(most_common[0][0])
return predictions
如果需要在大规模数据集上提高效率,可以使用 KD树 优化的版本。以下是构建 KD 树的伪代码:
class KDNode:
def __init__(self, point, left=None, right=None):
self.point = point
self.left = left
self.right = right
def build_kdtree(points, depth=0):
if not points:
return None
k = len(points[0]) # 维度
axis = depth % k # 选择分割维度
points.sort(key=lambda x: x[axis])
median = len(points) // 2 # 选择中位数
# 递归构建 KD 树
return KDNode(
point=points[median],
left=build_kdtree(points[:median], depth + 1),
right=build_kdtree(points[median + 1:], depth + 1)
)
KNN算法是一种简单直观且有效的分类和回归方法,但其计算复杂度较高,特别是在高维空间和大规模数据集上。通过引入 KD树,可以在低维空间中显著提高KNN的效率。然而,在高维空间中,KD树的效果会减弱。因此,在实际应用中,需要根据数据集的具体特征选择合适的优化技术。