【问题标题】:What is the Most Efficient Way to Compute the (euclidean) Distance of the Nearest Neighbor in a List of (x,y,z) points?计算(x,y,z)点列表中最近邻的(欧几里得)距离的最有效方法是什么?
【发布时间】:2020-02-02 02:32:20
【问题描述】:

计算数组中每个点的最近邻(欧几里得)距离的最有效方法是什么?

我有一个 100k (X,Y,Z) 点的列表,我想计算最近邻距离的列表。距离的索引将对应点的索引。

我调查了 PYOD 和 sklearn 邻居,但这些似乎需要“教学”。我认为我的问题比这更简单。对于每个点:找到最近的邻居,计算距离。

示例数据:

points = [
     (0             0   1322.1695
      0.006711111   0   1322.1696
      0.026844444   0   1322.1697
      0.0604        0   1322.1649
      0.107377778   0   1322.1651
      0.167777778   0   1322.1634
      0.2416        0   1322.1629
      0.328844444   0   1322.1631
      0.429511111   0   1322.1627...)]

计算 k = 1 个最近邻距离

结果格式:

results = [nearest neighbor distance]

示例结果:

results = [
0.005939372
0.005939372
0.017815632
0.030118587
0.041569616
0.053475883
0.065324964
0.077200014
0.089077602)
]

更新:

我已经实施了两种建议的方法。

  1. 使用 scipy.spatial.cdist 计算完整距离矩阵
  2. 使用半径 R 中最近的 X 个邻居来查找每个点的邻居距离子集并返回最小的。

结果是方法 2 比方法 1 更快,但实施起来要付出更多的努力(有道理)。

似乎方法 1 的限制因素是运行完整计算所需的内存,尤其是当我的数据集接近 10^5 (x, y, z) 点时。对于我的 23k 点数据集,捕获最小距离大约需要 100 秒。

对于方法 2,速度缩放为 n_radius^2。也就是说,“邻居半径平方”,这实际上意味着算法随着包含的邻居的数量线性缩放。使用大约 5 的半径(给定应用程序绰绰有余),对于 23k 点的集合,需要 5 秒才能提供与 point_list 本身顺序相同的分钟列表。 “精确解”与方法二的差分矩阵基本为零。

感谢大家的帮助!

【问题讨论】:

  • 你知道这些距离的值应该是多少吗?将有助于获得预期的输出。
  • 问题是什么?
  • 到质心的距离?在所有点之间?
  • 欢迎来到 SO。这不是讨论论坛或教程。请使用tour 并花时间阅读How to Ask 以及该页面上的其他链接。
  • d_kennetz 是的。我在 JMP 中有输出。希望将其移植到 python。

标签: python knn nearest-neighbor euclidean-distance


【解决方案1】:

类似于 Caleb 的回答,但如果您获得的距离大于之前的某个最小距离(抱歉 - 没有代码),您可以停止迭代循环。

我曾经为视频游戏编程。计算两点之间的实际距离需要太多的 CPU。我们所做的是将“屏幕”划分为更大的笛卡尔正方形,如果 Delta-X 或 Delta-Y “太远”,则避免实际距离计算——这只是减法,所以也许类似的东西来限定实际欧几里得的位置需要计算距离度量(根据需要扩展到n维)?

编辑 - 扩展“太远”的候选对选择 cmets。 为简洁起见,我将假设一个二维景观。 取兴趣点 (X0,Y0) 并​​围绕该点“绘制”一个 nxn 正方形,以 (X0,Y0) 为原点。

遍历初始点列表并形成该正方形内的候选点列表。这样做时,如果 DeltaX [ABS(Xi-X0)] 在正方形之外,则无需计算 DeltaY。

如果没有候选点,则将正方形变大并迭代。

如果只有一个候选点并且在正方形所内圆的半径内,那就是你的最小值。

如果有“太多”候选者,则将正方形缩小,但您只需要重新检查此迭代中的候选者列表,而不是所有点。

如果没有“太多”候选者,则计算该列表的距离。这样做时,首先计算第一个候选者的 DeltaX^2 + DeltaY^2。如果对于后续的候选人,DetlaX^2 大于迄今为止的最小值,则无需计算 DeltaY^2。

如果该计算的最小值在正方形内接圆的半径内,则该最小值就是最小值。

如果不是,您需要返回之前的候选列表,其中包括圆内具有该最小值的点。例如,如果您在一个 2x2 正方形中以恰好位于顶点 X=1、Y=1 上的一个候选对象结束,则距离/半径将为 SQRT(2)。所以回到之前的候选列表,它的平方大于或等于 2xSQRT(2)。

