Numpy零排数组索引/广播

时间:2013-07-01 21:33:57

标签: python numpy

我正在尝试编写一个支持广播的功能,并且同时快速。然而,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 Loop

最后,最天真的方式是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)

还有其他建议吗?

2 个答案:

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