问题描述
我有两个1D numpy数组A和B,分别是大小(n,)和(m,),它们对应于一行上点的x位置。 我想计算A中每个点到B中每个点之间的距离。然后我需要在设定的y距离d处使用这些距离来计算A中每个点的电位。
我目前正在使用以下内容:
V = numpy.zeros(n)
for i in range(n):
xdist = A[i] - B
r = numpy.sqrt(xdist**2 + d**2)
dV = 1/r
V[i] = numpy.sum(dV)
这适用于大型数据集可能需要一段时间,所以我想使用类似于scipy.spatial.distance.cdist的函数,它不适用于1D数组,我不想为数组添加另一个维度因为它们变得太大了。
1楼
矢量化方法
在使用引入新轴并因此利用将A
扩展到2D
之后的一种矢量化方法将是 -
(1/(np.sqrt((A[:,None] - B)**2 + d**2))).sum(1)
大阵列的混合方法
现在,对于大型数组,我们可能必须将数据划分为块。
因此,使用BSZ
作为块大小,我们将采用混合方法,如此 -
dsq = d**2
V = np.zeros((n//BSZ,BSZ))
for i in range(n//BSZ):
V[i] = (1/(np.sqrt((A[i*BSZ:(i+1)*BSZ,None] - B)**2 + dsq))).sum(1)
运行时测试
方法 -
def original_app(A,B,d):
V = np.zeros(n)
for i in range(n):
xdist = A[i] - B
r = np.sqrt(xdist**2 + d**2)
dV = 1/r
V[i] = np.sum(dV)
return V
def vectorized_app1(A,B,d):
return (1/(np.sqrt((A[:,None] - B)**2 + d**2))).sum(1)
def vectorized_app2(A,B,d, BSZ = 100):
dsq = d**2
V = np.zeros((n//BSZ,BSZ))
for i in range(n//BSZ):
V[i] = (1/(np.sqrt((A[i*BSZ:(i+1)*BSZ,None] - B)**2 + dsq))).sum(1)
return V.ravel()
时间和验证 -
In [203]: # Setup inputs
...: n,m = 10000,2000
...: A = np.random.rand(n)
...: B = np.random.rand(m)
...: d = 10
...:
In [204]: out1 = original_app(A,B,d)
...: out2 = vectorized_app1(A,B,d)
...: out3 = vectorized_app2(A,B,d, BSZ = 100)
...:
...: print np.allclose(out1, out2)
...: print np.allclose(out1, out3)
...:
True
True
In [205]: %timeit original_app(A,B,d)
10 loops, best of 3: 133 ms per loop
In [206]: %timeit vectorized_app1(A,B,d)
10 loops, best of 3: 138 ms per loop
In [207]: %timeit vectorized_app2(A,B,d, BSZ = 100)
10 loops, best of 3: 65.2 ms per loop
我们可以使用参数块大小BSZ
-
In [208]: %timeit vectorized_app2(A,B,d, BSZ = 200)
10 loops, best of 3: 74.5 ms per loop
In [209]: %timeit vectorized_app2(A,B,d, BSZ = 50)
10 loops, best of 3: 67.4 ms per loop
因此,最好的一个似乎是在我的结尾提供2x
倍的加速,块大小为100
。
2楼
编辑:仔细观察后,我的回答几乎与Divakar的相同。 但是,您可以通过就地执行操作来节省一些内存。 沿第二轴取总和比第一轴长。
import numpy
a = numpy.random.randint(0,10,10) * 1.
b = numpy.random.randint(0,10,10) * 1.
xdist = a[:,None] - b
xdist **= 2
xdist += d**2
xdist **= -1
V = numpy.sum(xdist, axis=1)
它提供与您的代码相同的解决方案。
3楼
我想使用类似于scipy.spatial.distance.cdist的函数,它不适用于1D数组,我不想为数组添加另一个维度,因为它们变得太大了。
工作正常,你只需要重新整形数组以形成(n,1)而不是(n,)。
您可以通过使用A[:, None]
或A.reshape(-1, 1)
将一个维度添加到一维数组A
而不复制基础数据。
例如,
In [56]: from scipy.spatial.distance import cdist
In [57]: A
Out[57]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [58]: B
Out[58]: array([0, 2, 4, 6, 8])
In [59]: A[:, None]
Out[59]:
array([[0],
[1],
[2],
[3],
[4],
[5],
[6],
[7],
[8],
[9]])
In [60]: cdist(A[:, None], B[:, None])
Out[60]:
array([[ 0., 2., 4., 6., 8.],
[ 1., 1., 3., 5., 7.],
[ 2., 0., 2., 4., 6.],
[ 3., 1., 1., 3., 5.],
[ 4., 2., 0., 2., 4.],
[ 5., 3., 1., 1., 3.],
[ 6., 4., 2., 0., 2.],
[ 7., 5., 3., 1., 1.],
[ 8., 6., 4., 2., 0.],
[ 9., 7., 5., 3., 1.]])
要计算代码中显示的V
,可以使用cdist
with metric='sqeuclidean'
,如下所示:
In [72]: d = 3.
In [73]: r = np.sqrt(cdist(A[:,None], B[:,None], metric='sqeuclidean') + d**2)
In [74]: V = (1/r).sum(axis=1)