高维数据中的最近邻居?

184

我在几天前发了一个问题,询问如何找到给定向量的最近邻。我的向量现在有21个维度,然而由于我不是机器学习或数学领域的人,所以在进一步进行之前,我开始思考一些基本问题:

  • 欧氏距离是否是首选用于寻找最近邻的良好度量方式?如果不是,我该选择什么方法?
  • 此外,如何确定确定k个最近邻的正确阈值?是否有一些分析可以帮助我找到这个值?
  • 之前,有人建议我使用kd-树,但维基百科页面清楚地指出,对于高维度,kd-Tree几乎等同于暴力搜索。在这种情况下,最佳的方式是如何高效地查找百万点数据集中的最近邻?

有没有人能够澄清上述某些(或全部)问题?


1
请在metaoptimize.com上尝试提问。 - pajton
4
对于某些人和数据,"高维度"可以是20,对于另一些人和数据则可能是50、100或1000。如果可以,请给出具体数字,例如:"我使用xx处理了21维、1000000个数据点的数据。" - denis
@denis,Higgs数据集的“dim 21, 1000000 data points”是指什么? - nikk
@nikk,不,我只是编了一个假的。你能指出真实的在线数据吗?这对于神经网络程序和人们来说都会很有用。 - denis
1
这是下载希格斯数据集的链接。包含 1100 万个观测值和 28 个属性。最后一列是标签:1 表示信号,0 表示噪声。https://archive.ics.uci.edu/ml/datasets/HIGGS - nikk
显示剩余3条评论
15个回答

202

我目前研究音乐信息检索中的分类和最近邻搜索等问题。

您可能对近似最近邻(Approximate Nearest Neighbor,ANN)算法感兴趣。这种算法的思想是允许算法返回足够接近的相邻点(可能不是最近邻),从而减少复杂度。您提到了kd树,它是其中一种例子。但正如您所说,kd树在高维下效果很差。实际上,所有基于空间划分的现有索引技术都会在足够高的维度下退化为线性搜索[1][2][3]。

在近期提出的ANN算法中,最受欢迎的可能是局部敏感哈希(Locality-Sensitive Hashing,LSH),它将高维空间中的一组点映射到一组桶中,即哈希表[1][3]。但与传统哈希不同,局部敏感哈希将附近的点放入同一个桶中。

LSH有一些巨大的优势。首先,它很简单。只需计算数据库中所有点的哈希值,然后从中创建哈希表。要查询,只需计算查询点的哈希值,然后从哈希表中检索所有位于同一桶中的点。

其次,有严格的理论支持其性能。可以证明查询时间与数据库的大小成亚线性关系,即比线性搜索更快。速度快多少取决于我们可以容忍多少近似值。

最后,LSH可与任何Lp范数兼容,其中0 < p <= 2。因此,回答您的第一个问题,您可以使用Euclidean距离度量来使用LSH,或者使用曼哈顿(L1)距离度量来使用它。还有Hamming距离和余弦相似度的变种。

在2008年IEEE信号处理杂志中,Malcolm Slaney和Michael Casey写了一篇不错的概述[4]。

局部敏感哈希(LSH)似乎已经被应用到各个领域,你可能想试试。


[1] Datar, Indyk, Immorlica, Mirrokni,“基于p稳定分布的局部敏感哈希方案”,2004。

[2] Weber, Schek, Blott,“高维空间相似性搜索方法的定量分析与性能研究”,1998。

[3] Gionis, Indyk, Motwani,“通过哈希在高维度中进行相似性搜索”,1999。

[4] Slaney, Casey,“利用局部敏感哈希查找最近邻居”,2008。


