包装np.arrays __pow__方法

时间:2015-10-02 09:10:21

标签: python performance numpy

我只是在重新审视我的一些代码以改善性能,并对一些奇怪的事情感到困惑:

a = np.linspace(10,1000,1000000).reshape(1000,1000)

%timeit np.square(a)
100 loops, best of 3: 8.07 ms per loop

%timeit a*a
100 loops, best of 3: 8.18 ms per loop

%timeit a**2
100 loops, best of 3: 8.32 ms per loop

好吧,在使用power-operator(**)时似乎有一些开销,但是看起来它们看起来完全相同(我猜NumPy正在这样做)但是后来很奇怪:

In [46]: %timeit np.power(a, 2)
10 loops, best of 3: 121 ms per loop

所以没有问题,但是对于魔法战队的后备而言似乎有点不一致,但对于UFUNC则没有。但后来我感兴趣,因为我经常使用第三种力量:

%timeit a*a*a
100 loops, best of 3: 18.1 ms per loop

%timeit a**3
10 loops, best of 3: 121 ms per loop

%timeit np.power(a, 3)
10 loops, best of 3: 121 ms per loop

在第三种力量中似乎没有“捷径”,UFUNC和'magic-pow'的工作方式相同(至少在性能方面)。

但是那不是那么好,因为我想要在我的代码中使用幂的一致方法,而我不太确定如何包装numpy的__pow__

所以为了达到目的,我的问题是:

有没有办法可以包含numpys __pow__方法?因为我想在我的脚本中写一致的写作方式,而不是写a**2和另一个地方power(a, 3)。简单地写a**3,并将其重定向到我的幂函数,将是首选(但为此我需要以某种方式包装ndarrays __pow__或?)。 目前我正在使用一个快捷方式但不是那么漂亮(我甚至必须声明exponent == 2 case,因为np.power在那里执行不是最优的):

def power(array, exponent):
    if exponent == 2: #catch this, or it calls the slow np.power(array, exponent)
        return np.square(array)
    if exponent == 3:
        return array * array * array
    #As soon as np.cbrt is avaiable catch the exponent 4/3 here too
    return np.power(array, exponent) 

%timeit power(a, 3)
100 loops, best of 3: 17.8 ms per loop
%timeit a**3
10 loops, best of 3: 121 ms per loop

我正在使用NumPy v1.9.3,我不想仅仅为np.ndarray方法包装__pow__。 : - )

编辑:我重写了我的问题部分。澄清一下:我不是在问NumPy为什么这样做 - 这只是为了解释我为什么提出这个问题。

2 个答案:

答案 0 :(得分:3)

这是一个很好的捕捉。我也很想知道为什么会这样。但要简短而简洁地回答这个问题,我会这样做:

def mypower(array, exponent):
    return reduce(lambda x,y: x*y, [array for _ in range(exponent)])


%timeit mypower(a,2)
100 loops, best of 3: 3.68 ms per loop

%timeit mypower(a,3)
100 loops, best of 3: 8.09 ms per loop

%timeit mypower(a,4)
100 loops, best of 3: 12.6 ms per loop

很明显,开销随着指数的增加而增加,但是对于低指数来说,优于10倍的时间。

注意这与原始的numpy实现不同,后者不是特定于数字指数并且支持指数数组作为第二个参数(check it out here)。

重载运算符

执行所需操作的方法是子类化ndarray并使用视图。请参阅以下示例:

import numexpr
import numpy as np
​
class MyArray(np.ndarray):
    def __pow__(self, other):
        return reduce(lambda x,y: x*y, [self for _ in range(other)])
​
class NumExprArray(np.ndarray):
    def __pow__(self, other):
        return numexpr.evaluate("self**%f" % other)
        #This implies extra overhead, is as much as 4x slower:
        #return numexpr.evaluate("self**other")

a = np.linspace(10,1000,1000000).reshape(1000,1000).view(MyArray)
na = np.linspace(10,1000,1000000).reshape(1000,1000).view(NumExprArray)
​
%timeit a**2
1000 loops, best of 3: 1.2 ms per loop

%timeit na**2
1000 loops, best of 3: 1.14 ms per loop

%timeit a**3
100 loops, best of 3: 4.69 ms per loop

%timeit na**3
100 loops, best of 3: 2.36 ms per loop

%timeit a**4
100 loops, best of 3: 6.59 ms per loop

%timeit na**4
100 loops, best of 3: 2.4 ms per loop

有关此方法的详细信息,请按此link进行操作。另一种方法是使用custom infix operator,但出于可读性目的不太好。可以看出,numexpr应该是最佳选择。

答案 1 :(得分:3)

如果我正确读取source,当numpy执行电源时,它会检查指数的数值是否是特殊情况之一(-0.5,0,0.5,1和2) )。如果是,则使用特殊例程完成操作。指数的所有其他数值都被认为是“通用”,并且将被输入到通用幂函数中,这可能很慢(特别是如果指数被提升为浮点类型,但我不确定这是否是案例a ** 3)。