如果有必要,生成一个仅包含 +/- SQRT(2) 正方形的点的新候选列表。 如上所述计算这些候选点的距离 - 忽略任何超过迄今为止计算的最小值的距离。

在您只有一个候选人之前,无需计算 Delta^2 之和的平方根。

如何确定初始正方形的大小,或者它是否应该是一个矩形,以及如何增加或减小正方形/矩形的大小可能会受到数据分布的应用知识的影响。

如果您使用的语言支持,我会考虑使用递归算法。

【讨论】:

  • 感谢您的反馈。我希望这样的事情是最好的方法。距离算法可能仍按 n^2 缩放,但如果您可以将每个点的数据集减少到喜欢 10 个甚至 100 个周围点,并且不要多次重新计算相同的东西(只需查找),那么速度可能会受到排序和查找先前值的限制。之后可能会像 nlog n 一样扩展。
  • 扩展了我的原始答案,以便更清楚地选择候选点。
【解决方案2】:

您可用的最快选项可能是scipy.spatial.distance.cdist,它会查找其输入中所有点之间的成对距离。虽然找到所有这些距离可能不是找到最近邻居的最快算法,但cdist 是用 C 实现的,因此它可能比您在 Python 中尝试的任何方法运行得更快。

import scipy as sp
import scipy.spatial
from scipy.spatial.distance import cdist

points = sp.array(...)
distances = sp.spatial.distance.cdist(points)

# An element is not its own nearest neighbor
sp.fill_diagonal(distances, sp.inf)

# Find the index of each element's nearest neighbor
mins = distances.argmin(0)

# Extract the nearest neighbors from the data by row indexing
nearest_neighbors = points[mins, :]

#  Put the arrays in the specified shape
results = np.stack((points, nearest_neighbors), 1)

理论上你可以让这个运行得更快(主要是通过将所有步骤组合到一个算法中),但除非你用 C 语言编写,否则你将无法与 SciPy/NumPy 竞争。

(cdist 在 Θ(n2) 时间内运行(如果每个点的大小是固定的),算法的其他部分在 O(n) 时间内运行,所以即使您确实尝试过在 Python 中优化代码,但您不会注意到少量数据的变化,而对于更多数据,cdist 会掩盖改进。)

【讨论】:

    【解决方案3】:

    这个怎么样?

    from scipy.spatial import distance
    
    A = (0.003467119 ,0.01422762 ,0.0101960126)
    B = (0.007279433  ,0.01651597  ,0.0045558849)
    C = (0.005392258  ,0.02149997  ,0.0177409387)
    D = (0.017898802  ,0.02790659  ,0.0006487222)
    E = (0.013564214  ,0.01835688  ,0.0008102952)
    F = (0.013375397  ,0.02210725 ,0.0286032185)
    
    points = [A, B, C, D, E, F]
    results = []
    for point in points:
        distances = [{'point':point, 'neighbor':p, 'd':distance.euclidean(point, p)} for p in points if p != point]
        results.append(min(distances, key=lambda k:k['d']))
    

    结果将是一个对象列表,如下所示:

    results = [
        {'point':(x1, y1, z1), 'neighbor':(x2, y2, z2), 'd':"distance from point to neighbor"},
    ...]
    

    其中point 是参考点,neighbor 是该点最近的邻居。

    【讨论】:

    • 谢谢迦勒。这会起作用,但是否有一种不那么蛮力的方法?这将具有 > N^2 的缩放比例,因为每个点计算距离然后为数组中的所有点找到最小值。数据大小接近 10^5。我正在考虑使用 k = 1 NN 或 LOF 方法来做到这一点。
    • @ColePierson 可能有更有效的方法,但我对此知之甚少。另外,我更新了您的问题,以表明您正在寻找最有效的方法,而不仅仅是任何旧方法(如我的)。我建议您对其进行审查,以确保我没有改变您答案的含义。
    • @ColePierson 一般来说,如果你正在寻找最有效的方法来做 x,你应该在你的问题中强调,所以无知的人(比如我)不要给你次优的解决方案。
    • 感谢@CalebGoodman。一点也不无知。事实上,除非出现明显的赢家,否则我可能会使用它。我只知道有一些算法旨在优化这种类型的计算,实现这种类型的解决方案将是一次很好的学习体验。
    猜你喜欢
    • 1970-01-01
    • 2011-07-19
    • 2017-06-22
    • 2021-12-03
    • 2020-06-11
    • 2017-10-09
    • 2017-03-21
    • 2013-04-07
    • 1970-01-01
    相关资源
    最近更新 更多