ML 系列:机器学习和深度学习的深层次总结(16) — 提高 KNN 效率-使用 KD 树和球树实现更快的算法

news2024/12/22 19:42:48

一、说明

        在机器学习系列的第 16 节,我们重点介绍了提高 K 最近邻 (KNN) 算法的效率,这是一种广泛用于分类和回归任务的方法。虽然 KNN 简单有效,但对于大型数据集来说,其计算成本可能会令人望而却步。为了解决这个问题,我们引入了两种高级数据结构:KD 树和球树,它们显着提高了 KNN 搜索的速度。

二、了解 KNN 的挑战

KNN 的工作原理是查找离查询点最近的数据点,并根据其标签进行预测。但是,简单方法需要计算从查询点到数据集中每个点的距离,导致每个查询的时间复杂度为 O(n),其中 n 是数据点的数量。对于大型数据集,这可能非常慢,尤其是在高维空间中。

三、KD 树:高效的空间分区

KD 树(K 维树)是一种二叉树数据结构,它以递归方式将数据空间划分为超矩形。以下是他们改进 KNN 的方法:

1. 构建:根据一个维度的中位数,在每个节点将数据集分为两半,在每个级别的维度之间循环。
2. 搜索:不是将查询点与每个数据点进行比较,而是将搜索限制在树的特定分支上,从而显著减少了距离计算的数量。

优势:KD 树对于低维到中高维数据(通常最多 20 维)非常有效。
限制:由于维度的诅咒,性能会随着维度的增加而降低。

3.1 球树:更适合高尺寸

球树解决了 KD 树的一些限制,尤其是在更高的维度中。它们使用不同的分区策略:

1. 构造:数据空间被划分为嵌套的超球体(球),每个超球体都包含一个数据点的子集。
2. 搜索:与 KD 树一样,球树通过仅关注相关区域来修剪大部分搜索空间。

优势:球树在高维空间中的性能优于 KD 树,并且可以更有效地处理非均匀数据分布。
限制:它们通常比 KD 树更复杂地实现和维护。

3.2 在实践中实现 KD 树和球树

KD 树和球树在流行的机器学习库(如 scikit-learn)中都受支持。以下是如何在 scikit-learn 中使用这些结构实现 KNN 的简短示例:

from sklearn.neighbors import KNeighborsClassifier
# Using KD-tree
knn_kd = KNeighborsClassifier(algorithm='kd_tree')
knn_kd.fit(X_train, y_train)
predictions_kd = knn_kd.predict(X_test)
# Using Ball Tree
knn_ball = KNeighborsClassifier(algorithm='ball_tree')
knn_ball.fit(X_train, y_train)
predictions_ball = knn_ball.predict(X_test)

四、KD树的原理

4.1 什么是K维树?

KD 树(也称为 K 维树)是一种二叉搜索树,其中每个节点中的数据是空间中的 K 维点。简而言之,它是一种空间分区(详情如下)数据结构,用于组织 K 维空间中的点。KD 树中的非叶节点将空间分为两部分,称为半空间。该空间左侧的点由该节点的左子树表示,空间右侧的点由右子树表示。我们很快就会解释如何划分空间和形成树的概念。为简单起见,让我们通过一个例子来理解 2-D 树。根将有一个 x 对齐的平面,根的子节点都将具有 y 对齐的平面,根的孙子节点都将具有 x 对齐的平面,根的曾孙节点都将具有 y 对齐的平面,依此类推。
 

 概括:我们将平面编号为 0、1、2、…(K – 1)。从上面的例子中可以清楚地看出,深度为 D 的点(节点)将具有 A 对齐平面,其中 A 的计算方式为:A = D mod K

 如何确定一个点位于左子树还是右子树?如果根节点在平面 A 中对齐,则左子树将包含该平面中坐标小于根节点的所有点。同样,右子树将包含该平面中坐标大于或等于根节点的所有点。

 创建二维树:考虑二维平面中的以下点:(3, 6)、(17, 15)、(13, 15)、(6, 12)、(9, 1)、(2, 7)、(10, 19)

  1. 插入(3,6):由于树为空,因此将其作为根节点。
  2. 插入(17,15):将其与根节点点进行比较。由于根节点是 X 对齐的,因此将比较 X 坐标值以确定它位于右子树还是左子树中。该点将 Y 对齐。
  3. 插入 (13, 15):此点的 X 值大于根节点中点的 X 值。因此,这将位于 (3, 6) 的右子树中。再次将此点的 Y 值与点 (17, 15) 的 Y 值进行比较(为什么?)。由于它们相等,因此该点将位于 (17, 15) 的右子树中。该点将 X 对齐。
  4. 插入 (6, 12):此点的 X 值大于根节点中点的 X 值。因此,这将位于 (3, 6) 的右子树中。再次将此点的 Y 值与点 (17, 15) 的 Y 值进行比较(为什么?)。由于 12 < 15,因此此点将位于 (17, 15) 的左子树中。此点将 X 对齐。
  5. 插入(9, 1):同样,该点将位于(6, 12)的右边。
  6. 插入(2, 7):同样,该点将位于(3, 6)的左边。
  7. 插入(10,19):同样,该点将位于(13,15)的左边。

