2014-01-14 64 views
9

我在numpy中編寫了一些中等性能的關鍵代碼。 這段代碼將在最內循環中運行時間以小時爲單位進行計算。 快速計算表明,在計算的某些變體中,此代碼將執行類似10^12次的操作。Numpy Pure函數用於性能,緩存

所以函數是計算S形(X)和另一個來計算它的導數(梯度)。 乙狀結腸具有用於
Y = S形(x)中,DY/DX = Y(1-y)的
的Python爲numpy的這看起來像屬性:

sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x))) 
grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x))) 

如可以看到的,這兩個函數是純粹的(沒有副作用),所以它們是用於記憶的理想候選者,至少在短期內, ,我有一些憂慮緩存每個單一調用S形有史以來:存儲10^12浮動將需要幾個兆兆字節的RAM。

有沒有好的方法來優化呢?
請問python會認爲這些是純函數,並根據需要緩存給我?
我擔心什麼都沒有?

+7

注意,'1 /(1 + NP。 exp(-x))'已經接受numpy數組作爲輸入並且速度非常快(我的猜測是memoization根本沒有幫助)。通過使用'vectorize',你可以讓它慢得多,因爲它使用慢for循環來實現。 –

+0

x的大小是多少? – M4rtini

+0

@ M4rtini,各不相同,但我一直假設我的規則計算,x通常是長度爲100的向量。 –

回答

30

這些函數已經存在於scipy中。 sigmoid函數可以用作scipy.special.expit

In [36]: from scipy.special import expit 

比較expit到矢量雙曲線函數:

In [38]: x = np.linspace(-6, 6, 1001) 

In [39]: %timeit y = sigmoid(x) 
100 loops, best of 3: 2.4 ms per loop 

In [40]: %timeit y = expit(x) 
10000 loops, best of 3: 20.6 µs per loop 

expit也比自己實施公式更快:

In [41]: %timeit y = 1.0/(1.0 + np.exp(-x)) 
10000 loops, best of 3: 27 µs per loop 

物流配送的CDF是雙曲線函數。它可作爲scipy.stats.logisticcdf方法提供,但cdf最終會調用expit,因此使用該方法沒有意義。可以使用pdf方法來計算S形函數,或具有較少的開銷的_pdf方法的衍生物,但「滾動自己的」更快:

In [44]: def sigmoid_grad(x): 
    ....:  ex = np.exp(-x) 
    ....:  y = ex/(1 + ex)**2 
    ....:  return y 

時序(x具有長度1001):

In [45]: from scipy.stats import logistic 

In [46]: %timeit y = logistic._pdf(x) 
10000 loops, best of 3: 73.8 µs per loop 

In [47]: %timeit y = sigmoid_grad(x) 
10000 loops, best of 3: 29.7 µs per loop 

如果您打算使用遠離尾部的值,請注意您的實現。指數函數可以很容易地溢出。 logistic._cdf比我快實現sigmoid_grad更穩健一點:

In [60]: sigmoid_grad(-500) 
/home/warren/anaconda/bin/ipython:3: RuntimeWarning: overflow encountered in double_scalars 
    import sys 
Out[60]: 0.0 

In [61]: logistic._pdf(-500) 
Out[61]: 7.1245764067412855e-218 

使用sech**21/cosh**2)的實現比上述sigmoid_grad慢一點:

In [101]: def sigmoid_grad_sech2(x): 
    .....:  y = (0.5/np.cosh(0.5*x))**2 
    .....:  return y 
    .....: 

In [102]: %timeit y = sigmoid_grad_sech2(x) 
10000 loops, best of 3: 34 µs per loop 

但它更好地處理尾巴:

In [103]: sigmoid_grad_sech2(-500) 
Out[103]: 7.1245764067412855e-218 

In [104]: sigmoid_grad_sech2(500) 
Out[104]: 7.1245764067412855e-218 
+0

當你說_「你可以使用pdf方法來計算sigmoid函數的派生值,或者_pdf方法的開銷更少」_,那麼_overhead_的意思是什麼?你是否認爲受保護的方法更快,因爲它通過較少的代碼? – Nicholas

+1

@Nicholas是的,'._pdf()'對參數的錯誤檢查較少。它也不使用分佈式的'loc'(location)和'scale'參數 - 這些參數在'.pdf()'方法中處理。 '.pdf()'最終調用'._pdf()'來做實際的計算。 –

1

如果您正在尋找此流程的備忘,我會將該代碼包裝在一個函數中,並使用functools.lru_cache(maxsize=n)進行裝飾。嘗試使用maxsize值來爲您的應用程序找到合適的大小。爲了獲得最佳效果,請使用兩個冪的maxsize參數。

from functools import lru_cache 

lru_cache(maxsize=8096) 
def sigmoids(x): 
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x))) 
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x))) 
    return sigmoid, grad_sigmoid 

如果你在2.7(我希望你是因爲你使用numpy的),你可以看看https://pypi.python.org/pypi/repoze.lru/用相同的語法記憶化圖書館。

您可以通過畫中畫安裝:pip install repoze.lru

from repoze.lru import lru_cache 

lru_cache(maxsize=8096) 
def sigmoids(x): 
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x))) 
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x))) 
    return sigmoid, grad_sigmoid 
+0

我想補充說functools.lru_cache是​​在Python 3.2中引入的:) –

+0

良好的調用 - 我已經添加了2.7替代我的答案。 –

5

就擴大對我的評論,在這裏是通過vectorize你乙狀結腸之間的比較,並使用numpy的直接:

In [1]: x = np.random.normal(size=10000) 

In [2]: sigmoid = np.vectorize(lambda x: 1.0/(1.0 + np.exp(-x))) 

In [3]: %timeit sigmoid(x) 
10 loops, best of 3: 63.3 ms per loop 

In [4]: %timeit 1.0/(1.0 + np.exp(-x)) 
1000 loops, best of 3: 250 us per loop 

正如你所看到的,vectorize不僅使得它慢得多,事實上,你可以在250微秒(即每個25納秒)計算10000個sigmoid。 Python中的單個字典查找比這更慢,更不用說所有其他代碼來實現記憶。

優化這是我能想到的唯一的辦法就是寫numpy的,這基本上將實施下運行這樣一個乙狀結腸ufunc,你就不必做每一項操作乙狀結腸到整個數組,儘管numpy確實很快。

+0

使用現有代碼對RBM進行測試: 矢量化:1循環,最好3:每循環5.44秒 非變元:1循環,最好3:每循環4.75秒 非矢量化,而不使用lambda :1個循環,最好3個循環:每個循環4.53秒 由於時間不是非常合適的數字,所以它只做了一個循環,但是表明了我的想法。 因此,一個堅實的加速這樣一個小的變化。 –

0

大部分我同意Warren Weckesser和他的回答r above。 但對於S形的衍生物可以使用下列:

In [002]: def sg(x): 
    ...: s = scipy.special.expit(x) 
    ...: return s * (1.0 - s) 

時序:

In [003]: %timeit y = logistic._pdf(x) 
10000 loops, best of 3: 45 µs per loop 

In [004]: %timeit y = sg(x) 
10000 loops, best of 3: 20.4 µs per loop 

唯一的問題是精度:

In [005]: sg(37) 
Out[005]: 0.0 

In [006]: logistic._pdf(37) 
Out[006]: 8.5330476257440658e-17