我正在研究如何在python3中尽可能高效地计算形式的双重总和中的点积:
import cmath
for j in range(0,N):
for k in range(0,N):
sum_p += cmath.exp(-1j * sum(a*b for a,b in zip(x, [l - m for l, m in zip(r_p[j], r_p[k])])))
其中r_np是几千个三元组的数组,x是一个常数三元组。长度为N=1000
三倍的时间约为2.4s
。同样使用numpy:
import numpy as np
for j in range(0,N):
for k in range(0,N):
sum_np = np.add(sum_np, np.exp(-1j * np.inner(x_np,(r_np[j] - r_np[k]))))
运行时约为4.0s
,实际上速度较慢。我认为这是由于没有大的矢量化优势,只有短3点3是np.dot,这是通过启动循环中的那些N ^ 2来吃掉的。 然而,通过使用普通python3与map和mul,我可以获得第一个例子的适度加速:
from operator import mul
for j in range(0,N):
for k in range(0,N):
sum_p += cmath.exp(-1j * sum(map(mul,x, [l - m for l, m in zip(r_p[j], r_p[k])])))
运行时约为2.0s
尝试使用if条件不计算案例j=k
,其中
r_np[j] - r_np[k] = 0
因此点积也变为0,或者将总和分成两部分以实现相同的
for j in range(0,N):
for k in range(j+1,N):
...
for k in range(0,N):
for j in range(k+1,N):
...
两者都变得更慢。因此,整个事物用O(N ^ 2)进行扩展,我想知道是否使用某些方法(如排序或其他方法)可以摆脱循环并使其与O(N logN)进行缩放。
问题是我需要一组N~6000
三元组的单位二次运行时间,因为我有数千个要计算的总和。否则我必须尝试scipy的编织,numba,pyrex或python或完全沿着C路走......
提前感谢您的帮助!
修改
这是数据样本的样子:
# numpy arrays
x_np = np.array([0,0,1], dtype=np.float64)
N=1000
xy = np.multiply(np.subtract(np.random.rand(N,2),0.5),8)
z = np.linspace(0,40,N).reshape(N,1)
r_np = np.hstack((xy,z))
# in python format
x = (0,0,1)
r_p = r_np.tolist()
答案 0 :(得分:1)
我用它来生成测试数据:
x = (1, 2, 3)
r_p = [(i, j, k) for i in range(10) for j in range(10) for k in range(10)]
在我的计算机上,您的算法花了2.7
秒。
然后我摆脱了zip
和sum
:
for j in range(0,N):
for k in range(0,N):
s = 0
for t in range(3):
s += x[t] * (r_p[j][t] - r_p[k][t])
sum_p += cmath.exp(-1j * s)
这使其降至2.4
秒。
然后我注意到x
是不变的,所以:
x * (p - q) = x1*p1 - x1*q1 + x2*p2 - x2*q2 - ...
所以我将生成代码更改为:
x = (1, 2, 3)
r_p = [(x[0] * i, x[1] * j, x[2] * k) for i in range(10) for j in range(10) for k in range(10)]
算法:
for j in range(0,N):
for k in range(0,N):
s = 0
for t in range(3):
s += r_p[j][t] - r_p[k][t]
sum_p += cmath.exp(-1j * s)
让我到2.0
秒。
然后我意识到我们可以将其重写为:
for j in range(0,N):
for k in range(0,N):
sum_p += cmath.exp(-1j * (sum(r_p[j]) - sum(r_p[k])))
令人惊讶的是,让我到1.1
秒,我无法解释 - 可能还有一些缓存?
无论如何,无论是否缓存,您都可以预先计算三元组的总和,然后您就不必依赖缓存机制了。我做到了:
sums = [sum(a) for a in r_p]
sum_p = 0
N = len(r_p)
start = time.clock()
for j in range(0,N):
for k in range(0,N):
sum_p += cmath.exp(-1j * (sums[j] - sums[k]))
让我到0.73
秒。
我希望这足够好了!
<强>更新强>
这是0.01
秒左右的一个for循环。它似乎在数学上是合理的,但是它给出的结果略有不同,我猜这是由于精度问题。我不知道如何解决这些问题,但我想我会发布它,以防您遇到精确问题或有人知道如何修复它们。
考虑到我使用的exp
次调用次数少于初始代码,请考虑这可能是更正确的版本,而您的初始方法是精确问题。
sums = [sum(a) for a in r_p]
e_denom = sum([cmath.exp(1j * p) for p in sums])
sum_p = 0
N = len(r_p)
start = time.clock()
for j in range(0,N):
sum_p += e_denom * cmath.exp(-1j * sums[j])
print(sum_p)
end = time.clock()
print(end - start)
更新2:
相同,除了乘法较少且函数调用sum
:
sum_p = e_denom * sum([np.exp(-1j * p) for p in sums])
答案 1 :(得分:1)
这个双循环是numpy
中的时间杀手。如果使用矢量化数组操作,评估将减少到一秒钟。
In [1764]: sum_np=0
In [1765]: for j in range(0,N):
for k in range(0,N):
sum_np += np.exp(-1j * np.inner(x_np,(r_np[j] - r_np[k])))
In [1766]: sum_np
Out[1766]: (2116.3316526447466-1.0796252780664872e-11j)
In [1767]: np.exp(-1j * np.inner(x_np, (r_np[:N,None,:]-r_np[None,:N,:]))).sum((0,1))
Out[1767]: (2116.3316526447466-1.0796252780664872e-11j)
时序:
In [1768]: timeit np.exp(-1j * np.inner(x_np, (r_np[:N,None,:]-r_np[None,:N,:]))).sum((0,1))
1 loops, best of 3: 506 ms per loop
In [1769]: %%timeit
sum_np=0
for j in range(0,N):
for k in range(0,N):
sum_np += np.exp(-1j * np.inner(x_np,(r_np[j] - r_np[k])))
1 loops, best of 3: 12.9 s per loop
将np.inner
替换为np.einsum
,将时间减少20%
np.exp(-1j * np.einsum('k,ijk', x_np, r_np[:N,None,:]-r_np[None,:N,:])).sum((0,1))
答案 2 :(得分:0)
好的伙计们,非常感谢你的帮助。 IVlads使用身份sum_j sum_k a[j]*a[k] = sum_j a[j] * sum_k a[k]
的最后一个代码产生了最大的不同。现在,这也会小于O(N ^ 2)。
在总和之前对点积进行预先计算使得hpaulj的numpy建议完全相同:
sum_np = 0
dotprods = np.inner(q_np,r_np)
sum_rkexp = np.exp(1j * dotprods).sum()
sum_np = sum_rkexp * np.exp(-1j * dotprods).sum()
两者都有关于0.0003s
的运行时。然而,我发现了另外一件事,它增加了约50%,而不是计算指数两倍,我把总和中的复共轭:
sum_np = 0
dotprods = np.inner(q_np,r_np)
rkexp = np.exp(1j * dotprods)
sum_rkexp = rkexp.sum()
sum_np = sum_rkexp * np.conj(rkexp).sum()
在0.0002s
附近运行。在我使用~4s
进行非矢量化numpy的第一次尝试时,这是一个大约2*10^4
的加速,而对于N~6000
的'{real}'数组来说,125s
的运行速度大约为0.0005s
我现在得到2.5*10^5
,这是一个惊人的加速{{1}}。非常感谢,IVlad和hpaulj,在最后一天学到了很多东西:)
附:我很惊讶你们用我花了半天才跟进的东西回答的速度有多快;)