我正在尝试编写一个支持广播的功能,并且同时快速。然而,numpy的零排数组像往常一样引起麻烦。我在谷歌上找不到任何有用的东西,或者在这里搜索。
所以,我问你。我应该如何有效地实现广播并同时处理零秩数组呢?
整个帖子变得比预期的要大,抱歉。
为了澄清我在说什么,我将举一个简单的例子:
说我想实现Heaviside step-function。即作用于实轴的函数,在负侧为0,在正侧为1,在0点从0到0的情况为0,0.5或1。
到目前为止,我发现的最有效的方法如下。它使用布尔数组作为掩码,将正确的值分配给输出向量中的相应插槽。
from numpy import *
def step_mask(x, limit=+1):
"""Heaviside step-function.
y = 0 if x < 0
y = 1 if x > 0
See below for x == 0.
Arguments:
x Evaluate the function at these points.
limit Which limit at x == 0?
limit > 0: y = 1
limit == 0: y = 0.5
limit < 0: y = 0
Return:
The values corresponding to x.
"""
b = broadcast(x, limit)
out = zeros(b.shape)
out[x>0] = 1
mask = (limit > 0) & (x == 0)
out[mask] = 1
mask = (limit == 0) & (x == 0)
out[mask] = 0.5
mask = (limit < 0) & (x == 0)
out[mask] = 0
return out
follow-the-numpy-docs 方式是在广播对象的平面迭代器上使用列表推导。但是,对于这些复杂的功能,列表推导变得绝对不可读。
def step_comprehension(x, limit=+1):
b = broadcast(x, limit)
out = empty(b.shape)
out.flat = [ ( 1 if x_ > 0 else
( 0 if x_ < 0 else
( 1 if l_ > 0 else
( 0.5 if l_ ==0 else
( 0 ))))) for x_, l_ in b ]
return out
最后,最天真的方式是for循环。它可能是最易读的选择。但是,Python for循环不是很快。因此,数字中的一个非常糟糕的主意。
def step_for(x, limit=+1):
b = broadcast(x, limit)
out = empty(b.shape)
for i, (x_, l_) in enumerate(b):
if x_ > 0:
out[i] = 1
elif x_ < 0:
out[i] = 0
elif l_ > 0:
out[i] = 1
elif l_ < 0:
out[i] = 0
else:
out[i] = 0.5
return out
首先是一个简短的测试,看输出是否正确。
>>> x = array([-1, -0.1, 0, 0.1, 1])
>>> step_mask(x, +1)
array([ 0., 0., 1., 1., 1.])
>>> step_mask(x, 0)
array([ 0. , 0. , 0.5, 1. , 1. ])
>>> step_mask(x, -1)
array([ 0., 0., 0., 1., 1.])
这是正确的,其他两个函数给出相同的输出。
效率怎么样?这些是时间:
In [45]: xl = linspace(-2, 2, 500001)
In [46]: %timeit step_mask(xl)
10 loops, best of 3: 19.5 ms per loop
In [47]: %timeit step_comprehension(xl)
1 loops, best of 3: 1.17 s per loop
In [48]: %timeit step_for(xl)
1 loops, best of 3: 1.15 s per loop
屏蔽版本的效果与预期一致。但是,我很惊讶理解与for循环处于同一水平。
但是,0级数组会造成问题。有时您想使用函数标量输入。最好不要担心将所有标量包装在至少一维数组中。
>>> step_mask(1)
Traceback (most recent call last):
File "<ipython-input-50-91c06aa4487b>", line 1, in <module>
step_mask(1)
File "script.py", line 22, in step_mask
out[x>0] = 1
IndexError: 0-d arrays can't be indexed.
>>> step_for(1)
Traceback (most recent call last):
File "<ipython-input-51-4e0de4fcb197>", line 1, in <module>
step_for(1)
File "script.py", line 55, in step_for
out[i] = 1
IndexError: 0-d arrays can't be indexed.
>>> step_comprehension(1)
array(1.0)
只有列表推导才能处理0级数组。另外两个版本 需要对0级数组进行特殊处理。
当你想为数组和标量使用相同的代码时,Numpy会有点乱。但是,我真的希望将函数作为任意输入进行处理。谁知道我想在某些时候迭代哪些参数。
实现上述功能的最佳方法是什么?有没有办法避免if scalar then
特殊情况?
我不是在寻找内置的Heaviside。这只是一个简化的例子。在我的代码中,上面的模式出现在许多地方,使参数迭代尽可能简单,而不会乱丢客户端代码和for循环或理解。
此外,我知道Cython,或编织&amp;有限公司,或直接在C中实施。但是,上面的掩盖版本的性能目前已足够。目前我想尽量保持简单。
在Ophion和DaveP之后,我改进了蒙面版本,如下所示:
def step_mask_improved(x, limit=+1):
b = np.broadcast(x, limit)
out=atleast_1d(np.zeros(b.shape))
out[np.where(x>0)]=1
zeroindices=np.where(x==0)
check=out[zeroindices]
check=np.where(limit>0,1,check)
check=np.where(limit==0,.5,check)
check=np.where(limit<0,0,check)
out[zeroindices]=check
return out.reshape(b.shape)
它与Ophium的解决方案一样快。
In [13]: %timeit step_mask(xl)
100 loops, best of 3: 11.1 ms per loop
In [14]: %timeit step_mask2(xl)
100 loops, best of 3: 9.11 ms per loop
In [15]: %timeit step_mask_improved(xl)
100 loops, best of 3: 9.13 ms per loop
但是,它可以处理零排数组,如果输入是标量,它仍会返回标量。
In [7]: step_mask_improved(1)
Out[7]: array(1.0)
还有其他建议吗?
答案 0 :(得分:1)
使用numpy.where
代替索引(其中也往往更快),例如替换:
out[mask] = 0.
使用:
out = numpy.where(mask, 0., out)
执行此操作时,您的功能最终会显示如下:
def step_mask(x, limit=+1):
mask = x > 0
out = where(mask, 1., 0.)
# Save this result to avoid re-computing
x_eq_zero = x == 0
mask = (limit > 0) & x_eq_zero
out = where(mask, 1, out)
mask = (limit == 0) & x_eq_zero
out = where(mask, .5, out)
mask = (limit < 0) & x_eq_zero
out = where(mask, 0, out)
return out
答案 1 :(得分:1)
需要考虑的是减少将对其执行第二次检查的阵列的大小。我不确定如何绕过if scalar then
陈述。
def step_mask2(x,limit=+1):
b = np.broadcast(x, limit)
out=np.zeros(b.shape)
out[np.where(x>0)]=1
zeroindices=np.where(x==0)
check=out[zeroindices]
check=np.where(limit>0,1,check)
check=np.where(limit==0,.5,check)
check=np.where(limit<0,0,check)
out[zeroindices]=check
return out
除了标量之外,还有限制吗?如果不是它可能更好地做if语句而不是np.where。
时机略好一些:
Yours took 0.0330839157104 seconds.
Mine took 0.0210998058319 seconds.
使用DaveP中的atleast1d想法进行更新:
def step_mask_improved(x, limit=+1):
b = np.broadcast(x, limit)
out=np.atleast_1d(np.zeros(b.shape))
out[np.where(x>0)]=1
zeroindices=np.where(x==0)
check=out[zeroindices]
check=np.where(limit>0,1,check)
check=np.where(limit==0,.5,check)
check=np.where(limit<0,0,check)
out[zeroindices]=check
return out
我不确定你为什么要重塑它 - 导致零排列数返回标量的原因,但如果它有问题。
return np.atleast_1d(out.reshape(b.shape))