我有一个需要至少评估1e6次的功能。它只需要两个数组作为输入。
import numpy as np
np.random.seed(1)
res = np.random.rand(0,1,1000)
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))
目前,
%timeit f1(res,np.sqrt(res))
在26 usec时钟。有没有办法让我可以更快地进行评估调用/执行时间?
任何手段都可以;例如不使用lambda函数,使用本机数学而不是numpy,或任何涉及使用python中实现的更快数学运算操作方程式的技巧。
编辑:
我犯了一个生成空res
数组的愚蠢错误。所以它应该是
res=np.random.rand(1000)
此外,第二个数组np.sqrt(res)
实际上独立于res
,并且通常由远小于res
的值组成,但我使用sqrt(res)
仅用于说明。
所以更清楚地重写代码
import numpy as np
np.random.seed(1)
res1 = np.random.rand(1000)
res2 = np.sqrt(res) #not true in general but res2<res1
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))
现在%f1(res1,res2)
在我的机器中以60 usec的速度计时。
答案 0 :(得分:2)
作为参考,[setVisibleMapRect: edgePadding:]
你定义的方式给了我:
10000次循环,最佳3:每循环20.9μs
一种简单的优化,可以避免每次计算f1
:
np.log(2*np.pi)
将log2pi = np.log(2*np.pi)
f2 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + log2pi + (r/sig)**2))
输出减少约15%:
100000个循环,最佳3:每循环17.8μs
此外,计算timeit
通常比x*x
更快。使用这种方法,您也可以避免x**2
操作。
2*
100000个循环,最佳3:每循环15μs
此外,根据您使用参数def f3(r, sig):
sig2 = sig * sig
return -0.5*(np.sum(np.log(sig2) + log2pi + (r*r/sig2)))
调用此函数的频率,您绝对可以考虑将其简化为:
res, np.sqrt(res)
您可以检查f4 = lambda a: -0.5*np.sum(np.log(a) + log2pi + a)
并且它的速度也明显更快(约44%)。
100000个循环,最佳3:每循环11.7μs
答案 1 :(得分:1)
实际上,您可以跳过np.sum()
,因为您通过应用+
运算符获得的单个参数调用它。此外,np.log(2*np.pi)
是常量并且不依赖于lambda args,因此您可以在lambda之外计算并重用。此外,这可能不会有太大变化,但请检查a**2
是否比a*a
更快或更慢;如果后者更快,请计算d = r/sig
一次并使用d*d
代替(r/sig)**2
。您还可以检查0.5*x
是否比x/2.0
更快或更慢。这很多,但它可能会让你加速一两次。
现在,有些数学。 根据上述建议,您得到:
log_of_pi = np.log(2*np.pi)
f1 = lambda r, sig: -0.5*(2*np.log(sig) + log_of_pi + (r/sig)**2))
f1(res,np.sqrt(res))
所以基本上,你计算:
c1*(2*log(sqrt(r)) + c2 + (r/sqrt(r))**2)
首先:
log(sqrt(r)) = log(r**(1/2)) = 1/2*log(r)
此外:
(r/sqrt(r))**2 = sqrt(r)**2 = abs(r)
基本上,你正试图计算:
c1*(2*log(sqrt(r)) + c2 + (r/sqrt(r))**2) = c1*(2*1/2*log(r) + c2 + abs(r)) = c1*(log(r) + c2 + abs(r))
所以,你可以跳过昂贵的除法平方(和的最后一部分)并计算r
的平方根(在调用f1
之前完成)。
当然,这是假设您始终将sqrt(r)
用作sig
。
PS。尊重grovina,在我之前15秒,answered有同样的思路,并提供了测量结果。你想接受我的答案,接受另一个,因为它更完整。
答案 2 :(得分:1)
我很确定最贵的东西都是log
,以及那些你可以保存的东西(即调用一次而不是sig.size
- 在这个例子中= 1000次)。基础身份是log(a) + log(b) = log(a*b)
。给我加速三倍。
顺便说一下。我将生成空数组的random.rand(0, 1, 1000)
更改为random.rand(1000)
。我也认为sig=sqrt(res)
不是一般性的假设,所以我没有试图利用它。
我还做了一些其他的优化,但是它们的效果远不如摆脱log
那么大。 1)预计算log(2*pi)
2)在最后一个学期使用np.dot
。
正如@JPdL所指出的,这种方法很容易受到过度和下溢的影响。可以使用分块交换各种性能以防止这种情况,请参阅下面的f4. f5. f6
。
import numpy as np
np.random.seed(1)
res = np.random.rand(1000)
f1 = lambda r, sig: -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))
def f2(r, sig):
return -0.5*(np.sum(2*np.log(sig) + np.log(2*np.pi) + (r/sig)**2))
# gung ho
def f3(r, sig, precomp=np.log(2*np.pi)):
rs = r/sig
return -np.log(np.prod(sig)) - 0.5*(r.size * precomp + np.dot(rs, rs))
# chunk and hope for the best
def f4(r, sig, chnk=32, precomp=np.log(2*np.pi)):
rs = r/sig
sumlogsig = np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))
# chunk and check for extreme values
def f5(r, sig, chnk=32,
precomp=np.log(2*np.pi), precomp2=np.exp([-8, 8])):
rs = r/sig
bad = np.where((sig<precomp2[0]) | (sig>precomp2[1]))[0]
sumlogsig = np.log(sig[bad]).sum()
keep = sig[bad]
sig[bad] = 1
sumlogsig += np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
sig[bad] = keep
return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))
# chunk and try to be extra clever
def f6(r, sig, chnk=512,
precomp=np.log(2*np.pi), precomp2=np.exp(np.arange(-18, 19))):
binidx = np.digitize(sig, precomp2[1::2])<<1
rs = r/sig
sig = sig*precomp2[36 - binidx]
bad = np.where((binidx==0) | (binidx==36))[0]
sumlogsig = binidx.sum() - 18*r.size + np.log(sig[bad]).sum()
sig[bad] = 1
sumlogsig += np.log(np.multiply.reduceat(sig, np.arange(0, len(r), chnk))).sum()
return -sumlogsig - 0.5*(r.size * precomp + np.dot(rs, rs))
from timeit import timeit
sr = np.sqrt(res)
print(timeit('f1(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f1':f1}))
print(timeit('f2(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f2':f2}))
print(timeit('f3(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f3':f3}))
print(timeit('f4(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f4':f4}))
print(timeit('f5(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f5':f5}))
print(timeit('f6(res,sr)', number=100, globals={'res':res, 'np':np, 'sr':sr, 'f6':f6}))
print(f1(res,np.sqrt(res)))
print(f2(res,np.sqrt(res)))
print(f3(res,np.sqrt(res)))
print(f4(res,np.sqrt(res)))
print(f5(res,np.sqrt(res)))
print(f6(res,np.sqrt(res)))
示例输出:
0.004246247990522534
0.00418912700843066
0.0011273059935774654
0.0013386670034378767
0.0022679700050503016
0.004274581006029621
-662.250886322
-662.250886322
-662.250886322
-662.250886322
-662.250886322
-662.250886322
答案 3 :(得分:1)
您正在使用lambda进行隐式复制。您可以尽可能使用函数的就地变体来提升性能。 http://ipython-books.github.io/featured-01/
def f2(r, sig):
x = np.log(sig)
x *= 2 # in-place operation
x += np.log(2*np.pi) # in-place operation
y = r / sig
y **= 2 # in-place operation
x += y # in-place operation
return -0.5 * x.sum()
另外,检查
的输出import numpy.distutils.system_info as sysinfo
sysinfo.show_all()
...
openblas_lapack_info:
libraries openblas not found in ['C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python35-32\\lib', 'C:\\', 'C:\\Users\\User\\AppData\\Local\\Programs\\Python\\Python35-32\\libs']
NOT AVAILABLE
如果blas,lapack和atlas是NOT AVAILABLE
那么你就没有从numpy中获得最大收益。不是由一个长镜头。这些库使numpy能够使用SIMD
操作(单指令多数据 - 一种并行化形式)。