📖概念
KNN(K-Nearest Neighbour),即K近邻算法。常用于分类,也适用于回归。
该方法的思路是:给定测试样本,基于某种距离度量找出训练集中与其最近的K个样本,如果这些样本多数属于某一个类别(或数值区间),则该测试样本也属于这个类别(预测数值取平均)。K近邻中的'K'就代表参考“几个”近邻。
简单来说,KNN是一种参照“就近原则”的算法,用最近的K个实例说话,遵守“人多力量大”的规矩。
K近邻是一种“懒惰学习”,把训练数据集作为分类的依据,但并不对其进行学习和建模。对于特征空间维数大及数据容量大的训练集,预测速度慢。
🧩三要素
用KNN实现预测涉及到几个关键要素,分别是:计算距离、选择K值、决策规则
🍋距离度量
由不同的距离度量所确定的最近邻点是不同的。
常见的距离计算方式有:
📏欧式距离:两点之间计算直线距离,⭐最常用。对数据尺度敏感,通常需要归一化①或标准化②消除差异,消除量纲影响。
计算公式:,平方和的平方根
📏曼哈顿距离:又称城市街区距离或L1距离,因为两个实值向量之间的距离是根据一个人只能以直角移动计算的。
计算公式:,绝对值之和
📏切比雪夫距离:又称为棋盘距离,各个坐标距离的最大值。
计算公式:,最大绝对值
📏闵可夫斯基距离:上述距离度量的广义形式,
计算公式:
当p=1时等价于曼哈顿距离,p=2时为欧氏距离,p→∞时为切比雪夫距离。
🍋K值选择
根据给定的距离度量,在训练集中找出与x最邻近的k个点。K表示要考虑的邻近点的数量。
k=1时,称为最近邻算法。
- 较小的K值搜索的邻域较小,对近邻的实例点敏感,就近的样本更能反映相似性,近似误差小;但模型过度拟合最近的数据,抗噪声干扰性弱,估计误差大。
- 较大的K值参考的实例点更多,但较远的点也会产生影响,尽管他们并不具备代表性。
K值的选取是个权衡策略,通常采用交叉验证法③来选取最优的K值。
🍋决策规则
对于分类任务,往往是“多数表决”,K个样本中哪个类别出现最多,就归为哪个类;
对于回归任务,取K个样本的平均值;
还可基于距离远近进行加权平均或加权投票,距离越近的样本权重越大。
🌾KNN的实现
💾线性扫描
最简单的方法,穷举计算,计算输入实例与每一个训练实例的距离,不适用于大容量训练集,非常耗时。
🎄kd树
是一种记录实例点空间信息的二叉树,便于快速检索数据。把整个k维空间进行切分并以树状结构进行记录,每个节点对应一个超矩形区域。根据包含目标的叶节点依次向根部回退,不断查找与目标点最邻近的节点,把搜索范围限制在局部区域上,节省计算量。
建树流程:
- 构造根节点,对应包含所有样本的最小超矩形。
- 选定一个维度对实例点排序,按中位数把数据区域切分为两个子区域,作为两个子节点
- 递归切分过程直至区域内实例点不再可分为止。
注意:依次选择坐标轴对空间切分,选择训练实例点在选定坐标轴上的中位数为切分点,这样得到的kd树是平衡的,搜索路径更短。
搜索策略:
- 先找到包含目标点的叶节点
- 从该叶节点出发,依次回退到父节点,不断查找与目标点最邻近的节点
- 确定不存在更近的节点时终止搜索。
如果实例点是随机分布的,kd树搜索的平均计算复杂度是O(logN),这里N是训练实例数。
🎄球树
kd树把数据空间划分为一个个超矩形,一定程度上缩小了搜索范围,但随着维度的增加,数据的分布更加稀疏,划分出的区域数量会呈指数级增长,每个区域可能包含很少的数据点,搜索变得复杂而低效。
球树,顾名思义,就是每个分割块都是超球体,相比之下,这种结构在高维空间中更为紧凑,能够更有效地覆盖数据点。
建树流程:
球树的搜索策略考虑了数据点到球心的距离,而不是像kd树那样仅仅基于维度的划分。
- 先构建一个超球体,这个超球体是可以包含所有样本的最小球体。
- 从球中选择第一个点(离球的中心最远的点),然后选择第二个点(离第一个点最远的点);将球中所有的点分配到离这两个聚类中心最近的一个上,然后计算每个聚类的中心,以及聚类能够包含它所有数据点所需的最小半径。这样我们得到了两个子超球体,对应kd树里的左右子树。
- 对这两个子超球体,递归执行步骤2,直到满足某个停止条件(如达到预设的树深度或每个子超球体包含的样本数少于某个阈值)
搜索策略:
- 首先找出包含目标点所在的叶子节点,并在这个叶子节点所代表的球内找出与目标点最邻近的点,这确定了目标点距离其最近邻点的一个上限值。
- 接着,检查兄弟节点,如果目标点到兄弟节点中心的距离超过兄弟节点的半径与当前上限值之和,则兄弟节点里不可能存在一个更近的点;否则,进一步检查位于兄弟节点以下的子树。
- 完成兄弟节点的检查后,向父节点回溯,继续搜索最小邻近值,直到回溯到根节点,此时的最小邻近值就是最终的搜索结果。
python代码
使用scikit-learn实现KNN
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 加载数据集
iris = load_iris()
X = iris.data
y = iris.target
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建KNN分类器实例
# algorithm参数可缺省,可以是'auto'(默认), 'ball_tree', 'kd_tree' 或 'brute'
# 'auto': fit方法接收稀疏输入,将使用球树,否则使用kd树
# 'brute': 线性扫描
knn = KNeighborsClassifier(n_neighbors=3)
# 在训练集上训练KNN分类器
knn.fit(X_train, y_train)
# 在测试集上进行预测
y_pred = knn.predict(X_test)
# 计算并打印准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy}")
文本注释
①归一化:通常指线性函数归一化(Min-Max Scaling),对数据进行线性变换,使结果映射到[0, 1]的范围,实现对数据的等比缩放。
计算公式:
②标准化:通常指零均值标准化(Z-Score Normalization),将数据映射到均值为0,标准差为1的分布上。
计算公式:,μ和σ指原始特征的均值和标准差
③ 交叉验证法:划分原始数据集为多个子集,得到不同的训练和验证集组合,方便多次实验并评估。此处用于多次试验,比较所有K值在验证集上的性能,选择性能最好的结果。
参考资料
- 周志华《机器学习》P225
- 李航《机器学习方法》P41
- kd 树算法之思路篇、kd 树算法之详细篇
- Ball Tree