【问题标题】:Efficiently Calculating a Euclidean Distance Matrix Using Numpy使用 Numpy 有效计算欧几里得距离矩阵
【发布时间】:2014-05-08 09:20:14
【问题描述】:

我在二维空间中有一组点,需要计算每个点到其他点的距离。

我的点数相对较少,可能最多 100 个。但由于我需要经常快速地执行此操作以确定这些移动点之间的关系,而且我知道迭代这些点可以与 O(n^2) 复杂度一样糟糕,我正在寻找利用 numpy 的矩阵魔法(或 scipy)的方法。

在我的代码中,每个对象的坐标都存储在它的类中。但是,当我更新类坐标时,我也可以在一个 numpy 数组中更新它们。

class Cell(object):
    """Represents one object in the field."""
    def __init__(self,id,x=0,y=0):
        self.m_id = id
        self.m_x = x
        self.m_y = y

我想到创建一个欧几里得距离矩阵来防止重复,但也许你有一个更聪明的数据结构。

我也对漂亮算法的指针持开放态度。

另外,我注意到有类似的问题涉及欧几里得距离和 numpy,但没有找到任何直接解决有效填充完整距离矩阵的问题。

【问题讨论】:

  • 在这里,这可能会有所帮助:scipy.spatial.distance.pdist
  • 无论如何,复杂度都将是 O(n^2):对于一组一般点,您能做的最好的事情就是只计算 n * (n - 1) / 2 距离,这仍然是 O(n^ 2).
  • 如果scipy可以用,考虑scipy.spatial.distance_matrix

标签: python numpy matrix performance euclidean-distance


【解决方案1】:

您可以利用complex 类型:

# build a complex array of your cells
z = np.array([complex(c.m_x, c.m_y) for c in cells])

第一个解决方案

# mesh this array so that you will have all combinations
m, n = np.meshgrid(z, z)
# get the distance via the norm
out = abs(m-n)

第二种解决方案

网格化是主要思想。但是numpy 很聪明,所以你不必生成m & n。只需使用z 的转置版本计算差异。网格是自动完成的:

out = abs(z[..., np.newaxis] - z)

第三种解决方案

而如果z直接设置为二维数组,则可以使用z.T代替怪异的z[..., np.newaxis]。最后,您的代码将如下所示:

z = np.array([[complex(c.m_x, c.m_y) for c in cells]]) # notice the [[ ... ]]
out = abs(z.T-z)

示例

>>> z = np.array([[0.+0.j, 2.+1.j, -1.+4.j]])
>>> abs(z.T-z)
array([[ 0.        ,  2.23606798,  4.12310563],
       [ 2.23606798,  0.        ,  4.24264069],
       [ 4.12310563,  4.24264069,  0.        ]])

作为补充,您可能想在之后删除重复项,取上三角形:

>>> np.triu(out)
array([[ 0.        ,  2.23606798,  4.12310563],
       [ 0.        ,  0.        ,  4.24264069],
       [ 0.        ,  0.        ,  0.        ]])

一些基准测试

>>> timeit.timeit('abs(z.T-z)', setup='import numpy as np;z = np.array([[0.+0.j, 2.+1.j, -1.+4.j]])')
4.645645342274779
>>> timeit.timeit('abs(z[..., np.newaxis] - z)', setup='import numpy as np;z = np.array([0.+0.j, 2.+1.j, -1.+4.j])')
5.049334864854522
>>> timeit.timeit('m, n = np.meshgrid(z, z); abs(m-n)', setup='import numpy as np;z = np.array([0.+0.j, 2.+1.j, -1.+4.j])')
22.489568296184686

【讨论】:

  • 你找到距离了吗?如果是这样,你就失去了我。这是在哪里发生的?
  • @WesModes 我编辑了我的帖子以使其更清晰,如果您仍然迷路,请告诉我。
【解决方案2】:

如果你不需要完整的距离矩阵,你最好使用 kd-tree。考虑scipy.spatial.cKDTreesklearn.neighbors.KDTree。这是因为 kd-tree kan 在 O(n log n) 时间内找到 k 个最近邻,因此您避免了计算所有 n × n 距离的 O(n**2) 复杂度。

【讨论】:

  • 这是一个很好的答案。需要整个距离矩阵的次数很少。
【解决方案3】:

Jake Vanderplas 在Python 数据科学手册 中使用广播给出了这个示例,这与@shx2 提出的非常相似。

import numpy as np
rand = random.RandomState(42)
X = rand.rand(3, 2)  
dist_sq = np.sum((X[:, np.newaxis, :] - X[np.newaxis, :, :]) ** 2, axis = -1)

dist_sq
array([[0.        , 0.18543317, 0.81602495],
       [0.18543317, 0.        , 0.22819282],
       [0.81602495, 0.22819282, 0.        ]])

【讨论】:

  • scipy.spatial.distance.cdist 比这个快,在我的测试中是 9 倍
  • @Tweakimp - 你应该通过调用%timeit 来写一个答案,也许是一个小的(10x10)和大的(1,000,000 x 1,000,000)距离矩阵。这对人们来说将是非常有用的信息!
  • 我无法在我的 jupyter 笔记本中使用 %timeit,因为我使用了在线变体,并且对于这么大的数组,它的内存不足
  • 这是一个超级快速的解决方案。
  • 这个解决方案是广播的一个很好的例子,但它消耗 Θ(n^2 * d) 内存(其中 n 是向量的数量,d 是维度),而最佳解决方案只会消耗 O(n^2)。 (由/usr/bin/time -v确认。)
【解决方案4】:

以下是使用 numpy 的方法:

import numpy as np

x = np.array([0,1,2])
y = np.array([2,4,6])

# take advantage of broadcasting, to make a 2dim array of diffs
dx = x[..., np.newaxis] - x[np.newaxis, ...]
dy = y[..., np.newaxis] - y[np.newaxis, ...]
dx
=> array([[ 0, -1, -2],
          [ 1,  0, -1],
          [ 2,  1,  0]])

# stack in one array, to speed up calculations
d = np.array([dx,dy])
d.shape
=> (2, 3, 3)

现在剩下的就是计算沿 0 轴的 L2 范数(如 here 所讨论的那样):

(d**2).sum(axis=0)**0.5
=> array([[ 0.        ,  2.23606798,  4.47213595],
          [ 2.23606798,  0.        ,  2.23606798],
          [ 4.47213595,  2.23606798,  0.        ]])

【讨论】:

  • 如果你有很大的 x 或 y,这实际上会占用相当多的内存,同时也很慢。 SciPy 的距离矩阵应该会快一些。
猜你喜欢
  • 2015-09-23
  • 2020-06-16
  • 2015-01-13
  • 2016-08-02
  • 1970-01-01
  • 2010-11-26
相关资源
最近更新 更多