K近邻算法等

news/2025/3/14 19:53:28/文章来源:https://www.cnblogs.com/luweiseu/p/18772762

1. KNN算法和KD - tree总结

1.1 KNN算法

模型

K近邻(K - Nearest Neighbors,KNN)算法是一种基本的分类与回归方法。它的模型实际上是对特征空间的划分,给定一个训练数据集,对于新的输入实例,在训练数据集中找到与该实例最邻近的 \(K\) 个实例,然后根据这 \(K\) 个实例的类别来决定新实例的类别(分类问题)或值(回归问题)。在分类问题中,通常采用多数表决规则来确定新实例的类别。

策略

KNN算法的策略是经验风险最小化。要使误分类率最小即经验风险最小,就要使“后验概率最大”(对应你提到的 [插图],这里后验概率指的是在已知特征向量的情况下,属于某一类别的概率)。在分类问题中,多数表决规则等价于经验风险最小化。设分类的损失函数为 0 - 1 损失函数:

\[L(Y, f(X)) = \begin{cases} 1, & Y \neq f(X) \\ 0, & Y = f(X) \end{cases} \]

经验风险为:

\[R_{emp}(f) = \frac{1}{N} \sum_{i = 1}^{N} L(y_i, f(x_i)) \]

对于新的输入 \(x\),它的类别预测为:

\[\hat{y} = \arg \max_{c_j} \sum_{x_i \in N_k(x)} I(y_i = c_j) \]

其中 \(N_k(x)\)\(x\)\(K\) 近邻点的集合,\(I\) 是指示函数,这个多数表决规则能使经验风险最小。

方法

KNN算法的核心是计算实例之间的距离,常用的距离度量是欧氏距离:

\[d(x_i, x_j) = \sqrt{\sum_{l = 1}^{n} (x_i^{(l)} - x_j^{(l)})^2} \]

对于新的输入实例,计算它与训练数据集中所有实例的距离,然后选取距离最近的 \(K\) 个实例,根据多数表决规则确定其类别。

1.2 KD - tree

KD - tree(K - Dimensional Tree)是一种对 \(K\) 维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。

模型

KD - tree 是一种二叉树,它将 \(K\) 维空间进行递归划分。每个内部节点表示一个超矩形区域,通过选择一个坐标轴和该坐标轴上的一个分割点,将该区域划分为两个子区域。叶节点存储一个或多个实例点。

策略

KD - tree 的构建策略是通过递归地选择坐标轴和分割点,使得树的结构尽可能平衡,从而提高搜索效率。通常选择方差最大的坐标轴作为分割坐标轴,选择该坐标轴上的中位数作为分割点。

方法
  • 构建 KD - tree:从根节点开始,选择一个坐标轴和分割点,将数据集划分为两部分,分别作为左右子树的数据集,递归地构建左右子树。
  • 搜索 KD - tree:对于一个查询点,从根节点开始,根据查询点在分割坐标轴上的值与分割点的大小关系,选择进入左子树或右子树进行搜索。同时,需要回溯检查其他可能包含最近邻的区域。

2. Python代码实现

