k近邻法(二)
上一篇文章讲了k近邻法,以及使用kd树构造数据结构,使得提高最近邻点搜索效率,但是这在数据点N 远大于 2^n 时可以有效的降低算法复杂度,n为数据点的维度,否则,由于需要向上回溯比较距离,使得实际效率总是很低(接近线性扫描)。比如SIFT特征矢量128维,SURF特征矢量64维,维度都比较大,N 远大于 2^n 可能无法满足。此外,由于每个最近邻点都需要回溯到根节点才算结束,那么,在获取k个近邻点时,必然存在大量不必要的回溯点,这些都需要另寻其他查询方法。
一个简单的改进思路就是将“查询路径”上的结点进行排序,如按各自分割超平面(也称bin)与查询点的距离排序,也就是说,回溯检查总是从优先级最高(Best Bin)的树结点开始
所以这篇文章讨论这种改进的方法 Best Bin First(BBF)。
主要思想是,使用一个优先列表,按节点与目标点的距离排序,从优先列表中取出第一项(当前距离最近的)节点,按照某个规则决定是访问其左子节点或者右子节点,同时将另一个子节点(如果存在)存储到优先列表中,循环以上操作,直到优先列表为空。
步骤:
- 将根结点store in 优先列表Priority中。声明一个最近节点对象nearest,先令其指向根节点,一个当前节点对象current
- 取出第一项,根据访问规则,访问其左子节点或者右子节点,并令current指向它,然后将另一个子节点store in Priority中,递归向下,直到遇到叶节点,此时,比较current和nearest哪个距离目标节点更近,更新nearest,
- 如果Priority中还有项,则继续步骤2,否则返回nearest,此为最近邻点
由于比较简单,这里不再详述,直接给出代码。
private List<Tuple<TreeNode, double>> _priorities = new List<Tuple<TreeNode, double>>();
/// <summary>
/// 按priority升序排序插入
/// </summary>
/// <param name="node"></param>
/// <param name="priority"></param>
private void InsertByPriority(TreeNode node, double priority)
{
if(_priorities.Count == )
{
_priorities.Add(new Tuple<TreeNode, double>(node, priority));
}
else
{
for(int i = ; i < _priorities.Count; i++)
{
if(_priorities[i].Item2 >= priority)
{
_priorities.Insert(i, new Tuple<TreeNode, double>(node, priority));
break;
}
}
}
}
private double GetPriority(TreeNode node, Point p, int axis) => Math.Abs(node.point.vector[axis] - p.vector[axis]); public Point BBFSearchNearestNode(Point p)
{
var rootPriority = GetPriority(root, p, root.axis);
InsertByPriority(root, rootPriority);
var nearest = root; TreeNode topNode = null; // 优先级最高的节点
TreeNode curNode = null;
while(_priorities.Count > )
{
topNode = _priorities[].Item1;
_priorities.RemoveAt(); while(topNode != null)
{
if(topNode.left != null || topNode.right != null)
{
var axis = topNode.axis;
if(p.vector[axis] <= topNode.point.vector[axis])
{
// wanna to go down left child node
if(topNode.right != null) // 将右子节点添加到优先列表
{
InsertByPriority(topNode.right, GetPriority(topNode.right, p, axis));
}
topNode = topNode.left;
}
else
{
// wanna to go down right child node
if(topNode.left != null)
{
InsertByPriority(topNode.left, GetPriority(topNode.left, p, axis));
}
topNode = topNode.right;
}
}
else
{
curNode = topNode;
topNode = null;
} if(curNode != null && p.Distance(curNode.point) < p.Distance(nearest.point)) // find a nearer node
{
nearest = curNode;
}
}
}
return nearest.point;
}
上面的代码仅仅是返回了最近的那一个点,如果要返回k个近邻点,则只需对上面代码稍作修改,将 上面每次的current保存到一个按距离排序的列表中,这样前k个点就是所求的k近邻点,代码如下:
/// <summary>
/// 最大检测次数
/// </summary>
public int max_nn_chks = 0x1000;
/// <summary>
/// 搜索k近邻点
/// </summary>
/// <param name="p"></param>
/// <param name="k"></param>
/// <returns></returns>
public List<TreeNode> BBFSearchKNearest(Point p, int k)
{
var list = new List<BBFData>(); //
var pq = new MinPQ();
pq.insert(new PQNode(root, ));
int t = ;
while(pq.nodes.Count > && t < max_nn_chks)
{
var expl = pq.pop_min_default().data;
expl = Explore2Leaf(expl, p, pq); var bbf = new BBFData(expl, expl.point.Distance(p));
insert(list, k, bbf); t++;
}
return list.Select(l => l.data).ToList();
}
/// <summary>
/// 向下访问叶节点,并将slide添加到优先列表中
/// </summary>
/// <param name="node"></param>
/// <param name="p"></param>
/// <param name="pq"></param>
/// <returns></returns>
private TreeNode Explore2Leaf(TreeNode node, Point p, MinPQ pq)
{
TreeNode unexpl;
var expl = node;
TreeNode prev;
while(expl != null && (expl.left != null || expl.right != null))
{
prev = expl;
var axis = expl.axis;
var val = expl.point.vector[axis]; if(p.vector[axis] <= val)
{
unexpl = expl.right;
expl = expl.left;
}
else
{
unexpl = expl.left;
expl = expl.right;
}
if(unexpl != null)
{
pq.insert(new PQNode(unexpl, Math.Abs(val - p.vector[axis])));
}
if(expl == null)
{
return prev;
}
}
return expl;
}
/// <summary>
/// 将节点按距离插入列表中
/// </summary>
/// <param name="list"></param>
/// <param name="k"></param>
/// <param name="bbf"></param>
private void insert(List<BBFData> list, int k, BBFData bbf)
{
if(list.Count == )
{
list.Add(bbf);
return;
} int ret = ;
int oldCount = list.Count;
var last = list[list.Count - ];
var df = bbf.d;
var dn = last.d;
if(df >= dn) // bbf will be appended to list
{
if(oldCount == k) // already has k nearest neighbors, nothing should be done
{
return;
}
list.Add(bbf); // append directively
return;
} // bbf will be inserted into list internally if(oldCount < k)
{
// suppose bbf be inserted at idx1, all elements after idx1 should be moved 1 backward respectively
// first we move the last element
list.Add(last);
}
// from backer to former, move related elements
int i = oldCount - ;
while(i > -)
{
if (list[i].d <= df)
break; list[i + ] = list[i]; // move backward
i--;
}
i++;
list[i] = bbf;
}
其中用到的辅助类如下:
public class BBFData
{
public TreeNode data;
/// <summary>
/// 节点与目标点的距离
/// </summary>
public double d; public BBFData(TreeNode data, double d)
{
this.data = data;
this.d = d;
}
} public class PQNode
{
public TreeNode data;
/// <summary>
/// 目标点与当前节点的超平面的距离
/// </summary>
public double d; public PQNode(TreeNode data, double d)
{
this.data = data;
this.d = d;
}
} public class MinPQ
{
public List<PQNode> nodes;
// 将节点插入优先列表中
public void insert(PQNode node)
{
nodes.Add(node); int i = nodes.Count - ;
int p = parent(i);
PQNode tmp;
while(i > && p >= && nodes[i].d < nodes[p].d)
{
tmp = nodes[p];
nodes[p] = nodes[i];
nodes[i] = tmp;
i = p;
p = parent(i);
}
} public PQNode get_min_default() => nodes.Count > ? nodes[] : null;
public PQNode pop_min_default()
{
if (nodes.Count == ) return null; var ret = nodes[];
nodes[] = nodes[nodes.Count - ];
nodes.RemoveAt(nodes.Count - );
restore_minpq_order(, nodes.Count); return ret;
} private void restore_minpq_order(int i, int n)
{
int l = left(i);
int r = right(i);
int min = i; if (l < n && nodes[l].d < nodes[i].d)
min = l;
if (r < n && nodes[r].d < nodes[min].d)
min = r;
if(min != i)
{
var tmp = nodes[min];
nodes[min] = nodes[i];
nodes[i] = tmp;
}
} public static int parent(int i) => (i - ) / ;
public static int right(int i) => * (i + );
public static int left(int i) => * i + ;
}
注意,上面代码中,优先列表是使用最小堆实现。
后记:
以上代码搜索k近邻,仅仅是一定程度上得到最大可能的近似k近邻,因为有max_nn_chks的检测次数限制。
假设没有这个限制,则实际上应该对全部训练数据集中的数据点做检测的,而不用k-d树结构存储数据集时也是要检测全部数据点的,不过,两者区别还是有的,也许使用了k-d树后,由于是从优先列表中选择数据点进行检测,导致insert结果列表的操作平均时间复杂度低(当然了,这些我此时并没有去仔细想),而且使用了k-d树后,在数据集数量很大时,需要max_nn_chks限制,此时近似k近邻还是比不使用k-d树得到的近似k近邻更加准确吧(概率意义上)^^!
k近邻法(二)的更多相关文章
- 统计学习方法与Python实现(二)——k近邻法
统计学习方法与Python实现(二)——k近邻法 iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.定义 k近邻法假设给定一个训练数据集,其中的实例类别已定 ...
- K近邻法(KNN)原理小结
K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用.比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出 ...
- 机器学习PR:k近邻法分类
k近邻法是一种基本分类与回归方法.本章只讨论k近邻分类,回归方法将在随后专题中进行. 它可以进行多类分类,分类时根据在样本集合中其k个最近邻点的类别,通过多数表决等方式进行预测,因此不具有显式的学习过 ...
- scikit-learn K近邻法类库使用小结
在K近邻法(KNN)原理小结这篇文章,我们讨论了KNN的原理和优缺点,这里我们就从实践出发,对scikit-learn 中KNN相关的类库使用做一个小结.主要关注于类库调参时的一个经验总结. 1. s ...
- 学习笔记——k近邻法
对新的输入实例,在训练数据集中找到与该实例最邻近的\(k\)个实例,这\(k\)个实例的多数属于某个类,就把该输入实例分给这个类. \(k\) 近邻法(\(k\)-nearest neighbor, ...
- k近邻法
k近邻法(k nearest neighbor algorithm,k-NN)是机器学习中最基本的分类算法,在训练数据集中找到k个最近邻的实例,类别由这k个近邻中占最多的实例的类别来决定,当k=1时, ...
- 机器学习中 K近邻法(knn)与k-means的区别
简介 K近邻法(knn)是一种基本的分类与回归方法.k-means是一种简单而有效的聚类方法.虽然两者用途不同.解决的问题不同,但是在算法上有很多相似性,于是将二者放在一起,这样能够更好地对比二者的异 ...
- 《统计学习方法》笔记三 k近邻法
本系列笔记内容参考来源为李航<统计学习方法> k近邻是一种基本分类与回归方法,书中只讨论分类情况.输入为实例的特征向量,输出为实例的类别.k值的选择.距离度量及分类决策规则是k近邻法的三个 ...
- k近邻法(kNN)
<统计学习方法>(第二版)第3章 3 分类问题中的k近邻法 k近邻法不具有显式的学习过程. 3.1 算法(k近邻法) 根据给定的距离度量,在训练集\(T\)中找出与\(x\)最邻近的\(k ...
随机推荐
- Python中有许多HTTP客户端,但使用最广泛且最容易的是requests
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:北京尚脑软件测试 PS:如有需要Python学习资料的小伙伴可以加点击 ...
- L13过拟合欠拟合及其解决方案
过拟合.欠拟合及其解决方案 过拟合.欠拟合的概念 权重衰减 丢弃法 模型选择.过拟合和欠拟合 训练误差和泛化误差 在解释上述现象之前,我们需要区分训练误差(training error)和泛化误差(g ...
- 漫谈LiteOS-Huawei_IoT_Link_SDK_OTA 开发指导
1概述 在应用升级过程中,无线下载更新(OTA)是一种常用,且方便的升级方式.Liteos采用的OTA升级方案基于LwM2M协议,实现了固件升级(FOTA)和软件升级(SOTA)两种升级方案.用户可根 ...
- 谁说 Vim 不好用?送你一个五彩斑斓的编辑器!
相信大家在使用各种各样强大的 IDE 写代码时都会注意到,代码中各种类型的关键字会用独特的颜色标记出来,然后形成一套语法高亮规则.这样不仅美观,而且方便代码的阅读. 而在上古神器 Vim 中,我们通常 ...
- PHP函数:json_last_error
json_last_error() - 返回 JSON 编码解码时最后发生的错误.. 说明: json_last_error ( void ) : int 参数: 无 返回值: 返回一个整型(int ...
- [转载]MySQL中int(11)最大长度是多少?
原文地址:https://blog.csdn.net/allenjay11/article/details/76549503 今天在添加数据的时候,发现当数据类型为 int(11) 时,我当时让用户添 ...
- 3. css百度制作字体图片
http://fontstore.baidu.com/static/editor/index.html?qq-pf-to=pcqq.group
- 解析网站爬取腾讯vip视频
今天用油猴脚本vip一件解析看神奇队长.想到了问题,这个页面应该是找到了视频的api的接口,通过接口调用获取到了视频的地址. 那自己找腾讯视频地址多费劲啊,现在越来越多的参数,眼花缭乱的. 那我就找到 ...
- Java的数组索引问题
/* 数组操作的两个常见小问题: ArrayIndexOutOfBoundsException:数组索引越界异常 原因:你访问了不存在的索引. NullPointerException:空指针异常 原 ...
- linux--配置开发环境 --Nginx篇
安装: 安装好了话,我们的nginx的目录在: /etc/nginx 启动: sudo service nginx start 然后访问我们的页面就可以看到了我们的界面 然后我们配置我们的域名: 我 ...