使用4個機器學習庫來處理大數據

在Booking.com,我們可以根據客戶和合作夥伴(酒店,住宅,度假租賃等)如何與我們的平臺互動來處理大量數據。我們的主要挑戰之一是在合理的時間內在這些非常大且稀疏的數據集上訓練準確的機器學習模型。

在這篇文章中,我們比較了四種最流行的機器學習庫的性能,這些庫為大數據提供瞭解決方案:H2OTensorFlowSparkMLlib VowpalWabbit。目前我們將僅涵蓋線性模型,將其他機器學習模型的比較作為未來的工作。我們的目標是專注於更大的數據集的解決方案,這些數據集無法融入單個計算機的內存中。

數據集的描述

Booking.com網站為任何能上網的人提供全球各地的住宿機會。為了更好地分析我們的數據,我們將世界分為超過100k個不同的位置,併為它們分配一個唯一的id。當然可以通過相似的地點聚類或使用目標編碼來降低維度問題。然而,由於booker和property location的每種組合都表現出非常不同的市場行為,因此將每個位置作為單獨的特性使用通常更有效。

另一方面,訓練一個具有如此多特徵的模型需要大量的數據來防止過度擬合,在某些情況下,還需要大量的內存來執行計算。當數據科學家不得不選擇一個機器學習庫來使用時,這對他們來說是一個巨大的挑戰。

為了比較最流行的庫的算法和性能,我們使用了簡化版的數據集來處理我們在Booking.com上正在處理的問題:

當客戶到達Booking.com網站時,他們需要指定目的地和日期間隔。然後將向他們顯示可用屬性列表。在搜索會話期間,我們希望預測客戶下次單擊的屬性的評分。良好的預測使Booking.com能夠為用戶提供更好,更個性化的體驗。

不幸的是,一種簡單的啟發式方法並不能帶來令人滿意的結果。圖2中的表格顯示了基線預測的主要指標,這些指標是在會話中單擊的評論分數的平均值。如果這是不可用的,我們使用一個給定目的地的平均評論分數。下面我們報告通過比較標記數據和基線預測獲得的均方根誤差(RMSE),決定係數(R 2)和平均絕對誤差(MAE)。

使用4個機器學習庫來處理大數據

圖2:使用簡單啟發式的目標變量預測結果

為了改善這種預測,我們對80%的數據進行線性迴歸,將20%作為測試集。我們使用H2O,TensorFlow,SparkMLlib和VowpalWabbit來做到這一點。

線性迴歸

最簡單但性能最佳且使用最廣泛的機器學習算法之一是線性迴歸。要預測的變量Y被假定為一組特徵的線性函數。如果我們有一個包含N個示例和M個特徵的數據集,我們假設保持以下關係:

使用4個機器學習庫來處理大數據

Y是 N個目標值的矢量,X是特徵( N × M矩陣), α是必須估計的 M個係數的矢量。假設線性估計和ground truth 之間的剩餘偏差ε的分佈正態地分佈為零均值。

訓練線性迴歸意味著找到最適合觀察數據的向量α。這相當於找到α最小化最小平方損失函數:

使用4個機器學習庫來處理大數據

最小化損失函數的α值是將其梯度設置為零的值。找到這一點的典型方法是運行梯度下降 算法。從α空間中的隨機點開始,算法計算該點的損失函數的梯度,並將係數更新為負梯度方向的一步:

使用4個機器學習庫來處理大數據

該步驟的大小由參數λ控制,稱為學習率。該算法通常在有限次數的迭代之後收斂,即參數值的微小變化不會導致損失函數值的任何顯著降低。另一種流行的方法是使用二階方法 ,通過將梯度與Hessian的倒數(或其近似值)相乘來選擇下降方向。

一種流行的二階方法是Broyden-Fletcher-Goldfarb-Shanno算法(L- bfgs),其中“L”只用於有限的內存使用。L-BGFS使用的是Hessian的逆的近似,它要求在內存中保留以前的梯度值的固定數量。

