问题描述
我有下面给出的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)
1.07 ?s ± 24.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
np.exp(x)
的运行时间为1.07 ?s ± 24.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
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)
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
一样有效?
1楼
使用np.where
:
>>> 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)
为了进行比较,您的矢量化函数在我的计算机上运行大约30微秒。
至于为什么运行速度较慢,它比np.exp
复杂得多。
它进行了大量的类型推导,广播,并且可能对实际方法进行了多次调用。
这大部分发生在Python本身,而对np.exp
(以及此处的np.where
版本)的调用中几乎所有内容都在C中。
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))
首先为所有arr
计算np.exp(arr)
(这是正常的Python评估顺序),然??后执行where
选择。
通过这个exp
没什么大不了的,但是使用log
可能会出现问题。
3楼
只是在框外思考,如何实现一个函数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()
使用整数索引而不是布尔掩码,并且合理地接近np.exp()
速度,因此np.where()
变得效率np.exp()
。
编辑
同样, np.where()
版本( bnaeker_exp()
)的性能确实取决于实际满足条件的数组元素的数量。
如果它们都不起作用(例如,当您在x = np.random.rand(100)
上进行测试时),则这x = np.random.rand(100)
数组乘法版本( piecewise_exp()
)要快一些( 128 ?s ± 3.26 ?s per loop (mean ± std. dev. of 7 runs, 10000 loops each)
在我的机器上进行128 ?s ± 3.26 ?s per loop (mean ± std. dev. of 7 runs, 10000 loops each)
,其中n = 100000
。