ktree_1

 

空间是如何划分的? 

所有 7 个点都将在 XY 平面上绘制如下:

  1. 点(3, 6)将空间分成两部分:画线 X = 3。

  2. 点 (2, 7) 将线 X = 3 左侧的空间水平分成两部分。在线 X = 3 左侧绘制线 Y = 7。

  3. 点 (17, 15) 将线 X = 3 右侧的空间水平分成两部分。在线 X = 3 右侧绘制线 Y = 15。
     

  4. 点 (6, 12) 将线 Y = 15 下方和线 X = 3 右侧的空间分成两部分。将线 X = 6 绘制在线 X = 3 的右侧和线 Y = 15 下方。
     

  5. 点 (13, 15) 将线 Y = 15 下方和线 X = 6 右侧的空间分成两部分。将线 X = 13 绘制在线 X = 6 的右侧和线 Y = 15 下方。
     

  6. 点 (9, 1) 将线 X = 3、X = 6 和 Y = 15 之间的空间分成两部分。在线 X = 3 和 X = 13 之间绘制线 Y = 1。
     

  7. 点 (10, 19) 将线 X = 3 右侧和线 Y = 15 上方的空间分成两部分。将线 Y = 19 绘制在线 X = 3 右侧和线 Y = 15 上方。
     

以下是 KD 树基本操作(如搜索、插入和删除)的 C++ 实现。 

  • C++
  • Java
  • Python3
  • C#
  • JavaScript

// A C++ program to demonstrate operations of KD tree
#include<bits/stdc++.h>
using namespace std;
 
const int k = 2;
 
// A structure to represent node of kd tree
struct Node
{
    int point[k]; // To store k dimensional point
    Node *left, *right;
};
 
// A method to create a node of K D tree
struct Node* newNode(int arr[])
{
    struct Node* temp = new Node;
 
    for (int i=0; i<k; i++)
       temp->point[i] = arr[i];
 
    temp->left = temp->right = NULL;
    return temp;
}
 
// Inserts a new node and returns root of modified tree
// The parameter depth is used to decide axis of comparison
Node *insertRec(Node *root, int point[], unsigned depth)
{
    // Tree is empty?
    if (root == NULL)
       return newNode(point);
 
    // Calculate current dimension (cd) of comparison
    unsigned cd = depth % k;
 
    // Compare the new point with root on current dimension 'cd'
    // and decide the left or right subtree
    if (point[cd] < (root->point[cd]))
        root->left  = insertRec(root->left, point, depth + 1);
    else
        root->right = insertRec(root->right, point, depth + 1);
 
    return root;
}
 
// Function to insert a new point with given point in
// KD Tree and return new root. It mainly uses above recursive
// function "insertRec()"
Node* insert(Node *root, int point[])
{
    return insertRec(root, point, 0);
}
 
// A utility method to determine if two Points are same
// in K Dimensional space
bool arePointsSame(int point1[], int point2[])
{
    // Compare individual pointinate values
    for (int i = 0; i < k; ++i)
        if (point1[i] != point2[i])
            return false;
 
    return true;
}
 
// Searches a Point represented by "point[]" in the K D tree.
// The parameter depth is used to determine current axis.
bool searchRec(Node* root, int point[], unsigned depth)
{
    // Base cases
    if (root == NULL)
        return false;
    if (arePointsSame(root->point, point))
        return true;
 
    // Current dimension is computed using current depth and total
    // dimensions (k)
    unsigned cd = depth % k;
 
    // Compare point with root with respect to cd (Current dimension)
    if (point[cd] < root->point[cd])
        return searchRec(root->left, point, depth + 1);
 
    return searchRec(root->right, point, depth + 1);
}
 
// Searches a Point in the K D tree. It mainly uses
// searchRec()
bool search(Node* root, int point[])
{
    // Pass current depth as 0
    return searchRec(root, point, 0);
}
 
// Driver program to test above functions
int main()
{
    struct Node *root = NULL;
    int points[][k] = {{3, 6}, {17, 15}, {13, 15}, {6, 12},
                       {9, 1}, {2, 7}, {10, 19}};
 
    int n = sizeof(points)/sizeof(points[0]);
 
    for (int i=0; i<n; i++)
       root = insert(root, points[i]);
 
    int point1[] = {10, 19};
    (search(root, point1))? cout << "Found\n": cout << "Not Found\n";
 
    int point2[] = {12, 19};
    (search(root, point2))? cout << "Found\n": cout << "Not Found\n";
 
    return 0;
}

输出:

成立
未找到
 

时间复杂度:O(n)
辅助空间:O(n)

请参阅以下文章以了解如何查找最小值和删除操作。

  • KD 树 (查找最小值)
  • KD 树(删除)

4.2 K维树的优点-

Kd树作为一种数据结构有几个优点:

  • 高效搜索:Kd 树可有效搜索 k 维空间中的点,例如最近邻搜索或范围搜索。
  • 降维: Kd 树可用于降低问题的维数,从而缩短搜索时间并减少数据结构的内存要求。
  • 多功能性: Kd树可用于广泛的应用,例如数据挖掘、计算机图形学和科学计算。
  • 平衡: Kd 树是自平衡的,这确保了即使插入或删除数据时树仍然保持高效。
  • 增量构建: Kd 树可以增量构建,这意味着可以在结构中添加或删除数据,而不必重建整个树。
  • 易于实现: Kd树相对容易实现,可以用多种编程语言来实现。

参考题目:

Search and Insertion in K Dimensional tree - GeeksforGeeks

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot问卷调查系统小程序【附源码】

基于SpringBoot问卷调查系统小程序 效果如下&#xff1a; 管理员登录界面 管理员功能界面 调查人管理界面 问卷调查管理界面 问卷题目管理界面 用户登录界面 APP首页界面 公告信息界面 研究背景 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&…

组织病理学图像中的再识别|文献速递--基于多模态-半监督深度学习的病理学诊断与病灶分割

Title 题目 Re-identification from histopathology images 组织病理学图像中的再识别 01 文献速递介绍 在光学显微镜下评估苏木精-伊红&#xff08;H&E&#xff09;染色切片是肿瘤病理诊断中的标准程序。随着全片扫描仪的出现&#xff0c;玻片切片可以被数字化为所谓…

怎么在单片机裸机程序中移植EasyLogger?

1、介绍 EasyLogger 是一款超轻量级、高性能的C日志库&#xff0c;非常适合对资源敏感的软件项目。例如&#xff1a;IoT产品、可穿戴设备、智能家居等等。相比log4c、zlog这些知名的C日志库&#xff0c;EasyLogger的功能更加简单&#xff0c;提供给用户的接口更少&#xff0c;但…

肺腺癌预后新指标:全切片图像中三级淋巴结构密度的自动化量化|文献精析·24-10-09

小罗碎碎念 本期这篇文章&#xff0c;我去年分享过一次。当时发表在知乎上&#xff0c;没有标记参考文献&#xff0c;配图的清晰度也不够&#xff0c;并且分析的还不透彻&#xff0c;所以趁着国庆假期重新分析一下。 这篇文章的标题为《Computerized tertiary lymphoid structu…

基于springboot vue 校园失物招领平台的设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm springcloud等开发框架&#xff09; vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…

【AIGC】OpenAI API在快速开发中的实践与应用:优化ChatGPT提示词Prompt加速工程

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;使用最新型号确保最佳实践利用最新模型进行高效任务处理为什么要选择最新模型&#xff1f;结论 &#x1f4af;指令与上下文的分隔最佳实践分隔指令和上下文的重要性使用符…

叉车毫米波雷达防撞技术,保护叉车作业安全

在叉车作业频繁的仓库与物流中心&#xff0c;安全隐患往往隐藏于细微之处&#xff0c;稍有不便可能引发重大事故。我们的叉车毫米波防撞系统方案&#xff0c;正是针对这一痛点而精心设计的创新之作。该系统通过集成的毫米波雷达技术&#xff0c;实现了对叉车周边环境的实时、精…

【动态规划】dp之斐波那契数列模型

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 目录 循环渐进Forward-CSDN博客 第N个泰波那契序数 思路&#xff1a; 代码实现&#xff1a; 三步问题 思路&#xff1a; 代码实现&#xff1a; 使用最小花费爬楼梯 思路…

C语言 | 第十三章 | 二维数组 冒泡排序 字符串指针 断点调试