為了計算梯度,(如果需要的話),對逆Hessian的近似值,並更新系數,計算機必須記住所有的數據和每個迭代中係數的所有值。對於大於O(10GB)的數據集來說,這在技術上是不可行的,這是Booking.com的標準。幸運的是,有一些機器學習工具提供了不同的策略來解決這個問題:

  • H2O通過將數據集拆分為塊來分配計算。在每次迭代時,為每個塊計算梯度和近似逆Hessian。這部分計算就像一個“mapper”,因為與隨機梯度下降相比,模型參數沒有更新。在迭代的第二部分中,就像在“reducer”中一樣,漸變和近似Hessian被組合以更新權重。默認優化算法是LBGFS,但用戶可以選擇其他幾種算法。所有數據都存儲在內存中,因此H2O需要分配一個固定的,相對較大的內存來運行。
  • SparkML使用類似於H2O的方法在執行程序上分配計算。然而,使用稀疏向量存儲特徵示例,這允許減少所需的存儲量。用戶可以通過調整分區數來控制數據集分割的塊的大小。增加spark會話的執行程序的數量可能有助於減少訓練時間,因為可以並行執行更多操作。
  • VowpalWabbit使用隨機梯度下降(SGD)。該算法一次讀取一行數據,並僅使用該行的信息更新梯度和係數。然後它重新計算損失函數並進入下一個例子。消耗的內存量與數據集中的行數無關,並且相對較小,因為只需要存儲係數的值和損失函數的值。對於在每個更新步驟讀取所有數據的方法,在SGD需要更多迭代(即,更多數據點)來收斂時使用僅一行的信息。VowpalWabbit針對讀取數據進行了高度優化。係數更新比普通梯度下降更復雜。學習率不是恆定的,而是取決於隨時間更新和衰減的係數。根據讀取的新值重新標準化功能。
  • TensorFlow肯定是這四個中最可定製的庫。它主要用於構建深度學習算法,但也提供了使用張量操作構建模型的通用框架。用戶可以完全控制最小化算法,並可以根據需要進行修改。在本研究中,我們使用了小批量梯度下降,與SGD非常相似,但是使用一批數據而不是僅僅一行來更新梯度。在小批量大小表示在每個迭代中使用的行數。在本研究中,我們使用64的小批量大小。與Vowpal Wabbit類似,默認情況下,梯度更新規則比普通梯度下降更復雜。默認優化器是Follow-The-Regularized-Leader(FTRL)。

TensorFlow還支持散列技巧。與VowpalWabbit的主要區別在於可以獨立地為每個特徵列設置散列大小。

特徵編碼

超出數據集的大小,特徵的數量也在訓練所需的內存量中起著重要作用。事實上,在每次迭代時,需要計算所有的一階導數,並且應該更新所有係數(對於L-BFGS,還需要保留在一些先前步驟中計算的導數)。這意味著訓練具有多個分類變量的模型(例如屬性的位置)可能是內存昂貴的。我們來解釋一下原因。

要在線性迴歸中使用分類特徵,必須先對其進行編碼。最常用的編碼之一是one hot編碼,即將分類變量轉換為二進制向量,如下所示:

使用4個機器學習庫來處理大數據

圖3:分類功能的one-hot編碼,例如屬性所在的國家/地區

包含200個不同值(大致相當於世界上國家的數量)的分類特徵將被轉化為200個單獨的特徵。對於我們想要解決的問題,我們有這樣一個數據集:

使用4個機器學習庫來處理大數據

圖4:用於訓練線性重新生成的數據的快照

不同的列是:

  • visitor_loc_id:訪問者的位置ID
  • dest_id:屬性的位置ID
  • avg_score:會話中單擊的屬性的平均評分
  • prev_score:點擊最後一個屬性的評分
  • target:點擊下一個屬性的得分,我們想要預測的內容

所有特徵都被視為分類。為此,prev_scoreavg_score的值已在第一個小數位被截斷。每個特徵的基數如下表所示:

使用4個機器學習庫來處理大數據

圖5:數據集中分類特徵的基數

經過one-hot-encoding後,我們獲得了280164種不同的特徵。為了在不事先知道VowpalWabbit特徵的基數的情況下執行one-hot-encoding,使用所謂的散列技巧。它首先通過哈希函數將所有要特徵轉換為整數,然後對結果進行one-hot-encoding。用戶可以通過指定散列值的位數來選擇散列函數的共域的大小。對於這項研究,我們使用28位,即268435456可能的散列函數的不同結果(VowpalWabbit的默認值是18位)。較大數量的比特減少但不消除衝突的機會,即將兩個不同的特徵映射到相同的散列值。