1
@Steve:感谢您的回复。您对LSH实现有什么建议吗?我看到的唯一一个是MIT的那个。还有其他的包吗? - Legend
1
除了那一个,不,我不知道其他的。最终我为了我的特定目的用Python编写了自己的哈希表。基本上,每个哈希表都被实现为一个Python字典d,其中d[k]是具有键k的一个bin。d[k]包含所有哈希值为k的点的标签。然后,您只需要计算每个点的哈希值。请参见[4]中的公式(1)或[1]中的第3节。 - Steve Tjoa
1
另一个支持LSH的参考文献:《在高维空间中比较最近邻算法》,Hendra Gunadi,2011年。http://cs.anu.edu.au/student/projects/11S2/Reports/Hendra%20Gunadi.pdf - Oliver Coleman
1
@SteveTjoa:发现很难直观地理解关键字和嵌入式公式。由于您已经对LSH进行了单一的突出显示,我进行了补充。只是出于最好的意图。不过,如果您想要恢复原样,也请随意。毕竟这是您的答案。 :) - Regexident
有一份关于LSH的调查报告,对于在查看其他论文之前了解LSH以及多种度量标准时非常有用。http://research.microsoft.com/en-us/um/people/jingdw/Pubs%5CHashingSurvey-August-13-2014.pdf - 1vand1ng0
显示剩余15条评论

90

I. 距离度量

首先,在选择kNN中使用的距离度量时,数据集中特征(列)的数量并不是一个因素。已经有很多发表的研究专门针对这个问题,比较常见的基础是:

  • 你的数据的潜在统计分布;

  • 构成你的数据的特征之间的关系(它们是否相互独立-即协方差矩阵长什么样);和

  • 从中获取数据的坐标空间。

如果你没有关于你的数据被采样自哪种分布的先前知识,至少有一项(经过充分文献记录和详细描述的)研究认为欧氏距离是最佳选择。

在巨型网络推荐引擎以及当前学术研究中,欧几里得度量被用作计算距离的方法。欧几里得距离具有直观的意义,并且计算规模可扩展——即无论两点处于二维空间还是22维空间中,欧几里得距离的计算方式都相同。
它只在我几次失败时失效,每一次欧几里得距离失败都是因为底层(笛卡尔)坐标系选择不当。你通常会认识到这一点,例如路径长度(距离)不再是可加的——例如当度量空间是棋盘时,曼哈顿距离比欧几里得距离更好,同样地,当度量空间是地球时,如果你的距离是跨越多个大陆的航班,则适合于极坐标系的距离度量是一个好主意(例如,伦敦到维也纳需要2.5小时,维也纳到圣彼得堡又需要另外3小时,基本上是同一个方向,但是伦敦到圣彼得堡不是5.5小时,而是略超过3小时)。

但除了那些数据属于非笛卡尔坐标系的情况外,距离度量的选择通常并不重要。 (请参见这位CS学生的博客文章,通过检查距离度量对kNN分类器的影响来比较几种距离度量-卡方距离给出了最佳结果,但差异并不大; 一份更全面的研究在学术论文中,最近邻距离函数的比较研究-马哈拉诺比斯(基本上是欧几里得距离通过考虑维度协方差而归一化)在这项研究中表现最佳。)

重要提示:为了使距离度量计算有意义,您必须重新缩放数据——很少有可能构建一个kNN模型来生成准确的预测而不进行此操作。例如,如果您正在构建一个kNN模型来预测运动表现,并且您的期望变量是身高(厘米)、体重(千克)、体脂肪(%)和静息心率(每分钟心跳数),那么典型的数据点可能看起来像这样:[180.4,66.1,11.3,71]。显然,距离计算将被身高主导,而体脂肪百分比的贡献几乎可以忽略不计。换句话说,如果数据以不同的方式报告,例如将体重以克为单位而不是千克为单位,则原始值86.1将变成86100,这将对结果产生很大影响,这正是您不想要的。最常见的缩放技术可能是减去平均值并除以标准差(平均值和标准差分别针对数据集中的每一列或特征进行计算;X指数据行中的单个条目/单元格):

X_new = (X_old - mu) / sigma


II. 数据结构

如果您关心kd树结构的性能,那么Voronoi Tessellation是一个概念上简单的容器,但它将显著提高性能并比kd树更好地扩展。

dat

虽然这不是持久化kNN训练数据的最常见方式,但VT的应用以及随之而来的性能优势已有充分记录(例如,请参阅此Microsoft Research报告)。这个实际意义在于,只要您使用的是“主流”语言(例如在TIOBE指数中),那么您应该能够找到执行VT的库。我知道在Python和R中,每种语言都有多个选项(例如,在CRAN上可用的R软件包voronoi)。

