我有下面给出的python函数:
def myfun(x):
if x > 0:
return 0
else:
return np.exp(x)
其中np
是numpy
库。我想将函数向量化为numpy,所以我使用:
vec_myfun = np.vectorize(myfun)
我做了一个测试以评估效率。首先,我生成一个包含100个随机数的向量:
x = np.random.randn(100)
然后我运行以下代码来获取运行时:
%timeit np.exp(x)
%timeit vec_myfun(x)
np.exp(x)
的运行时为1.07 µs ± 24.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
。
vec_myfun(x)
的运行时为71.2 µs ± 1.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
我的问题是:与np.exp
相比,vec_myfun
仅需要执行一个额外的步骤来检查$ x $的值,但它的运行速度比np.exp
慢得多。有没有一种有效的方法可以对myfun
进行向量化,使其与np.exp
一样高效?
答案 0 :(得分:3)
使用QGroupBox
:
np.where
为进行比较,您的矢量化函数在我的计算机上运行大约30微秒。
关于为什么运行速度较慢,它比>>> x = np.random.rand(100,)
>>> %timeit np.exp(x)
1.22 µs ± 49.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> %timeit np.where(x > 0, 0, np.exp(x))
4.09 µs ± 282 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
复杂得多。它进行了大量的类型推导,广播,并且可能对实际方法进行了多次调用。这种情况大部分发生在Python本身,而对np.exp
(和此处的np.exp
版本)的调用中几乎所有内容都是C语言。
答案 1 :(得分:2)
ufunc
(例如np.exp
)具有一个where
参数,可以用作:
In [288]: x = np.random.randn(10)
In [289]: out=np.zeros_like(x)
In [290]: np.exp(x, out=out, where=(x<=0))
Out[290]:
array([0. , 0. , 0. , 0. , 0.09407685,
0.92458328, 0. , 0. , 0.46618914, 0. ])
In [291]: x
Out[291]:
array([ 0.37513573, 1.75273458, 0.30561659, 0.46554985, -2.3636433 ,
-0.07841215, 2.00878429, 0.58441085, -0.76316384, 0.12431333])
这实际上跳过了where
为假的计算。
相反:
np.where(arr > 0, 0, np.exp(arr))
首先为所有np.exp(arr)
计算arr
(这是正常的Python评估顺序),然后执行where
选择。有了这个exp
没什么大不了,但是有了log
可能会出问题。
答案 2 :(得分:1)
跳出框框思考,实现一个将piecewise_exp()
与np.exp()
基本相乘的函数arr < 0
怎么办?
import numpy as np
def piecewise_exp(arr):
return np.exp(arr) * (arr < 0)
编写到目前为止建议的功能代码
@np.vectorize
def myfun(x):
if x > 0:
return 0.0
else:
return np.exp(x)
def bnaeker_exp(arr):
return np.where(arr > 0, 0, np.exp(arr))
并测试所有内容是否一致:
np.random.seed(0)
# : test that the functions have the same behavior
num = 10
x = np.random.rand(num) - 0.5
print(x)
print(myfun(x))
print(piecewise_exp(x))
print(bnaeker_exp(x))
为小额投入做一些微基准测试
# : micro-benchmarks for small inputs
num = 100
x = np.random.rand(num) - 0.5
%timeit np.exp(x)
# 1.63 µs ± 45.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit myfun(x)
# 54 µs ± 967 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit bnaeker_exp(x)
# 4 µs ± 87.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit piecewise_exp(x)
# 3.38 µs ± 59.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
...以及较大的输入:
# : micro-benchmarks for larger inputs
num = 100000
x = np.random.rand(num) - 0.5
%timeit np.exp(x)
# 32.7 µs ± 1.78 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit myfun(x)
# 44.9 ms ± 1.17 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit bnaeker_exp(x)
# 481 µs ± 25.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit piecewise_exp(x)
# 149 µs ± 2.65 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
这表明piecewise_exp()
的速度比迄今为止提出的其他任何方法都要快,尤其是对于np.where()
效率较低的较大输入,因为它使用整数索引而不是布尔掩码,并且合理地采用了{{1 }}速度。
此外,np.exp()
版本(np.where()
)的性能确实取决于实际满足条件的数组元素的数量。如果它们都不起作用(例如,当您在bnaeker_exp()
上进行测试时),这比布尔数组乘法版本(x = np.random.rand(100)
)(我的机器上piecewise_exp()
上的128 µs ± 3.26 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
)。