我們使用H2O,SparkML,TensorFlow和VowpalWabbit,使用490M數據點(~20GB)對該數據集進行了線性迴歸。

機器學習庫比較

我們為所有四個庫訓練了一個線性迴歸模型。由於行數與特徵數量之比非常大,我們決定不應用任何正則化項。我們嘗試儘可能少地從庫的基本實現中進行更改,以重現典型用法。

在不改變初始參數的情況下,H2O在測試集上給出RMSE = 0.323 ,顯著高於用所有其他庫獲得的RMSE <0.3。這是因為,默認情況下,H2O將彈性網正則化項添加到最小平方損失函數,強度為λ。使用基於訓練數據的試探法計算參數λ的值。參數α,控制L1和L2之間的平衡懲罰默認設置為0.5。對於大數據和稀疏數據的問題,這種正則化太強,導致最小化算法陷入局部最小值。為了避免這個問題,H2O用戶需要在每次訓練線性迴歸時明確設置λ= 0。

將正則化項設置為零,H2O的性能與SparkML,TensorFlow和VowpalWabbit保持一致。四個庫之間比較的最終結果如下圖6所示:

使用4個機器學習庫來處理大數據

圖6:測試集上的RMSE和MAE與正在研究的四個庫的比較

我們觀察到訓練速度和內存消耗之間的權衡,如下圖所示:

使用4個機器學習庫來處理大數據

圖7:觀察到的訓練時間。* TensorFlow有兩個值:一個是默認值,另一個是經過優化的管道

使用4個機器學習庫來處理大數據

圖8:訓練過程中使用的內存

我們已經報告了TensorFlow的兩個訓練時間,因為我們已經觀察到根據數據讀取方式的顯著變化。具體來說,我們使用TensorFlow版本1.8並使用推薦的tf.data API讀取數據。此外,訓練數據以推薦的T FRecords格式存儲,使用TensorFlow連接器在Spark中編寫。TFRecords是針對閱讀優化的序列化訓練示例,然後在訓練時對其進行反序列化。

使用默認設置,我們觀察了6小時15分鐘的訓練時間,這段時間大大減少到2小時7分鐘,優化了數據讀取過程。識別數據讀取管道中的瓶頸被證明是非常重要的。關於這個優化的技術討論超出了本文的範圍。我們發現,在反序列化之前對tf記錄進行批處理,並創建一個預取數據的緩衝區,可以顯著提高速度。

對每個模型最重要的指標、訓練的內存和時間、以及所使用的算法的完整概述如下表所示

使用4個機器學習庫來處理大數據

圖9:包含每個庫最重要指標的表

括有關訓練時間和內存使用情況的信息。* TensorFlow訓練時間引用兩個值:一個具有默認值,另一個具有優化管道

特徵交互

為了用線性迴歸來描述非線性關係,通常將特徵相互作用包括在最小化中。如果這些特性是絕對的,這就意味著創建新的級別,由原始級別的組合提供,並將它們插入到損失函數中。創建所有交互將產生超過170億個獨特的特徵。

因為我們只有490M的數據點,所以不可能限制這麼多的參數,因此顯式地對所有級別進行one-hot編碼沒有多大意義。我們考慮了兩種不同的方法:

  • 散列所有特徵空間。無論所有特徵交互的基數如何,都將考慮最多2 ^ b個級別,其中b是散列函數的位數。如前所述,這種方法與VowpalWabbit開箱即用。用戶只需要指定他想用二次或三次交互作為命令行參數訓練模型。然而,我們注意到,在VW(-q ::)中交互所有特徵的默認方法在訓練時間中引入了大量開銷。因此,我們使用參數-q aa和分配給同一名稱空間a的所有功能手動指定了交互。與前一節一樣,我們使用b = 28。TensorFlow允許使用交叉層創建交互,交叉層交互兩個分類層,並將結果散列為用戶指定的每個交互的多個buckets。對於6對成對相互作用中的每一個,我們使用了大小為10⁶的buckets。使用SparkML和H2O,用戶需要預處理數據以創建散列記錄。Spark版本2.3.0提供了一種允許非常容易地執行此操作的方法。但是,在我們的例子中,當我們使用Spark 2.2.0時,我們使用MurmurHash3實現了轉換Scala 2.11庫中提供的函數。我們將位數限制為24,否則程序將在one-hot編碼階段崩潰。H2O不是為執行此操作而設計的。用戶原則上可以在Spark中進行散列,然後將數據複製到H2O。然而,這將是一個相當複雜的程序,我們不建議這樣做。相反,要遠離線性迴歸並使用其中一種H2O方法進行非線性算法,例如:隨機森林或梯度增強機器,這樣更容易,更有效。
  • 削減低發生類別。只考慮了一些相互作用。在我們的問題中,我們有兩個具有非常高基數的特性:dest_id和visitor_loc_id。我們只考慮了涉及這些特徵的級別的相互作用,其發生次數大於10000.每個交互的其他組合被放在一個區域中。這導致了具有626310獨特功能的數據集,可以通過H2O和SparkML消化,而無需用戶進行任何進一步操作。

