在MATLAB和數值計算的世界,for循環被剪掉,而向量為王。
我最喜歡的向量化例子是,一位同事在寫了一篇非常酷的論文,對其中所涉及的大量計算做了腳註,與我分享了他的lorenz96代碼。其中,內部的向量化速度比沒有向量化快了4倍。
現在,快速向量代碼使機器學習成為可能。例如QR分解,雖然我還沒做過,但我確信現在我可以用MATLAB或Numpy或Julia編寫。
我在MassMutual做的很多工作基本上都是數值計算,一條耗時數小時與耗時數秒的管道之間的差異巨大。秒意味著我們可以迭代,嘗試更多的選項。不過,為了靈活性,很多數值代碼都是用純Python(沒有Cython,沒有Numba)編寫的。我要說這是個壞想法!下面是一封同事的轉述郵件:
在偽代碼中,這是幾個月前我遇到的“精算”編碼難題:
EOM = 0 for months in years: PREM = 50 BOM = EOM + PREM WIT = 5 EOM = BOM – WIT
一個簡單的例子,但是我認為顯示了BOM/EOM的相互依賴性(還有一些其他變量具有相似的關係)。你不能在不知道EOM的情況下對BOM進行向量化,而且在知道BOM之前也不能對EOM進行向量化。如果WIT>0,PREM=0。基本上會出現很多相互依賴的情況。現在很多函數都不容易出現向量化。
好吧,我可以向量化這個,我做到了。以下是Python中的非向量化版本:
<code>import
numpy as np
years
=10
bom
=np.zeros(years*12)
eom
=np.zeros(years*12)
for
month in range(1, years*12):
prem
=50
=eom[month-1] + prem
wit
=5
=bom[month] - wit
/<code>
這是向量化版本:
<code>import
numpy as np
years
=10
prem
=50
wit
=5
eom
=np.arange(years*12)*prem - np.arange(years*12)*wit
bom
=eom + np.arange(years*12)*wit
/<code>
我還通過使用一系列字典來編寫for循環:
<code>years =10
prem =50
wit =5
result = [{'bom'
:0
,'eom'
:0
}]for
monthin
range(1
, years*12
):inner
= {}inner
.update({'bom'
: result[month-1
]['eom'
] + prem})inner
.update({'eom'
:inner
['bom'
] - wit}) result.append(inner
) /<code>
上面的這個返回一個不同類型的東西,一個dict列表…而不是兩個數組。
我們還可以導入Pandas來填充上述三個結果的結果(因此它們是一致的輸出,我們可以保存到excel中,等等)。如果加載了Pandas,則可以使用空數據幀進行迭代,因此還有一個選項:
<code>import
numpyas
npimport
pandasas
pd years =10
prem =50
wit =5
df = pd.DataFrame(data
={'bom'
: np.zeros(years*12
),'eom'
: np.zeros(years*12
)})for
i, rowin
df.iterrows():if
i >0
: row.bom = df.loc[i-1
,'eom'
] row.eom = row.bom - wit /<code>
對於所有這些類型的迭代,以及返回數據幀作為結果的選項,我們得到的結果是:
Cython 和Numba
我還添加了一些Cython版本的代碼,說明使用C可以在不使用numpy的情況下獲得向量化的性能,這確實可能在可讀性還有速度之間達到最佳平衡(保持for循環!)。
Numba也可以加速(它可能和Cython/Vectorized Numpy一樣快)。在這兩種情況下(Cython/Numba),你必須小心使用哪些數據類型(因為沒有dicts或pandas!)。我認為,如果你對如何集成Cython+Numpy循環更聰明的話,它將有可能使Cython+Numpy循環與向量化Numpy一樣快。
所有代碼,包括Cython,都可以在這裡找到:
https://github.com/andyreagan/vectorizing-matters。