使用 numpy 在 python 中矢量化空间距离
Vectorized spatial distance in python using numpy
我在 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 的本机 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's cdist
更有效,特别是对于大型数组(有大量的列)。
我们将使用其 source code
中的一些代码来适应我们这里的问题,为我们提供两种方法。
方法 #1
按照我们的第一种方法 wiki contents
, we could leverage matrix-multiplication
and some 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
另一种方法是创建 "extended" 版本的输入数组,在 github 源代码 link 中再次详细讨论了我们的第二种方法,这更适合较小的列,就像这里的情况一样 -
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
一种!
我在 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 的本机 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's cdist
更有效,特别是对于大型数组(有大量的列)。
我们将使用其 source code
中的一些代码来适应我们这里的问题,为我们提供两种方法。
方法 #1
按照我们的第一种方法 wiki contents
, we could leverage matrix-multiplication
and some 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
另一种方法是创建 "extended" 版本的输入数组,在 github 源代码 link 中再次详细讨论了我们的第二种方法,这更适合较小的列,就像这里的情况一样 -
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
一种!