使用VT进行kNN的工作方式如下:

从您的数据中随机选择w个点 - 这些是您的Voronoi中心。 Voronoi单元封装了所有最接近每个中心的相邻点。想象一下,如果为每个Voronoi中心分配不同的颜色,那么分配给给定中心的每个点都会被涂上该颜色。只要您具有足够的密度,这样做将很好地显示每个Voronoi中心的边界(作为分隔两种颜色的边界)。

如何选择Voronoi中心?我使用两条正交线指南。在随机选择w个点之后,计算训练数据的VT。接下来检查分配给每个Voronoi中心的数据点数量--这些值应该大致相同(假设你的数据空间具有均匀的点密度)。在二维空间中,这会导致大小相同的VT瓷砖。这是第一条规则,这是第二条规则。通过迭代来选择w--以w为可变参数运行kNN算法,并测量性能(查询VT返回预测所需的时间)。
想象一下你有一百万个数据点......如果这些点被持久化到普通的2D数据结构或kd-tree中,在每个新的预测响应变量的数据点上平均执行几百万次距离计算。当然,这些计算是在单个数据集上执行的。使用V/T,最近邻搜索分两步进行,依次针对两种不同的数据人群--首先是针对Voronoi中心,然后是找到最近的中心后,再搜索对应于该中心的单元格内的点,以找到实际的最近邻居(通过连续的距离计算)。组合起来,这两个查找比单个暴力查找快得多。这很容易理解:对于1M个数据点,假设你选择250个Voronoi中心来铺设数据空间。平均而言,每个Voronoi单元格将具有4000个数据点。因此,您不需要执行平均500,000次距离计算(暴力搜索),而只需执行更少的平均125+2,000次即可。
III. 计算结果(预测响应变量)
从一组kNN训练数据中计算预测值有两个步骤。第一个是确定n,或者说是用于计算的最近邻居的数量。第二个是如何加权它们对预测值的贡献。
关于第一个组件,你可以通过解决一个优化问题(与最小二乘优化非常相似)来确定最佳的n值。这是理论;在实践中,大多数人只使用n=3。无论如何,运行kNN算法来计算n=1、n=2、n=3等测试实例的预测值并绘制误差作为n的函数是很简单的。如果你只想得到一个合理的起始值,那么就使用n = 3。
第二个组件是如何加权每个邻居的贡献(假设n>1)。
最简单的加权技术只是将每个邻居乘以一个加权系数,这个系数只是1 /(dist * K),即从该邻居到测试实例的距离的倒数经常乘以一些经验派生的常数K。我不喜欢这种技术,因为它经常过度加权最近的邻居(并且同时低估更远的邻居);这意味着给定的预测可以几乎完全取决于单个邻居,这反过来增加了算法对噪声的敏感性。
一个更好的加权函数是高斯函数,它可以大大避免这种限制,在Python中看起来像这样:
def weight_gauss(dist, sig=2.0) :
    return math.e**(-dist**2/(2*sig**2))

要使用kNN算法来计算预测值,您需要确定n个最近邻居,他们与响应变量(即“测试实例”)的数据点最接近,然后为n个邻居中的每一个调用weight_gauss函数,将每个邻居与测试点之间的距离作为参数传入。该函数将返回每个邻居的权重,然后将其用作加权平均计算中的系数。

2
很棒的回答!根据我的经验,它全面而准确。 - Ted Dunning
好答案,+1,我在这里添加了一个更新的答案 here,它还不错吧? - gsamaras
1
所以想象一下,如果你有一百万个数据点...... 如果这些点被保存在普通的二维数据结构中,或者在kd树中,那么对于每个新的数据点,你需要执行平均数百万次距离计算来预测其响应变量。不同意。可以证明,在二维情况下,KD树具有O(sqrt(n))的搜索复杂度。 - Antoine

