Python資料預處理:Dask和Numba並行化加速!

趙鈺瑩發表於2018-06-06

   如果你善於使用Pandas變換資料、建立特徵以及清洗資料等,那麼你就能夠輕鬆地使用Dask和Numba並行加速你的工作。單純從速度上比較,Dask完勝Python,而Numba打敗Dask,那麼Numba+Dask基本上算是無敵的存在。

  將數值計算分成Numba sub-function和使用Dask map_partition+apply,而不是使用Pandas。對於100萬行資料,使用Pandas方法和混合數值計算建立新特徵的速度比使用Numba+Dask方法的速度要慢許多倍。

  Python:60.9x | Dask:8.4x | Numba:5.8x |Numba+Dask:1x

Python資料預處理:Dask和Numba並行化加速!

  作為舊金山大學的一名資料科學碩士,會經常跟資料打交道。使用Apply函式是我用來建立新特徵或清理資料的眾多技巧之一。現在,我只是一名資料科學家,而不是電腦科學方面的專家,但我是一個喜歡搗鼓並使得程式碼執行更快的程式設計師。現在,我將會分享我在並行應用上的經驗。

  大多Python愛好者可能瞭解Python實現的全域性直譯器鎖(GIL),GIL會佔用計算機中所有的CPU效能。更糟糕的是,我們主要的資料處理包,比如Pandas,很少能實現並行處理程式碼。

  Apply函式vs Multiprocessing.map

Python資料預處理:Dask和Numba並行化加速!

  Tidyverse已經為處理資料做了一些美好的事情,Plyr是我最喜愛的資料包之一,它允許R語言使用者輕鬆地並行化他們的資料應用。Hadley Wickham說過:

  “plyr是一套處理一組問題的工具:需要把一個大的資料結構分解成一些均勻的資料塊,之後對每一資料塊應用一個函式,最後將所有結果組合在一起。”

  對於Python而言,我希望有類似於plyr這樣的資料包可供使用。然而,目前這樣的資料包還不存在,但我可以使用並行資料包構成一個簡單的解決方案。

  Dask

Python資料預處理:Dask和Numba並行化加速!

  之前在Spark上花費了一些時間,因此當我開始使用Dask時,還是比較容易地掌握其重點內容。Dask被設計成能夠在多核CPU上並行處理任務,此外也借鑑了許多Pandas的語法規則。

  現在開始本文所舉例子。對於最近的資料挑戰而言,我試圖獲取一個外部資料來源(包含許多地理編碼點),並將其與要分析的一大堆街區相匹配。在計算歐幾里得距離的同時,使用最大啟發式將最大值分配給一個街區。

Python資料預處理:Dask和Numba並行化加速!

Python資料預處理:Dask和Numba並行化加速!

  二者看起來很相似,apply核心語句是map_partitions,最後有一個compute()語句。此外,不得不對npartitions初始化。 分割槽的工作原理就是將Pandas資料幀劃分成塊,對於我的電腦而言,配置是6核-12執行緒,我只需告訴它使用的是12分割槽,Dask就會完成剩下的工作。

  接下來,將map_partitions的lambda函式應用於每個分割槽。由於許多資料處理程式碼都是獨立地執行,所以不必過多地擔心這些操作的順序問題。最後,compute()函式告訴Dask來處理剩餘的事情,並把最終計算結果反饋給我。在這裡,compute()呼叫Dask將apply適用於每個分割槽,並使其並行處理。

  由於我透過迭代行來生成一個新佇列(特徵),而Dask apply只在列上起作用,因此我沒有使用Dask apply,以下是Dask程式:

Python資料預處理:Dask和Numba並行化加速!

  由於我是根據一些簡單的線性運算(基本上是勾股定理)對資料進行分類,所以認為使用類似下面的Python程式碼會執行得更快一些。

Python資料預處理:Dask和Numba並行化加速!

Python資料預處理:Dask和Numba並行化加速!

  Broadcasting用以描述Numpy中對兩個形狀不同的矩陣進行數學計算的處理機制。假設我有一個陣列,我會透過迭代並逐個變換每個單元格來改變它

Python資料預處理:Dask和Numba並行化加速!

  相反,我完全可以跳過for迴圈,並對整個陣列執行操作。Numpy與broadcasting混合使用,用來執行元素智慧乘積(對位相乘)。

Python資料預處理:Dask和Numba並行化加速!

  Broadcasting可以實現更多的功能,現在看看骨架程式碼:

Python資料預處理:Dask和Numba並行化加速!

  從本質上講,程式碼的功能是改變陣列。好的一方面是執行很快,甚至能和Dask並行處理速度比較。其次,如果使用的是最基本的Numpy和Python,那麼就可以及時編譯任何函式。壞的一面在於它只適合Numpy和簡單Python語法。我不得不把所有的數值計算從我的函式轉換成子函式,但其計算速度會增加得非常快。

  將其一起使用

  簡單地使用map_partition()就可以將Numba函式與Dask結合在一起,如果並行操作和broadcasting能夠密切合作以加快執行速度,那麼對於大資料集而言,將會看到其執行速度得到大幅提升。

Python資料預處理:Dask和Numba並行化加速!

Python資料預處理:Dask和Numba並行化加速!

  上面的第一張圖表明,沒有broadcasting的線性計算其表現不佳,並行處理和Dask對速度提升也有效果。此外,可以明顯地發現,Dask和Numba組合的效能優於其它方法。

  上面的第二張圖稍微有些複雜,其橫座標是對行數取對數。從第二張圖可以發現,對於1k到10k這樣小的資料集,單獨使用Numba的效能要比聯合使用Numba+Dask的效能更好,儘管在大資料集上Numba+Dask的效能非常好。

  最佳化

  為了能夠使用Numba編譯JIT,我重寫了函式以更好地利用broadcasting。之後,重新執行這些函式後發現,平均而言,對於相同的程式碼,JIT的執行速度大約快了24%。

Python資料預處理:Dask和Numba並行化加速!

  可以肯定的說,一定有進一步的最佳化方法使得執行速度更快,但目前沒有發現。Dask是一個非常友好的工具,本文使用Dask+Numba實現的最好成果是提升執行速度60倍。如果你知道其它的提升執行速度的技巧,歡迎在留言區分享。

  作者資訊

  Ernest Kim,舊金山大學碩士生,專注於機器學習、資料科學。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31077337/viewspace-2155744/,如需轉載,請註明出處,否則將追究法律責任。

相關文章