K 近邻算法(KNN)与KD 树实现

























KD树节点

/// <summary>/// KD树节点/// /2016/4/1安晟添加/// </summary>[Serializable]public class KDTreeNode{/// <summary>///   获取或设置节点的空间坐标/// </summary>public double[] Position { get; set; }/// <summary>///   获取或设置分裂维度的索引///   Gets or sets the dimension index of the split. /// </summary>public int Axis { get; set; }/// <summary>///   获取或设置该节点在分裂维度上的取值/// </summary>public double Value { get; set; }/// <summary>/// 获取或设置该节点对应样本在原数据集中的索引/// 这个属性在查询k近邻元素时十分重要/// </summary>public int OriginalIndex { get; set; }/// <summary>///   获取或设置该节点的左孩子节点/// </summary>public KDTreeNode Left { get; set; }/// <summary>///   获取或设置该节点的右孩子节点/// </summary>public KDTreeNode Right { get; set; }/// <summary>/// 获取该节点是否为叶子节点(木有孩子节点)///   Gets whether this node is a leaf (has no children)./// </summary>public bool IsLeaf{get { return Left == null && Right == null; }}}


构造KD树

/// <summary>/// 构造KDTree/// </summary>/// <param name="DateSet">输入样本集(构造树的过程中样本的顺序会被修改,不过不必担心,/// 方法内部已经单独克隆一块内存区域来处理)</param>public void BuildKDTree(double[][] DataSet){KDTreeNode rootNode = new KDTreeNode();//克隆数据集,DataSet将指向新开辟的数据集//这样expand_kdTreeNode对样本顺序进行修改就不会影响到外部调用者DataSet = (double[][])DataSet.Clone();//Indexs数组用于记录每个样本对应的索引//在expand_kdTreeNode方法内部,Indexs应随着DataSet中样本数据顺序的变化相应的变动。int[] Indexs = new int[DataSet.Length];for (int i = 0; i < DataSet.Length; i++)Indexs[i] = i;//从根节点开始递归构造KD树expand_kdTreeNode(rootNode, DataSet, Indexs, 0, DataSet.Length);this.Root = rootNode;this.Dimensions = DataSet.Length;}
/// <summary>/// 递归增加kd树节点/// 按照方差选择分裂维度/// 内部样本会被打乱顺序,如有需要使用前需单独复制一块内存区域/// </summary>/// <param name="currentNode"></param>/// <param name="DataSet"></param>/// <param name="startIndex"></param>/// <param name="length"></param>private void expand_kdTreeNode(KDTreeNode currentNode,double[][] DataSet,int[] Indexs,int startIndex,int length){if(length==1){//当前只有一个样本点,无需再进行分裂//当前节点为叶子节点currentNode.Position = DataSet[startIndex];currentNode.OriginalIndex = Indexs[startIndex];currentNode.Left = null;currentNode.Right = null;//因为是叶子节点并不需要分裂,所以 分裂维度Axis属性 和 该维度对应值Value属性 随便赋一个值就好了currentNode.Axis = 0;currentNode.Value = currentNode.Position[0];}else{//else对应通常情况,即需要对当前的数据集DataSet[startIndex:startIndex+length-1]//的各个特征维度进行方差分析,找出分裂维度和分裂值,生成当前节点#region 对数据集的关心区域,按照找到的分裂维度进行排序//计算各个维度的方差,找到分裂维度int split = CalculateFeaturesVariance(DataSet, startIndex, length);//根据split维特征的取值,对数据集进行排序double[] splitValueArray = new double[length]; //记录split维的特征值数组int[] IndexArray = new int[length];            //记录对应相对索引(从0开始)for(int i=startIndex;i<startIndex+ length;i++){splitValueArray[i - startIndex] = DataSet[i][split];IndexArray[i - startIndex] = i-startIndex;  }//根据splitValueArray记录的split维的特征值数组,同时对splitValueArray和IndexArray进行排序//IndexArray排序后记录的是 startIndex:startIndex+length-1 排序时的交换顺序Array.Sort(splitValueArray, IndexArray);//根据结果对DataSet的关心区域startIndex:startIndex+length-1重新排序//首先需要克隆关心区域到内存中double[][] tempData = new double[length][];int[] tempIndex = new int[length];for(int i=startIndex;i<startIndex+length;i++){tempData[i - startIndex] = DataSet[i];tempIndex[i - startIndex] = Indexs[i];}int RelativeDataIndex = -1; //对于每一个位置,它应当插入的元素所对应的排序前的索引for (int i = startIndex; i < startIndex + length; i++){RelativeDataIndex = IndexArray[i - startIndex];DataSet[i] = tempData[RelativeDataIndex];Indexs[i] = tempIndex[RelativeDataIndex];}//将临时数组清空,本函数由于是个递归函数,//为了避免内存中可能会存众多的tempData,所以用完就清空掉,应该是合理的tempData = null;tempIndex = null;#endregion//到这里数据集的关心区域已经按照split维进行排序了#region 构造当前节点int middleIndex = startIndex + length / 2;int leftStart = startIndex;int leftlength = middleIndex - leftStart ;int rightStart = middleIndex + 1;int rightlength = startIndex + length - 1 - middleIndex;currentNode.Axis = split;currentNode.Position = DataSet[middleIndex];currentNode.Value = currentNode.Position[split];currentNode.OriginalIndex = Indexs[middleIndex];currentNode.Left = new KDTreeNode();currentNode.Right = new KDTreeNode();#endregion#region 递归//递归左孩子节点if (leftlength > 0)  //因为选取中点时采取的逻辑,这个if语句是不起作用的(leftlength永远都不会等于0),仅仅是为了便于理解代码expand_kdTreeNode(currentNode.Left, DataSet, Indexs, leftStart, leftlength);elsecurrentNode.Left = null;//递归右孩子节点if (rightlength > 0)expand_kdTreeNode(currentNode.Right, DataSet, Indexs, rightStart, rightlength);elsecurrentNode.Right = null;#endregion}}