import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score# 定义 KD - tree 节点类
class KDNode:def __init__(self, point, split_axis, left=None, right=None):self.point = pointself.split_axis = split_axisself.left = leftself.right = right# 构建 KD - tree
def build_kdtree(points, depth=0):if len(points) == 0:return Nonek = len(points[0])split_axis = depth % ksorted_points = sorted(points, key=lambda point: point[split_axis])median_index = len(sorted_points) // 2return KDNode(sorted_points[median_index],split_axis,build_kdtree(sorted_points[:median_index], depth + 1),build_kdtree(sorted_points[median_index + 1:], depth + 1))# 计算两点之间的欧氏距离
def euclidean_distance(point1, point2):return np.sqrt(np.sum((np.array(point1) - np.array(point2)) ** 2))# 在 KD - tree 中搜索最近的 K 个点
def knn_search_kdtree(root, point, k, depth=0, heap=None):if root is None:return []if heap is None:heap = []k = min(k, len(heap) + 1)split_axis = root.split_axisnext_branch = Noneopposite_branch = Noneif point[split_axis] < root.point[split_axis]:next_branch = root.leftopposite_branch = root.rightelse:next_branch = root.rightopposite_branch = root.left# 递归搜索下一个分支knn_search_kdtree(next_branch, point, k, depth + 1, heap)# 计算当前节点到查询点的距离dist = euclidean_distance(point, root.point)if len(heap) < k:heap.append((dist, root.point))heap.sort(key=lambda x: x[0])elif dist < heap[-1][0]:heap[-1] = (dist, root.point)heap.sort(key=lambda x: x[0])# 检查对面的分支是否可能包含更近的点if len(heap) < k or abs(point[split_axis] - root.point[split_axis]) < heap[-1][0]:knn_search_kdtree(opposite_branch, point, k, depth + 1, heap)return heap# KNN 分类器
class KNNClassifier:def __init__(self, k=3):self.k = kself.kdtree = Noneself.labels = Noneself.X_train = Nonedef fit(self, X, y):self.kdtree = build_kdtree(X)self.labels = yself.X_train = Xdef predict(self, X):predictions = []for point in X:neighbors = knn_search_kdtree(self.kdtree, point, self.k)neighbor_points = [neighbor[1] for neighbor in neighbors]neighbor_indices = []for neighbor_point in neighbor_points:for i, train_point in enumerate(self.X_train):if np.array_equal(train_point, neighbor_point):neighbor_indices.append(i)breakneighbor_labels = [self.labels[index] for index in neighbor_indices]most_common = Counter(neighbor_labels).most_common(1)[0][0]predictions.append(most_common)return np.array(predictions)# 生成随机数据
X, y = make_classification(n_samples=200, n_features=2, n_informative=2, n_redundant=0, random_state=42, n_clusters_per_class=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 创建 KNN 分类器并训练
knn = KNNClassifier(k=3)
knn.fit(X_train, y_train)# 进行预测
y_pred = knn.predict(X_test)# 计算准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"KNN 分类器的准确率: {accuracy}")# 绘制决策边界
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),np.arange(y_min, y_max, 0.02))
Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)# 绘制决策区域
plt.contourf(xx, yy, Z, alpha=0.4)# 绘制训练数据点
plt.scatter(X_train[:, 0], X_train[:, 1], c=y_train, edgecolors='k', marker='o', s=80, label='Training Data')
# 绘制测试数据点
plt.scatter(X_test[:, 0], X_test[:, 1], c=y_test, edgecolors='k', marker='s', s=80, label='Test Data')plt.title('KNN Decision Boundary')
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.legend()
plt.show()

代码解释

  • KDNode类:定义了KD - tree的节点结构,包含节点的坐标、分割轴以及左右子节点。
  • build_kdtree函数:递归地构建KD - tree,根据当前深度选择分割轴,使用中位数作为分割点。
  • euclidean_distance函数:计算两点之间的欧氏距离。
  • knn_search_kdtree函数:在KD - tree中搜索最近的 \(K\) 个点,使用递归和回溯的方法。
  • KNNClassifier类:实现了KNN分类器,包括训练和预测方法。
  • 数据生成和评估:使用make_classification函数生成随机分类数据,将数据划分为训练集和测试集,训练KNN分类器并计算准确率。

KD - tree(K维树)是一种用于对K维空间中的数据点进行组织和索引的数据结构,常用于高效地进行最近邻搜索等操作。其构造和查询的复杂度如下:

  • 构造复杂度
    • 理想情况下,每次划分都能将数据集均匀地分成两部分,对于包含 \(n\) 个数据点的 \(k\) 维数据集,构造KD - tree的时间复杂度为 \(O(nlogn)\)。这是因为在每一层,我们需要 \(O(n)\) 的时间来找到划分点,而树的深度为 \(O(logn)\)
    • 最坏情况下,数据点分布不均匀,导致树的构造退化为线性结构,此时构造KD - tree的时间复杂度为 \(O(n^2)\)
  • 查询复杂度
    • 在理想情况下,查询一个点的最近邻时,每次迭代都能排除一半的空间,时间复杂度为 \(O(logn)\)
    • 最坏情况下,查询复杂度可能达到 \(O(n)\),例如当数据点分布呈病态,或者查询的点位于树的边界附近,需要访问大量节点时。

