当前位置: 代码迷 >> python >> 定制numpy向量化操作的效率问题 编辑
  详细解决方案

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

热度:102   发布时间:2023-07-14 08:42:53.0

我有下面给出的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)

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一样有效?

使用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中。

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可能会出现问题。

只是在框外思考,如何实现一个函数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