18
你所面临的问题被称为维度灾难。有时运行PCA或ICA等算法很有用,以确保您真正需要21个维度,并可能找到一种线性变换,使您可以使用少于21个维度,但结果质量近似相同。 更新: 我在Rangayyan的《生物医学信号处理》一书中遇到了它们(希望我记得正确)。ICA不是一个简单的技术,但它是由芬兰的研究人员开发的,我认为Matlab代码可以公开下载。 PCA是一种更广泛使用的技术,我相信您应该能够找到它的R或其他软件实现。通过迭代求解线性方程组来执行PCA。我做过这件事,但是已经太久了,无法记住如何做。=)
思路是将信号分解为独立的特征向量(实际上是离散特征函数)和它们的特征值,您的情况下有21个。每个特征值显示每个特征函数对每个测量值提供的贡献量。如果一个特征值非常小,则可以在完全不使用其对应的特征函数的情况下非常接近地表示信号,这就是如何消除一个维度。

+1 谢谢您。这是一个非常有趣的建议,而且非常合理。最后一个请求,您是否熟悉任何实践教程(使用Python、R或其他语言),可以交互式地解释如何进行此操作(我的意思是逐步解释整个过程)。自昨天以来,我已经阅读了一些文档,但大多数似乎超出了我的理解范围。有什么建议吗? - Legend
4
挑刺:ICA 不是降维算法。它不知道如何对组件进行评分,因此不应该被用作此类算法。 - Gael Varoquaux

14

顶尖答案都是不错的,但都比较老,所以我想补充一个2016年的答案


在高维空间中,维度诅咒潜伏在角落里,使传统方法(如流行的k-d树)变得像暴力方法一样缓慢。因此,我们将兴趣转向“近似最近邻搜索(ANNS)”,它在某种精度的情况下加快了过程。您可以获得精确NN的良好近似值,并具有很好的概率。

以下是可能有价值的热门话题:

  1. LSH的现代方法,例如 Razenshteyn提出的方法。
  2. RKD forest: 随机k-d树(RKD)森林,如 FLANN所描述的, 或者是我参与的最新方法 kd-GeRaF
  3. LOPQ代表局部优化的乘积量化,如 here所述。它与Babenko+Lemptitsky的 approach非常相似。

你还可以查看我的相关答案:
  1. 两组高维点:在另一组中找到最近的邻居
  2. 不同数据结构上最近邻查询运行时间的比较
  3. PCL kd树实现非常慢

12

逐一回答您的问题:

  • 不,欧几里得距离在高维空间中是一种糟糕的度量方式。基本上,在高维数据中,数据点之间具有较大的差异。这会降低给定数据点与其最近和最远邻居之间距离的相对差异。
  • 有很多高维数据的论文/研究,但大部分需要很高的数学水平。
  • KD树在高维数据中表现不佳...尽可能避免使用它。

以下是一篇不错的论文,可以帮助您朝着正确的方向开始学习:“When in Nearest Neighbour meaningful” by Beyer et all. (英文原版链接)

我处理的是维度为20K及以上的文本数据。如果您需要一些与文本相关的建议,我也许能够帮到您。


1
+1 我正在打印这篇论文,准备现在阅读。同时,你有没有其他关于如何找到最近邻的建议?如果距离度量和邻居本身的定义都存在缺陷,那么人们通常如何解决高维问题,以便基于特征向量进行近似匹配?有什么建议吗? - Legend
1
在文本方面,我们经常使用余弦相似度。我自己从事文本分类工作,并发现对于高维度来说,带有线性核的支持向量机似乎是最有效的。 - BiGYaN
@BiGYaN 你是如何定义你的空间的呢?我的意思是基于词袋向量还是嵌入向量? - user3487667
@user3487667,空间取决于您如何构建问题。我在谈论一个简单的词袋模型。 - BiGYaN

5
余弦相似度是比较高维向量的常见方法。请注意,由于它是相似性而不是距离,您希望将其最大化而不是最小化。您还可以使用特定于领域的方法来比较数据,例如,如果您的数据是DNA序列,则可以使用考虑突变概率等的序列相似性。
要使用的最近邻居数因数据类型、噪声程度等而异。没有通用规则,您只需要尝试范围内的所有值,找到适合您特定数据和问题的最佳值。人们有一种直觉理解,即数据越多,所需的邻居就越少。在假设情况下,您拥有所有可能的数据,只需要查找单个最近邻居进行分类。
k最近邻方法被认为计算成本高。这是人们转向其他算法(如支持向量机)的主要原因之一。

