用Dask並行化特徵工程!

格伯納發表於2018-08-20

作者:William Koehrsen

當計算非常緩慢時,需要考慮的最重要的問題是:"當前我們的瓶頸是什麼?"一旦你找出瞭解決方案,接下來的邏輯步驟就是找出如何度過瓶頸期。

用Dask並行化特徵工程!


在這裡,我們的瓶頸是沒有充分利用我們的硬體資源。例如,我們的計算機有8個核心,然而只有一個核心在執行計算。如果我們編寫的程式碼不能"照顧"到我們所現有的全部資源。那麼簡單地更換一個更大的機器——比如說擴充RAM或核心的容量——是無法解決問題的。因此,我認為最佳的解決方案是重寫程式碼,儘可能有效地利用我們現有的硬體。

在本文中,我們將看到如何重構我們的自動化特徵工程程式碼,以便在所有筆記本的核心上並行執行,從而減少我們的執行計算時間(預估可以減少八倍以上的時間)。我們將使用兩個開源庫,即用於自動特徵工程(https://medium.com/@williamkoehrsen/why-automated-feature-engineering-will-change-the-way-you-do-machine-learning-5c15bf188b96)的Featuretools(https://www.featuretools.com/)和用於Dask裡的並行處理工具(https://dask.pydata.org/),然後用它們解決實際問題。

用Dask並行化特徵工程!


文中的一些具體的解決方案僅適用於上述問題。但是其中一些通用方法你可以學習用於自己的資料集。

雖然在這裡我們將堅持使用一臺多核計算機,但在將來,我們將使用相同的方法在多臺計算機上執行計算。

完整的程式碼實現可以在GitHub上的Jupyter筆記本(https://github.com/Featuretools/Automated-Manual-Comparison/blob/master/Loan%20Repayment/notebooks/Featuretools%20on%20Dask.ipynb)中找到。如果你還不熟悉Featuretools,請點選:https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219。在本文,我們的主要關注點是Dask和特徵工程的使用,會跳過一些自動化特徵工程的細節。

問題:資料太多,時間不夠。

在這裡,我們將應用自動特徵工具解決住宅信貸違約風險的問題(即預測客戶是否會按時償還貸款),我們有大量的資料,這導致我們需要很長的特徵計算時間(https://www.kaggle.com/c/home-credit-default-risk)。使用深度特徵合成(https://www.featurelabs.com/blog/deep-feature-synthesis/),我們能夠從7個資料表、共計5800萬行客戶資訊中自動生成1820個特徵,呼叫這個只有一個核心的函式需要花費25個小時,即使是在一個64 GB記憶體的EC2例項上也是如此!

談到我們的EC2例項 ,通常我們的膝上型電腦 有8個核心,為了加快計算速度,我們不需要更多的記憶體,但我們需要更好的利用這些核心Featuretools確實允許並行處理,方法是在呼叫深度特徵合成時設定n_jobs引數。但是,目前該函式必須將完整的EntitySet傳送到機器上的所有核心中 。使用大型EntitySet,如果每個核心的記憶體耗盡,這可能會產生一些問題。我們的改良方案是使用並行化,從目前的結果看來Dask解決了我們的問題。

解決方案:製造很多小問題

方法是將一個大問題分解成多個較小的問題,然後使用 Dask一次執行多個小問題,每個問題位於不同的核心上。這裡最重要的一點是,我們必須確保每個問題 都獨立於其他任務,以便它們能夠同時執行。因為我們正在為資料集中的每個客戶建立特徵,所以我們目前的主要任務是為客戶建立特徵矩陣。

用Dask並行化特徵工程!


我們的做法概述如下:

  1. 通過對資料進行劃分,將一個大問題轉化為許多小問題。

  2. 編寫函式,從每個子資料集中生成一個特徵矩陣。

  3. 使用dask在所有核心上並行執行步驟2。

最後,我們得到許多較小的特徵矩陣,然後我們把它們連線到一個最終的特徵矩陣中。同樣的方法,將一個大問題分解為多個並行執行的小問題,可以縮放到任意大小的資料集,並分佈到其他庫中實現計算,例如使用PySPark(http://spark.apache.org/docs/2.2.0/api/python/pyspark.html)的SPark(https://spark.apache.org/)。

無論我們擁有什麼資源,我們都希望儘可能有效地利用它們,我們的目的是採用同樣的框架處理更大的資料集。

分割槽資料:劃分和征服

我們的第一步是建立原始資料集的小分割槽,每個分割槽包含來自七個表的所有資訊,用於處理客戶資訊。然後,我們可以使用每個分割槽獨立計算一組客戶資訊的特徵矩陣。

完成此操作的方法是:獲取所有客戶資訊的列表,將其分解為104個子列表,然後迭代這些子列表,最後將資料細分為只包括子列表中的客戶資訊,並將結果資料儲存到磁碟中。此過程的基本虛擬碼是:

用Dask並行化特徵工程!


用Dask並行化特徵工程!


104個分割槽是根據以下幾個準則選擇的:

  1. 我們希望至少有和核心一樣多的分割槽,並且這個數目應該是核心數量的倍數。

  2. 每個分割槽必須小到只能容納單個核心的記憶體。

  3. 分割槽越多,完成每個任務的時間變化就越小。

(這樣做額外的優化點是可以減少記憶體使用。這使我們的整個資料集從4GB縮小到到大約2GB左右。我建議你閱讀這篇文章:https://pandas.pydata.org/pandas-docs/stable/categorical.html 。因為只有這樣你才能有效地使用它們。

將所有104個分割槽儲存到磁碟需要大約30分鐘,但這樣的步驟,我們以後不需重複第二次。

用Dask並行化特徵工程!


來自分割槽的實體集

Featuretools中的實體集是一種有用的資料結構,因為它包含多個表及其之間的關係。若要建立一個EntitySet從分割槽中,我們需要編寫一個函式,該函式將從磁碟讀取分割槽,然後生成EntitySet以及它們之間的關係。

此步驟的虛擬碼是:

用Dask並行化特徵工程!


用Dask並行化特徵工程!


注意,這個時候函式返回了EntitySet,而不是像對分割槽的資料那樣儲存它。的確儲存原始資料是解決此問題的一個更好的選擇,但是我們可能更希望修改EntitySets (比如新增有趣的值或領域知識特徵 ),而不更改原始資料。動態生成EntitySet,然後將其傳遞到下一階段:計算特徵矩陣。

一個實體集合的特徵矩陣

函式feature_matrix_from_entityset完全按照我們當初命名的目的那樣做:即接受先前建立的EntitySet(實體集),並使用深度特徵合成技術生成一個包含數千個特徵的特徵矩陣。然後將特徵矩陣儲存到磁碟中。為了確保每個分割槽都有相同的特徵,我們只生成一次特徵定義,然後使用Featuretools函式calculate_feature_matrix。

下面是整個函式(我們傳入一個帶有EntitySet和分割槽號的字典,這樣我們就可以儲存具有唯一名稱的函式矩陣):

用Dask並行化特徵工程!


用Dask並行化特徵工程!


用Dask並行化特徵工程!


chunk_size是這個呼叫中唯一棘手的部分:它用於將特徵矩陣計算分解為更小的部分,但是由於我們已經對資料進行了分割槽,這不再是必要的,只要整個EntitySet能夠容納記憶體就好。同時我發現,將chunk_size設定為觀察時的數量,可以有效的節省時間。

現在我們有了從磁碟上的資料分割槽到特徵矩陣所需要的所有單獨的部分。我們之前所做的步驟包含了大部分準備工作,現在讓Dask並行執行變的非常簡單。

Dask:釋放你的機器

Dask是一個平行計算庫,它允許我們同時進行多項計算,或者使用一臺機器(本地)上的程式/執行緒,或者使用多臺單獨的計算機(叢集)。對於一臺機器,Dask允許我們使用執行緒或程式並行執行計算。

程式不共享記憶體並在單個核心上執行,更適合於不需要通訊的計算密集型任務。執行緒共享記憶體,但在Python中,由於全域性直譯器鎖(GIL)的原因,兩個執行緒不能在同一個程式中同時操作,只有一些操作可以使用執行緒並行執行。(有關執行緒/程式的更多資訊,請點選:https://medium.com/@bfortuner/python-multithreading-vs-multiprocessing-73072ce5600b)

由於計算特徵矩陣屬於計算密集型,並且與之對應的每個分割槽都可以獨立完成,所以在本文我們希望使用程式。任務不需要共享記憶體,因為每個特徵矩陣不依賴於其他特徵矩陣。在電腦科學方面,通過對資料進行劃分,我們解決了我們的問題。不過這是一場"尷尬的"並行(http://www.cs.iusb.edu/~danav/teach/b424/b424_23_embpar.html),因為我們的核心不需要交流。

如果我們使用程式 (按下面的程式碼 那樣做),我們將有8個核心,每個核心分配2GB記憶體(總共16 GB,具體記憶體取決於你的膝上型電腦)。

用Dask並行化特徵工程!


為了檢查一切是否順利,我們可以導航到localhost:8787,其中Dask已經為我們設定了一個Bokeh儀表板。在核心選項卡上,我們看到8個記憶體為2GB的核心:

用Dask並行化特徵工程!


目前,所有8個核心都處於閒置狀態,因為我們沒有給它們分配任何事情。下一步是建立一個"Dask包",你可以理解為是Dask分配給核心的任務列表。我們使用db.from_sequence方法和分割槽路徑列表來生成"Dask包"。

用Dask並行化特徵工程!


然後,我們將計算任務對映到Dask包上。對映意味著獲取一個函式和一個輸入列表,並將該函式應用於列表中的每個元素。因為我們首先需要從每個分割槽建立一個EntitySet(實體集),所以我們將相關的函式對映到"Dask包"上:

用Dask並行化特徵工程!


接下來,我們進行另一次對映,這一次是為了生成特徵矩陣:

用Dask並行化特徵工程!


這段程式碼將獲取第一個對映 的輸出(即實體集 ) ,並將其傳遞給第二個對映。這些步驟實際上並不執行計算,而是列出Dask隨後將分配給核心的任務列表。要執行任務並生成特徵矩陣,我們呼叫:

用Dask並行化特徵工程!


DASK根據由對映構造的任務圖(有向無環圖)自動將任務分配給核心。當計算髮生時,我們可以在Bokeh儀表板上檢視任務圖和狀態(https://en.wikipedia.org/wiki/Directed_acyclic_graph)。

用Dask並行化特徵工程!


上圖中,左邊的表示entity_set_from_partition函式,右邊的是feature_matrix_from_entityset函式。從這個圖中,我們可以看到兩個函式之間有依賴關係,但每個分割槽的特徵矩陣計算之間沒有依賴關係。

Bokeh儀表板上還有許多其他視覺化方法,包括任務流(下圖左)和操作概要(下圖右):

用Dask並行化特徵工程!


從任務流中,我們可以看到,所有八個核心同時使用,總共有208項任務要完成。配置檔案告訴我們,花費時間最長的操作是計算每個分割槽的特徵矩陣。在我的MacBook上,構建和儲存所有104個功能矩陣需要6200秒(1.75小時)。這是一個相當大的改進,這就是重寫程式碼到儘可能高效地使用可用硬體帶來的好處。

我們沒有更高一級的計算機,而是重寫程式碼,以便最有效地利用我們擁有的資源。然後,如果我們有更高一級的計算機,我們可以使用相同的程式碼,以進一步減少計算時間。

構造一個特徵矩陣

一旦我們有了單獨的特徵矩陣,如果我們使用增量學習(https://blog.bigml.com/2013/03/12/machine-learning-from-streaming-data-two-problems-two-solutions-two-concerns-and-two-lessons/),我們就可以直接使用它們進行建模。另一種選擇是建立一個特徵矩陣,可以在純Python使用pandas完成:

用Dask並行化特徵工程!


用Dask並行化特徵工程!


單一特徵矩陣有350,000行和1,820列,形狀與我第一次使用單個核心時的形狀相同。

用Dask並行化特徵工程!


結論

我們不應該考慮如何獲得更好的計算裝置,而應該考慮如何儘可能有效地使用我們擁有的硬體。在本文中,我們介紹瞭如何使用Dask並行程式碼,它允許我們使用膝上型電腦完成計算,計算速度是單核計算速度的8倍。

我們設計的解決方案主要利用了幾個關鍵概念:

  1. 將問題分解成更小的、獨立的塊。

  2. 編寫函式時一次處理一個塊。

  3. 將每個塊委託給一個核心平行計算。

現在我們不僅可以利用Featuretools自動特徵工程的的速度和建模效能,還可以使用Dask並行地執行計算,並獲得更快的結果。此外,我們還可以使用相同的方法來擴充套件到更大的資料集,從而使我們能夠很好地處理任何機器學習問題。


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

相關文章