实际应用中,KD - tree的性能通常较好,能够在接近 \(O(logn)\) 的时间复杂度内完成查询操作,尤其是对于低维数据(如二维、三维数据)效果更为显著。但随着维度的增加,KD - tree的性能可能会下降,出现“维度灾难”问题。

HNSW(Hierarchical Navigable Small World)是一种用于高维空间中近似最近邻搜索的算法。以下是对HNSW的解释以及它与KNN、KD - tree的对比:

HNSW算法原理

  • HNSW构建了一个分层的图结构,每个节点在不同层次上与其他节点相连。底层包含所有的数据点,随着层次的升高,节点数量逐渐减少,形成一种类似于金字塔的结构。
  • 在搜索时,从高层开始,利用节点之间的连接快速定位到可能包含目标最近邻的区域,然后在底层进行更精确的搜索。这种分层结构和连接方式使得HNSW能够在高维空间中高效地进行近似最近邻搜索,大大减少了搜索时间和空间复杂度。

对比

  • 搜索效率
    • KNN:在数据量较大时,每次搜索都需要遍历整个数据集来计算距离并找到最近邻,搜索效率较低,时间复杂度为 \(O(n)\),其中 \(n\) 是数据集的大小。
    • KD - tree:通过构建树结构来划分数据空间,在理想情况下可以将搜索复杂度降低到 \(O(logn)\),但在高维空间中性能会下降,甚至可能退化为线性搜索。
    • HNSW:在高维空间中具有出色的搜索效率,能够在较短时间内找到近似最近邻。它利用分层图结构和启发式搜索策略,大大减少了搜索的范围和时间,通常比KD - tree在高维数据上表现更好。
  • 空间复杂度
    • KNN:不需要额外的空间来存储数据结构,只需要存储数据集本身,空间复杂度为 \(O(n)\)
    • KD - tree:需要额外的空间来存储树结构,包括节点信息、划分维度等,空间复杂度通常为 \(O(n)\),但在某些情况下可能会更高。
    • HNSW:构建的分层图结构需要较多的额外空间来存储节点之间的连接信息,空间复杂度相对较高,一般为 \(O(nc)\),其中 \(c\) 是一个与图的连接密度相关的常数。
  • 数据适应性
    • KNN:对数据的分布没有特殊要求,适用于各种类型的数据,但对于大规模数据和高维数据,性能会受到影响。
    • KD - tree:对于低维数据且分布较为均匀的数据表现较好,但对于高维数据和分布不均匀的数据,可能会出现性能下降甚至失效的情况。
    • HNSW:适用于各种类型的数据,尤其是在高维数据上表现出较好的适应性和鲁棒性,能够处理不同分布的数据。
  • 准确性
    • KNN:是一种精确的最近邻搜索算法,只要计算出所有数据点与查询点的距离,就能准确找到最近邻。
    • KD - tree:在理想情况下能够准确找到最近邻,但在某些情况下,由于树的划分方式和数据分布的影响,可能无法找到全局最优的最近邻。
    • HNSW:是一种近似最近邻搜索算法,它不能保证找到的一定是全局最优的最近邻,但在大多数情况下,能够找到非常接近真实最近邻的结果,在实际应用中,这种近似结果通常已经能够满足需求。

综上所述,KNN是一种简单直接的精确最近邻搜索算法,适用于小规模数据;KD - tree在低维数据上有较好的性能,但对高维数据适应性有限;HNSW则是一种高效的近似最近邻搜索算法,在高维数据和大规模数据上表现出色,能够在较短时间内找到近似最优解,在实际应用中得到了广泛的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/898835.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正则表达式--java进阶day06

