2015-10-02 34 views
5

我只是重新審視我的一些代碼,以提高性能和stumpled了一些奇怪的事情:包裝np.arrays __pow__方法

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 

好它似乎有使用電運營商時,一些開銷(**)但除此之外,他們似乎是相同的(我猜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__或?)。 目前我使用的快捷方式,但是這不是那個漂亮(我甚至不得不宣佈指數== 2的情況下,因爲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,我不只是爲了包裝__pow__方法而子類np.ndarray。 :-)

編輯:我重寫了我得到我的問題的部分。澄清一下:我不是在問爲什麼NumPy會這樣做 - 這只是爲了解釋爲什麼我會問這個問題。

+0

你真的想微方案優化電源操作員,因爲它快一點?這似乎真的被過度優化,特別是如果它意味着引入其他功能。只要一直使用'** n',並且不用擔心那麼小的時間差異 – poke

+2

np.all(a ** 3 - a \ * a \ * a < 1e-6) ->是的,所以我認爲它是一樣的,對於你的numpy必須聲明它是一個矩陣來觸發矩陣乘法,是的問題是如何包裝\\ __ pow__。時間差異對我來說很重要,因爲我處理了很多真正的大數組(1000 1000×2000圖像)。對於編輯,我很新,沒有正確的文本格式 – MSeifert

+2

不可能修改ndarray的'__pow__'方法而不使用子類。您可以(如您所建議的)編寫另一個函數。如果你關心性能,你可以使用像[numexpr](https://github.com/pydata/numexpr)這樣的庫,它具有優化的功能函數。例如,'numexpr.evaluate(「a ** 3 「)'比」a * a * a「大約快20倍。 –

回答

2

這是一個很好的結果。我也想知道爲什麼是這種行爲。但要簡明扼要回答這個問題,我只想做:

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 

Obsviouly與指數的開銷增加,但對於那些低優於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,但出於可讀性目的不太好。正如人們所看到的,數字應該是要走的路。

+0

建議的numexpr庫的時間,您的回答很好,但我想以某種方式包裝np.ndarray \ _ \ _ pow__函數(如果可以的話) ),所以我可以簡單地寫一個\ * \ * x,我認爲這是讀取代碼最好的方法。我正在利用關於我的數據的一些假設(實數作爲數據,只有2,3和4/3作爲指數)可能意味着它可能會在別的地方打破,但因爲這僅僅是我個人使用,這並不重要(希望) – MSeifert

+0

我更新了我認爲是做你想做的事情的唯一方法的答案... – rll

+0

通過平方運算求冪對於更大的冪運算會更快,numexpr似乎可以做到這一點,正如你可以在** 3和** 4 – jtaylor

2

如果我正確閱讀source,當numpy執行電源時,它會檢查指數的數值是否是特殊情況之一(-0.5,0,0.5,1和2)。如果是這樣,則使用特殊例程完成操作。指數的所有其他數值都被認爲是「一般」的,並且將被饋送到通用冪函數中,這可能會很慢(特別是如果指數被提升爲浮點類型,但我不確定這是否是與a ** 3的情況)。

+0

對不起,也許我在發佈我的問題時做了錯誤的事情,但我並不是問爲什麼numpy會這樣做。根據我的經驗,numpy儘可能地完成大部分事情。我正在問一個重定向Python調用的方法numpy.ndarray。\ _ \ _ pow__,當調用一個**時,我的權力函數,它捕獲我提升的案例,並優化它們,如果它不能拋出它們回到numpy。 – MSeifert