这很有趣。您能详细说明一下我如何在我的情况下利用SVM吗?我认为k最近邻更像是无监督的,而SVM是有监督的。如果我错了,请纠正我。 - Legend
2
两种方法都是有监督的,因为你的训练数据已经标注了正确的类别。如果你只有特征向量,而不知道它们属于哪个类别,那么你就不能使用kNN或SVM。无监督学习方法通常被称为聚类算法。它们可以识别相似数据的组,但不告诉你这些组的含义。 - Colin
谢谢您的澄清。您是正确的。这确实是一种监督技术。我只是没有意识到我所称之为类别实际上也是类 :) - Legend

4

kd树在高维数据中的表现不佳。因为修剪步骤不能提供太多帮助,因为最接近的边 - 一维偏差 - 几乎总是小于已知最近邻居的全维偏差。

此外,据我所知,kd树只适用于Lp范数,并且存在距离集中效应,使得随着维数增加,基于距离的算法会退化。

如果需要更多信息,您可以阅读关于维度诅咒及其各种变体的内容(它有不止一个方面!)

我并不认为仅仅盲目地近似欧几里得最近邻居,例如使用LSH或随机投影,有很大用处。可能需要首先使用一个更精细调整的距离函数!


你的第一和第二段有参考资料吗? - Chuck
不,但它们应该相当明显,来自通常的“维度诅咒”实例(参见调查),并尝试找到任何支持除欧几里得距离之外的k-d-tree...支持其他距离是可能的,但不常见(ELKI允许所有闵可夫斯基距离+平方欧几里得距离,但大多数只有欧几里得距离)。只需考虑k-d-trees仅使用一个维度进行修剪,并将其与涉及所有维度的距离进行比较。此外,您的分裂将无法在每个维度上进行分裂。 - Erich Schubert

3
KD树在21个维度下表现良好,如果您提前退出,在查看所有点的5%后,FLANN可以做到这一点(以及其他加速),以匹配128-dim SIFT向量。 (不幸的是,FLANN仅使用欧几里德距离,而快速且稳定的scipy.spatial.cKDTree仅使用Lp度量;这些可能或可能不足以适用于您的数据)。当然,在速度和精度之间存在权衡。

(如果您能描述Ndata,Nquery,数据分布,这可能有助于人们尝试类似的数据。)

添加于4月26日,在我的旧mac ppc上带有截止时间的cKDTree运行时间,以给出可行性的非常粗略的想法:

kdstats.py p=2 dim=21 N=1000000 nask=1000 nnear=2 cutoff=1000 eps=0 leafsize=10 clustype=uniformp
14 sec to build KDtree of 1000000 points
kdtree: 1000 queries looked at av 0.1 % of the 1000000 points, 0.31 % of 188315 boxes; better 0.0042 0.014 0.1 %
3.5 sec to query 1000 points
distances to 2 nearest: av 0.131  max 0.253

kdstats.py p=2 dim=21 N=1000000 nask=1000 nnear=2 cutoff=5000 eps=0 leafsize=10 clustype=uniformp
14 sec to build KDtree of 1000000 points
kdtree: 1000 queries looked at av 0.48 % of the 1000000 points, 1.1 % of 188315 boxes; better 0.0071 0.026 0.5 %
15 sec to query 1000 points
distances to 2 nearest: av 0.131  max 0.245

3

2
据我所知,均值漂移不适用于聚类高维数据。K均值可能是更好的选择。 - fdermishin

3

我认为,在布尔特征的tf-idf上使用余弦函数对大多数问题都是有效的。这是因为它是一种经过时间验证的启发式算法,用于许多搜索引擎,如Lucene。在我的经验中,欧几里得距离对于任何类似文本的数据都表现不佳。选择不同的权重和k个实例可以通过训练数据和暴力参数选择来完成。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接