【问题标题】:Vectorizing euclidean distance computation - NumPy向量化欧几里德距离计算 - NumPy
【发布时间】:2017-05-03 20:40:52
【问题描述】:

我的问题是关于我的代码的矢量化。我有一个包含 3D 坐标的数组和一个包含连接坐标的边信息的数组:

In [8]:coords
Out[8]: 
array([[ 11.22727013,  24.72620964,   2.02986932],
       [ 11.23895836,  24.67577744,   2.04130101],
       [ 11.23624039,  24.63677788,   2.04096866],
       [ 11.22516632,  24.5986824 ,   2.04045677],
       [ 11.21166992,  24.56095695,   2.03898215],
       [ 11.20334721,  24.5227356 ,   2.03556442],
       [ 11.2064085 ,  24.48479462,   2.03098583],
       [ 11.22059727,  24.44837189,   2.02649784],
       [ 11.24213409,  24.41513252,   2.01979685]])

In [13]:edges
Out[13]: 
array([[0, 1],
       [1, 2],
       [2, 3],
       [3, 4],
       [4, 5],
       [5, 6],
       [6, 7],
       [7, 8],], dtype=int32)

现在,我想计算边缘数组中坐标之间的欧几里得距离之和。例如。从 coords[0] 到 coords[1] 的距离 + 从 coords[1] 到 coords[2] 的距离.....

我有以下代码,可以完成这项工作:

def networkLength(coords, edges):

   from scipy.spatial import distance 
   distancesNetwork = np.array([])    

   for i in range(edges.shape[0]):
        distancesNetwork = np.append(distancesNetwork, distance.euclidean(coords[edges[i, 0]], coords[edges[i, 1]]))

   return sum(distancesNetwork)

我想知道是否可以对代码进行矢量化,而不是循环。 pythonian的方法是什么?非常感谢!!

【问题讨论】:

  • 1.将 import 语句放在任何模块开头以外的任何位置都被认为是一种非常糟糕的做法。从函数内部导入特别奇怪。 2. 在循环中添加 numpy 数组是非常低效的,因为它会导致许多重新分配,使得任何算法在最佳情况下有效 O(n^2)。
  • @EliKorvigo 1. 我不敢苟同:在函数中导入有助于打破循环依赖关系,或者拉入仅用于可选功能的大型包,以便从不使用这些功能的人可以使用模块而不需要大包。这是不寻常的,当然,但不是很糟糕的做法。只知道你为什么这样做。我同意这里可能没有必要。
  • @DavidZ 在我的实践中,当您想避免导入繁重的内容时,您可以简单地将函数放入包中的单独模块中。这也是解决循环导入问题的常用方法。
  • 谢谢大家!反馈很有帮助!

标签: python numpy scipy vectorization euclidean-distance


【解决方案1】:

方法#1

我们可以切出第一列和第二列以索引到coords,而不是沿着它们迭代每个元素并执行欧几里德距离计算,该计算涉及到每行的元素平方和求和,然后得到元素-明智的平方根。最后,我们需要对一个标量的所有这些值求和,如原始代码所示。

因此,一种矢量化实现将是 -

np.sqrt(((coords[edges[:, 0]] - coords[edges[:, 1]])**2).sum(1)).sum()

NumPy 中有一个内置函数可以像np.linalg.norm 一样执行这些距离计算操作。在性能方面,我认为它可以与我们之前列出的相媲美。为了完整起见,实现将是 -

np.linalg.norm(coords[edges[:, 0]] - coords[edges[:, 1]],axis=1).sum()

方法 #2

调整之前的方法,我们可以使用np.einsum,它可以在一个步骤中同时执行squaring 和summing along each row,这样会更有效率。

实现看起来像这样 -

s = coords[edges[:, 0]] - coords[edges[:, 1]]
out = np.sqrt(np.einsum('ij,ij->i',s,s)).sum()

运行时测试

函数定义-

def networkLength(coords, edges): # Original code from question
   distancesNetwork = np.array([])    
   for i in range(edges.shape[0]):
        distancesNetwork = np.append(distancesNetwork, \
        distance.euclidean(coords[edges[i, 0]], coords[edges[i, 1]]))
   return sum(distancesNetwork)

def vectorized_app1(coords, edges):
    return np.sqrt(((coords[edges[:, 0]] - coords[edges[:, 1]])**2).sum(1)).sum()

def vectorized_app2(coords, edges):
    s = coords[edges[:, 0]] - coords[edges[:, 1]]
    return np.sqrt(np.einsum('ij,ij->i',s,s)).sum()

验证和计时 -

In [114]: # Setup bigger inputs
     ...: coords = np.random.rand(100,3)
     ...: edges = np.random.randint(0,100,(10000,2))

# Verify results across all approaches
In [115]: networkLength(coords, edges)
Out[115]: 6607.8829431403547

In [116]: vectorized_app1(coords, edges)
Out[116]: 6607.8829431403337

In [117]: vectorized_app2(coords, edges)
Out[117]: 6607.8829431403337

In [118]: %timeit networkLength(coords, edges)
     ...: %timeit vectorized_app1(coords, edges)
     ...: %timeit vectorized_app2(coords, edges)
     ...: 
1 loops, best of 3: 519 ms per loop
1000 loops, best of 3: 822 µs per loop
1000 loops, best of 3: 668 µs per loop

【讨论】:

  • 哇,非常感谢您抽出宝贵的时间。这对我的理解很有帮助!!我可能需要一些时间才能理解 np.einsum 方法中发生的事情:)
  • fyi - 刚刚比较了 'np.linalg.norm' 和 'np.sqrt(((coords[edges[:, 0]] - coords[edges[:, 1]])* *2).sum(1)).sum()' 方法。后者稍快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-23
  • 2010-11-26
  • 1970-01-01
  • 2015-04-25
  • 2019-09-14
相关资源
最近更新 更多