查找K近邻节点

<div style="text-align: left;"><span style="font-family: Arial, Helvetica, sans-serif;">/// <summary></span></div>        /// 获取给定样本的K个最近邻邻居/// </summary>/// <param name="DataPointX">数据点</param>/// <param name="KNNIndexs">K个最近邻邻居(在原训练样本中)的索引</param>/// <returns>返回K个最近邻邻居对应的样本</returns>public double[][] GetNearestNeighbors(double[] DataPointX, out int[] KNNIndexs){//用于维护K个最近邻节点KDTreeNodeCollection collection = new KDTreeNodeCollection(this.K);//从根节点开始递归寻找K近邻nearest(this.Root, DataPointX, collection);double[][] KNN_points = new double[collection.listNode.Count][]; //记录K近邻邻居对应数据KNNIndexs = new int[collection.listNode.Count]; //K个最近邻邻居(在原训练样本中)的索引for (int k=0;k<collection.listNode.Count;k++){KNN_points[k] = collection.listNode[k].Position;KNNIndexs[k] = collection.listNode[k].OriginalIndex;}return KNN_points;}
/// <summary>///   搜索K近邻邻居/// </summary>private void nearest(KDTreeNode current, double[] position, KDTreeNodeCollection collection){#region 判断当前节点是否是目前的K近邻邻居// 计算当前节点到指定数据点的距离double d = Distance(position, current.Position);//尝试将新得到的节点加入K近邻节点集合//如果比K个邻居中最远的距离还要大就不会被添加进去collection.Add(current, d);#endregion#region 利用深度优先的逻辑进行递归(完成初步搜索和回溯)//通过分裂维度的取值,判断指定数据点在 当前节点所决定的超平面的哪一边//若在左边,则递归左子节点,然后回溯右半平面//若在右边,则递归右子节点,然后回溯左半平面double value = position[current.Axis]; //指定数据点在分裂维度的取值double median = current.Value;         //当前节点在分裂维度的决定分割超平面的取值double u = value - median;             if (u <= 0){//指定数据点在 当前节点决定超平面的左侧//递归直至叶子节点if (current.Left != null)nearest(current.Left, position, collection);//运行到这里说明已经深度优先搜索到了叶子节点//如果从指定点出发,以当前最大距离为半径的超球面与超平面有交割//则回溯右半平面//Math.Abs(u) <= collection.MaxDistance 意味着以当前最大距离为半径能够与分割超平面有交集,需要回溯if (current.Right != null && Math.Abs(u) <= collection.MaxDistance)nearest(current.Right, position, collection);}else{//指定数据点在 当前节点决定超平面的右侧//原理与上面相同if (current.Right != null)nearest(current.Right, position, collection);if (current.Left != null && Math.Abs(u) <= collection.MaxDistance)nearest(current.Left, position, collection);}#endregion}




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

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