如下面的圖10所示,使用交互對模型進行訓練,可以使RMSE和MAE有一個小的改進,同時可以顯著增加內存消耗和訓練時間。對於SparkML,我們將結果繪製為切割低發生率類別。由於所採用的方法是不同的,所以在庫中進行訓練時間和內存消耗的比較是不完全公平的。

使用4個機器學習庫來處理大數據

圖10:在測試集上測量的RMSE和MAE與所研究的四個庫的比較,包括模型中的特徵相互作用

使用4個機器學習庫來處理大數據

圖11:包含每個庫最重要指標的表

包括有關訓練時間和內存使用情況的信息。“Strategy”列指示了在模型中包含要素交互的方法。* TensorFlow使用優化和默認管道運行

結論

我們比較了四種最流行的機器學習庫來訓練生產線性模型。儘管這些庫以固有的不同方式處理大數據,但它們的表現非常相似。

H2O 強大賣點是其易於使用的語法和詳細的界面。這使得即使是非專家也可以在很短的時間內訓練大數據的機器學習模型。這種用戶友好性的代價 是,有時H2O對於其背後發生的事情並不完全透明。這使得難以理解為什麼模型表現不佳。事實上,在我們的例子中,最好覆蓋默認參數以明確地將正則化項設置為零。

SparkML代表了分佈式機器學習的有效替代方案,訓練時間非常短。內存和資源佔用空間很大,但可以根據可用資源進行調製(以速度為代價)。SparkML還具有在同一框架中提供機器學習工具和強大的Spark數據重複功能的巨大優勢。不幸的是,沒有任何界面來監控訓練進度,並且語法不像H2O那樣友好。

VowpalWabbit已經提供了開箱即用設置的良好效果。散列函數的位數基本上是在大型稀疏數據集上訓練線性模型時唯一需要關注的參數。由於哈希技巧的開箱即用實現,它在內存消耗方面是最有效的庫。另一方面,這些庫提供了有限的非線性算法選擇和一個非常基本的界面來監控訓練的進度。用戶需要努力計算最基本的訓練指標。為了解釋模型,需要反轉散列函數。對於大位值,此操作可能在計算上很昂貴。

TensorFlow具有與VowpalWabbit類似的內存和資源佔用空間,具有更好的用戶界面和靈活的算法。用戶可以完全定製最小化策略以使其適應每個特定問題。訓練時間的顯著增加是由於底層代碼的複雜性更高。實際上,該庫是一個針對各種優化問題的通用求解器。找到最小化訓練時間的最佳配置本身就是一項非常重要的任務。在這裡,我們使用了一種簡單的方法來嘗試重現庫的標準用法,並使用更復雜的方法來顯示可以觀察到多少變化。

總之,關於使用哪個庫的選擇實際上取決於手頭的問題和可用的資源量。有了適合您的大型集群,可以使用H2O庫,特別是如果您熟悉大數據的機器學習。如果數據準備部分也在Spark中完成,SparkML提供了一個很好的選擇。如果資源有限,VowpalWabbit在大型數據集上提供了良好的性能,但非線性選項很少。對於更具可擴展性和定製的解決方案,TensorFlow提供了其他任何庫都無法提供的良好性能和靈活性。


分享到:


相關文章: