以NumPy向量化的方式思考,执行逐元素微分、平方和最后沿着最后一个轴求平方根。因此,直接的实现方案是 -
np.sqrt(((A - B)**2).sum(-1))
我们可以使用np.einsum
一次性完成沿着最后一个轴的平方和运算,从而使其更加高效,代码如下 -
subs = A - B
out = np.sqrt(np.einsum('ijk,ijk->ij',subs,subs))
使用numexpr
模块的另一种选择 -
import numexpr as ne
np.sqrt(ne.evaluate('sum((A-B)**2,2)'))
由于我们在最后一个轴上使用了长度为2
,因此我们可以只对其进行切片并将其馈送到evaluate
方法中。请注意,在评估字符串内部无法进行切片。因此,修改后的实现如下:
a0 = A[...,0]
a1 = A[...,1]
b0 = B[...,0]
b1 = B[...,1]
out = ne.evaluate('sqrt((a0-b0)**2 + (a1-b1)**2)')
运行时测试
函数定义 -
def sqrt_sum_sq_based(A,B):
return np.sqrt(((A - B)**2).sum(-1))
def einsum_based(A,B):
subs = A - B
return np.sqrt(np.einsum('ijk,ijk->ij',subs,subs))
def numexpr_based(A,B):
return np.sqrt(ne.evaluate('sum((A-B)**2,2)'))
def numexpr_based_with_slicing(A,B):
a0 = A[...,0]
a1 = A[...,1]
b0 = B[...,0]
b1 = B[...,1]
return ne.evaluate('sqrt((a0-b0)**2 + (a1-b1)**2)')
时间 -
In [288]:
...: dim = 2
...: N = 1000
...: A = np.random.rand(N,N,dim)
...: B = np.random.rand(N,N,dim)
...:
In [289]: %timeit sqrt_sum_sq_based(A,B)
10 loops, best of 3: 40.9 ms per loop
In [290]: %timeit einsum_based(A,B)
10 loops, best of 3: 22.9 ms per loop
In [291]: %timeit numexpr_based(A,B)
10 loops, best of 3: 18.7 ms per loop
In [292]: %timeit numexpr_based_with_slicing(A,B)
100 loops, best of 3: 8.23 ms per loop
In [293]: %timeit np.linalg.norm(A-B, axis=-1)
10 loops, best of 3: 45 ms per loop
np.einsum
和普通numpy效率的链接或资源? - jadsqeinsum
一次完成两件事情,这使得它在这里非常有益。 - Divakar