相关文章

PC软件开发技术之二:用C#开发基于自动化接口的OPC客户端

OPC全称是Object Linking and Embedding&#xff08;OLE&#xff09; for Process Control&#xff0c;它的出现为基于Windows的应用程序和现场过程控制应用建立了桥梁。OPC作为一整套接口、属性和方法的协议标准集&#xff0c;与具体的开发语言没有关系。 1、OPC客户端接口方…

标记符控制的分水岭算法原理及matlab实现

-------------------------------------------------------------------------------------------------------------------- 附录A 教程【3】给出的matlab源码&#xff0c;附详细注释 function [ ] MarkerControlled_Watershed_tutorial( ) %标记符控制的分水岭算法教程 …

PC软件开发技术之三:C#操作SQLite数据库

我们在开发应用是经常会需要用到一些数据的存储&#xff0c;存储的方式有多种&#xff0c;使用数据库是一种比较受大家欢迎的方式。但是对于一些小型的应用&#xff0c;如一些移动APP&#xff0c;通常的数据库过于庞大&#xff0c;而轻便的SQLite则能解决这一问题。不但操作方便…

自动搜索数据增强方法分享——fast-autoaugment

前言 简短的介绍下分享fast-autoaugment的原因 毫无疑问数据增强对于训练CNN非常有效&#xff0c;大家也在不断发明新的数据增强方法 拿到一份数据集&#xff0c;我们凭借之前的经验组合不同的增强方法形成一个数据增强策略&#xff0c;通常可以得到一个还不错的baseline。但…

SSD之硬的不能再硬的硬核解析

本文是对经典论文 SSD: Single Shot MultiBox Detector 的解析&#xff0c;耗时3周完成&#xff0c;万字长文&#xff0c;可能是你能看到的最硬核的SSD教程了&#xff0c;如果想一遍搞懂SSD&#xff0c;那就耐心读下去吧~ 一句话总结SSD效果就是&#xff1a;比YOLO快一点且准很…

C语言学习及应用笔记之五:C语言typedef关键字及其使用

在C语言中有一个typedef关键字&#xff0c;其用来定义用户自定义类型。当然&#xff0c;并不是真的创造了一种数据类型&#xff0c;而是给已有的或者符合型的以及复杂的数据类型取一个我们自己更容易理解的别名。总之&#xff0c;可以使用typedef关键字定义一个我们自己的类型名…

Modbus协议栈开发笔记之五:Modbus RTU Slave开发

Modbus在串行链路上分为Slave和Master&#xff0c;这一节我们就来开发Slave。对于Modbus RTU从站来说&#xff0c;需要实现的功能其实与Modbus TCP的服务器端是一样的。其操作过程也是一样的。首先接收到主站的访问命令&#xff0c;对该命令报文进行解析&#xff0c;这里我们也…

Modbus协议栈开发笔记之六:Modbus RTU Master开发

这一节我们来封装最后一种应用&#xff08;Modbus RTU Master应用&#xff09;&#xff0c;RTU主站的开发与TCP客户端的开发是一致的。同样的我们也不是做具体的应用&#xff0c;而是实现RTU主站的基本功能。我们将RTU主站的功能封装为函数&#xff0c;以便在开发具体应用时调用…

PID控制器开发笔记之十三:单神经元PID控制器的实现

神经网络是模拟人脑思维方式的数学模型。神经网络是智能控制的一个重要分支&#xff0c;人们针对控制过程提供了各种实现方式&#xff0c;在本节我们主要讨论一下采用单神经元实现PID控制器的方式。 1、单神经元的基本原理 单神经元作为构成神经网络的基本单位&#xff0c;具…

基于STM32L476的锂电池SOC检测

便携式设备由于使用需求而配备了锂电池&#xff0c;但使用过程中需要掌握电源的状态才能保证设备正常运行。而且在电池充放电的过程中&#xff0c;监控电池的充放电状态也是保证设备安全的需要。 1、硬件设计 电池SOC检测是一个难题&#xff0c;有很多的模型和检测电路。但对…

