Skip to main content
 首页 » 编程设计

python之使用numpy在python中矢量化空间距离

2025年05月04日22soundcode

我在 python 中有一个 numpy 数组,它包含很多 (10k+) 个 3D 顶点(坐标为 [x,y,z] 的向量)。我需要计算这些点的所有可能对之间的距离。

使用 scipy 很容易做到:

import scipy 
D = spdist.cdist(verts, verts) 

但由于引入新依赖项的项目政策,我不能使用它。

所以我想出了这个天真的代码:

def vert_dist(self, A, B): 
    return ((B[0]-A[0])**2+(B[1]-A[1])**2+(B[2]-A[2])**2)**(1.0/2) 
 
# Pairwise distance between verts 
#Use SciPy, otherwise use fallback 
try: 
    import scipy.spatial.distance as spdist 
    D = spdist.cdist(verts, verts) 
except ImportError: 
    #FIXME: This is VERY SLOW: 
    D = np.empty((len(verts), len(verts)), dtype=np.float64) 
    for i,v in enumerate(verts): 
        #self.app.setStatus(_("Calculating distance %d of %d (SciPy not installed => using SLOW AF fallback method)"%(i,len(verts))), True) 
        for j in range(i,len(verts)): 
            D[j][i] = D[i][j] = self.vert_dist(v,verts[j]) 

vert_dist() 计算两个顶点之间的 3D 距离,其余代码只是遍历一维数组中的顶点,并为每个顶点计算同一数组中每个顶点之间的距离,并生成距离的二维数组。

但与 scipy 的 native C 代码相比,这非常慢(1000 倍)。我想知道我是否可以使用纯 numpy 来加速它。至少在某种程度上。

更多信息:https://github.com/scipy/scipy/issues/9172

顺便说一句,我已经尝试过 PyPy JIT 编译器,它比纯 python 还要慢(10 倍)。

更新:我能够像这样加快速度:

    def vert_dist_matrix(self, verts): 
            #FIXME: This is VERY SLOW: 
            D = np.empty((len(verts), len(verts)), dtype=np.float64) 
            for i,v in enumerate(verts): 
                    D[i] = D[:,i] = np.sqrt(np.sum(np.square(verts-verts[i]), axis=1)) 
            return D 

这通过一次计算整行来消除内部循环,这使得东西更快,但仍然明显比 scipy 慢。所以还是看@Divakar的解决方案

请您参考如下方法:

有 eucl_dist包(免责声明:我是它的作者)基本上包含两种方法来解决计算平方欧氏距离的问题,这两种方法比 SciPy 的 cdist 更有效,特别是对于大型数组(具有相当大的列数).

我们将使用其 source code 中的一些代码适应我们这里的问题给我们两种方法。

方法 #1

在 wiki contents 之后, 我们可以利用 matrix-multiplication 和一些 NumPy specific implementations对于我们的第一种方法,就像这样 -

def pdist_squareformed_numpy(a): 
    a_sumrows = np.einsum('ij,ij->i',a,a) 
    dist = a_sumrows[:,None] + a_sumrows -2*np.dot(a,a.T) 
    np.fill_diagonal(dist,0) 
    return dist 

方法 #2

另一种方法是创建输入数组的“扩展”版本,在该 github 源代码链接中再次详细讨论了我们的第二种方法,这对于较小的列更好,就像这里的情况一样 -

def ext_arrs(A,B, precision="float64"): 
    nA,dim = A.shape 
    A_ext = np.ones((nA,dim*3),dtype=precision) 
    A_ext[:,dim:2*dim] = A 
    A_ext[:,2*dim:] = A**2 
 
    nB = B.shape[0] 
    B_ext = np.ones((dim*3,nB),dtype=precision) 
    B_ext[:dim] = (B**2).T 
    B_ext[dim:2*dim] = -2.0*B.T 
    return A_ext, B_ext 
 
def pdist_squareformed_numpy_v2(a): 
    A_ext, B_ext = ext_arrs(a,a) 
    dist = A_ext.dot(B_ext) 
    np.fill_diagonal(dist,0) 
    return dist 

请注意,这些为我们提供了平方欧氏距离。因此,对于实际距离,如果这是所需的最终输出,我们希望使用 np.sqrt()。

样本运行-

In [380]: np.random.seed(0) 
     ...: a = np.random.rand(5,3) 
 
In [381]: from scipy.spatial.distance import cdist 
 
In [382]: cdist(a,a) 
Out[382]:  
array([[0.  , 0.29, 0.42, 0.2 , 0.57], 
       [0.29, 0.  , 0.58, 0.42, 0.76], 
       [0.42, 0.58, 0.  , 0.45, 0.9 ], 
       [0.2 , 0.42, 0.45, 0.  , 0.51], 
       [0.57, 0.76, 0.9 , 0.51, 0.  ]]) 
 
In [383]: np.sqrt(pdist_squareformed_numpy(a)) 
Out[383]:  
array([[0.  , 0.29, 0.42, 0.2 , 0.57], 
       [0.29, 0.  , 0.58, 0.42, 0.76], 
       [0.42, 0.58, 0.  , 0.45, 0.9 ], 
       [0.2 , 0.42, 0.45, 0.  , 0.51], 
       [0.57, 0.76, 0.9 , 0.51, 0.  ]]) 
 
In [384]: np.sqrt(pdist_squareformed_numpy_v2(a)) 
Out[384]:  
array([[0.  , 0.29, 0.42, 0.2 , 0.57], 
       [0.29, 0.  , 0.58, 0.42, 0.76], 
       [0.42, 0.58, 0.  , 0.45, 0.9 ], 
       [0.2 , 0.42, 0.45, 0.  , 0.51], 
       [0.57, 0.76, 0.9 , 0.51, 0.  ]]) 

10k 点计时 -

In [385]: a = np.random.rand(10000,3) 
 
In [386]: %timeit cdist(a,a) 
1 loop, best of 3: 309 ms per loop 
 
# Approach #1 
In [388]: %timeit pdist_squareformed_numpy(a) # squared eucl distances 
1 loop, best of 3: 668 ms per loop 
 
In [389]: %timeit np.sqrt(pdist_squareformed_numpy(a)) # actual eucl distances 
1 loop, best of 3: 812 ms per loop 
 
# Approach #2 
In [390]: %timeit pdist_squareformed_numpy_v2(a) # squared eucl distances 
1 loop, best of 3: 237 ms per loop 
 
In [391]: %timeit np.sqrt(pdist_squareformed_numpy_v2(a)) # actual eucl distances 
1 loop, best of 3: 395 ms per loop 

第二种方法在性能上似乎接近 cdist 方法!