P 120 数组应用案例 2023/1/29 一、应用案例 案例一&#xff1a;创建一个char类型的26个元素的数组&#xff0c;分别 放置’A’-Z‘。使用for循环访问所有元素并打印出来。提示&#xff1a;字符数据运算 ‘A’1 -> ‘B’ #include<stdio.h>void main(){/*创建一个c…

【优选算法之BFS】No.15--- 经典BFS解决FloodFill算法和解决最短路问题

文章目录 前言一、BFS解决FloodFill算法示例&#xff1a;1.1 图像渲染1.2 岛屿数量1.3 岛屿的最⼤⾯积1.4 被围绕的区域 二、BFS解决最短路问题2.1 迷宫中离⼊⼝最近的出⼝2.2 最⼩基因变化2.3 单词接⻰2.4 为⾼尔夫⽐赛砍树 前言 &#x1f467;个人主页&#xff1a;小沈YO. &a…

Linux高级编程_31_消息队列

文章目录 消息队列作用&#xff1a;特点&#xff1a;消息队列限制值&#xff1a;注意&#xff1a;命令&#xff1a;ftok函数作用&#xff1a;语法&#xff1a; msgget函数作用&#xff1a;语法&#xff1a; msgsnd函数作用&#xff1a;语法&#xff1a; msgrcv函数作用&#xf…

QT实现QInputDialog中文按钮

这是我记录Qt学习过程心得文章的第三篇&#xff0c;主要是为了方便QInputDialog输入框的使用&#xff0c;通过自定义的方式&#xff0c;按钮中文化&#xff0c;统一封装成一个函数&#xff0c;还是写在了Skysonya类里面。 实现代码&#xff1a; //中文按钮文本输入对话框 QSt…

【gRPC】1—gRPC是什么

gRPC是什么 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; &#x1f4d6;RPC专栏&#xff1a;https://b…

鸿蒙--播放器状态控制

各个页面共享同一个播放状态&#xff0c;而且可以互相控制&#xff0c;如果传递来传递去会非常的麻烦&#xff0c;但是他们都是Tabs组件内的&#xff0c;我们在index页面提供一个状态&#xff0c;在各个组件接收即可 创建两个子组件&#xff0c;一个是播放控制的子组件&#xf…

1. Oracle 安装报错——环境变量过长

文章目录 1. 报错详细信息2. 解决方案2.1 方案一&#xff1a;修改配置文件cvu_prereq.xml2.2 方案二&#xff1a;修改环境变量配置 1. 报错详细信息 安装 Oracle 过程中&#xff0c;在执行 “先决条件检查” 时报错&#xff1a; 报错内容&#xff1a; This test checks wheth…

【自然语言处理】(3) --RNN循环神经网络

文章目录 RNN循环神经网络一、传统神经网络的问题二、RNN的基本结构三、计算过程4. RNN的局限 总结 RNN循环神经网络 循环神经网络&#xff08;RNN&#xff0c;Recurrent Neural Network&#xff09;是一种用于处理序列数据的神经网络模型。其关键特性在于网络节点&#xff08…

现代数字信号处理I-P2概率论学习笔记

目录 学习视频链接&#xff1a; 1. 三要素及关系 2. 期望和方差的定义及基本性质 2.1 期望&#xff08;均值&#xff09;定义&#xff1a; 在实际工作中很难获得随机变量的分布或者概率密度&#xff0c;用矩描述随机变量 2.2 期望基本性质&#xff1a; 2.3 方差定义 2.…

Android Studio Koala Feature Drop 稳定版现已推出

作者 / Android Studio 产品经理 Sandhya Mohan Android Studio Koala Feature Drop (2024.1.2) 现已推出&#xff01;&#x1f428; &#x1f517; Android Studio https://developer.android.google.cn/studio 今年早些时候&#xff0c;我们宣布每个 Android Studio 动物版本…

10月9日笔记(域内用户登录凭据窃取)

缺&#xff1a;BloodHound自动化分析域环境未实现&#xff08;环境问题&#xff09; 获取常见应用软件凭据 为了扩大可访问的范围&#xff0c;测试人员通常会搜索各种常见的密码存储位置&#xff0c;以获取用户凭据。一些特定的应用程序可以存储密码&#xff0c;以方便用户管…

python的特殊方法——魔术方法

前言 __init__(self[]) ​编辑 __call__(self [, ...]) __getitem__(self, key) __len__(self) __repr__(self) / __str__(self) __add__(self, other) __radd__(self, other) 参考文献 前言 官方定义好的&#xff0c;以两个下划线开头且以两个下划线结尾来命名的方法…