1.正则表达式2.正则表达式的规则、使用3.字符类讲解如图,单独一个a满足正则表达式的规则,所以返回true当删去[]后,正则表达式中的规则就会变为必须是abc,否则不满足条件,即使有一个a该规则是指a-d或者m-p,可以写成[a-dm-p]4.预定义字符类注意事项 正则表达式中存在数量问…

探秘Transformer系列之(13)--- FFN

从零开始解析Transformer,目标是:(1) 解析Transformer如何运作,以及为何如此运作,让新同学可以入门;(2) 力争融入一些比较新的或者有特色的论文或者理念,让老鸟也可以有所收获。探秘Transformer系列之(13)--- FFN 目录探秘Transformer系列之(13)--- FFN0x00 概述0x01…

EXCEL-时间函数

💖简介 在Excel中,时间函数用于处理和操作日期和时间数据; 以下是Excel中常用的时间函数及其常见应用场景的总结.📖函数 ⭐时间函数基础 🌟TIME语法:TIME(hour, minute, second) 功能:将小时、分钟、秒转换为时间序列号(0到0.99999999之间的数值)。 示例:TIME(9,30…

day29linux三剑客----sed

day29linux三剑客----sed单个正则字符还认识组合到一起就晕了,怎么办?本质还是对单个字符没理解.认识*认识.*组合到就一起就蒙了,为什么?还是没想明白.的意义,*的意义正则表达式,从左向右,逐步理解单个字符的意义怎么做? 1.思维脑图写没写? 2.每一个正则表达式的符号,…

3.14 学习记录

基于Android Studio 完成了简单的石家庄地铁购票APP

Android配置

将grade-wrapper.properties中地址改为 https://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-bin.zip 等待下载。一般要几个小时。

sqlserver 的视图创建

首先,什么是视图?视图是一种数据库对象,是从一个或者多个数据表或视图中导出的虚表,视图的结构和数据是对数据表进行查询的结果,只存放视图的定义,不存放视图对应的数据; 其结构和和数据是建立在对表的查询基础上,故表中的数据发生变化,从视图中查询出的数据也随之改变…

day:21 python——判断语句

一.if语句 (1)单分支: 格式: if 判断条件: 执行语句块 else: 执行语句块2 备注:判断条件 if中可以使用比较运算符,<,!=,==,>=,<=在学习自动化中也可以用if语句断言, 案例1: a=10 if a != 10: print("你中奖了") else: print("谢谢惠顾"…

clickhouse 开启认证

配置文件说明 默认路径:/etc/clickhouse-server/users.xml 密码存储类型 明文密码(不推荐) <password>qwerty</password> <!-- 直接明文存储 --> SHA256 哈希 <password_sha256_hex>5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d15…

二分查找--java进阶day06

1.二分查找 https://kdocs.cn/l/ciMkwngvaWfz?linkname=150996908 二分查找:每一次查找都从中间的元素查起,根据比较的大小来折半,以此类推,直到最后找到该数2.二分查找的前提 确保查找的数组是排好序的数组,否则就会出错 如下图,假设我们要找的是200,箭头指向88,发现…

Fastjson 和Jackson 兼容性问题

起因是因为java类里面多层嵌套验证发现APIFOX调用接口验证无法生效 发现是因为没有赋值嵌套类 经过查看属性填充的源码,requestbody传参,用的是Fastjson的注解但是走到了AbstractJackson2HttpMessageConverter类的readJavaType方法 一查发现可能有不兼容的问题,Jackson 需要…

modelsim保存波形图为图片报错:image file format bmp is unknown

在波形窗口依次点击 File-->Export-->Image 如果版本低于2020.2,会报错:解决方案为: 在命令框输入:package require Img 解决方案来自Why cant I export the wave image in bmp format in the ModelSim-Intel FPGA Edition and Questa-Intel FPGA Edition simulator?…