定制numpy向量化操作的效率问题

时间:2018-09-24 04:02:02

标签: python numpy vectorization

我有下面给出的python函数:

def myfun(x):
    if x > 0:
        return 0
    else:
        return np.exp(x)

其中npnumpy库。我想将函数向量化为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一样高效?

3 个答案:

答案 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) )。