C语言学习及应用笔记之六:C语言extern关键字及其使用

在C语言中&#xff0c;修饰符extern用在变量或者函数的声明前&#xff0c;用来以标识变量或者函数的定义在别的文件中&#xff0c;提示编译器遇到此变量或者函数时&#xff0c;在其它文件中寻找其定义。extern关键字的用法有几种&#xff0c;我们下面对其进行说明。 1、extern…

C语言学习及应用笔记之七:C语言中的回调函数及使用方式

我们在使用C语言实现相对复杂的软件开发时&#xff0c;经常会碰到使用回调函数的问题。但是回调函数的理解和使用却不是一件简单的事&#xff0c;在本篇我们根据我们个人的理解和应用经验对回调函数做简要的分析。 1、什么是回调函数 既然谈到了回调函数&#xff0c;首先我们…

STM32与SHT1X温湿度传感器通讯

在这次项目开发中应用到了SHT1X温湿度传感器&#xff0c;该系列有SHT10、SHT11和SHT15&#xff0c;属于Sersirion温湿度传感器家族中的贴片封装系列。包括一个电容性聚合体测湿敏感元件、一个用能隙材料制成的测温元件&#xff0c;传感器内部有一个精度高达14为位的A/D转换器。…

STM32与MS5837压力传感器的I2C通讯

MS5837压力传感器是一种可用于电路板上&#xff0c;适用于检测10-1200mbar压力范围的传感器&#xff0c;灵敏度非常高&#xff0c;理论上能够检测到0.01mbar的压力变化&#xff0c;实际使用过程中测试并无明显的变化。 MS5837采用I2C总线通讯&#xff0c;与STM32的MCU可以实现…

STM32F0使用LL库实现MS5536C通讯

在本次项目中&#xff0c;限于空间要求我们选用了STM32F030F4作为控制芯片。这款MCU不但封装紧凑&#xff0c;而且自带的Flash空间也非常有限&#xff0c;所以我们选择了LL库实现。在本文中我们说明一下&#xff0c;使用LL库实现MS5536C的SPI通讯。 1、MS5536C简述 MS5536C是…

STM32F0使用LL库实现DMA方式AD采集

在本次项目中&#xff0c;限于空间要求我们选用了STM32F030F4作为控制芯片。这款MCU不但封装紧凑&#xff0c;而且自带的Flash空间也非常有限&#xff0c;所以我们选择了LL库实现。在本文中我们将介绍基于LL库的ADC的DMA采集方式。 1、概述 这次我们使用DMA方式实现对AD的采集…

STM32与宇电设备实现AI-BUS通讯

宇电的设备使用基于RS-485的自定义协议&#xff0c;协议本身比较简单&#xff0c;只有2条指令&#xff1a; 读&#xff1a;地址代号52H&#xff08;82&#xff09; 要读的参数代号00校验码 写&#xff1a;地址代号43H&#xff08;67&#xff09;要写的参数代号写入数低字节写…

FreeRTOS如何结束和重新启动调度程序

大多数主机或桌面系统&#xff08;比如Linux&#xff0c;Mac或Windows&#xff09;都有一个正常的用例&#xff0c;你可以在早上启动操作系统&#xff0c;然后在晚上关闭它&#xff0c;然后你就离开机器。嵌入式系统是不同的&#xff1a;他们没有参加&#xff0c;他们应该“永远…

先进过程控制之一:浅说APC

先进过程控制&#xff08;APC&#xff09;技术作为在生产装置级的信息化应用&#xff0c;在优化装置的控制水平和提高生产过程的管理水平的同时&#xff0c;还为企业创造了可观的经济效益。 1、什么是APC 先进过程控制&#xff0c;简称APC&#xff0c;并不是什么新概念。它仅…

STM32与多台MS5803压力传感器I2C通讯

MS5803压力传感器支持SPI和I2C总线通讯&#xff0c;拥有24位AD转换。能够同时获得压力值和温度值&#xff0c;其中压力测量范围为10-1100mbar&#xff0c;温度的测量范围是-40-85摄氏度。各引脚功能及参数如下&#xff1a; 传感器内部结构图如下&#xff1a; 通讯协议的选择通过…