Spark 1.2在MLlib中引入了隨機森林和梯度提升樹(GBTs).這兩種機器學習方法適用於分類和迴歸,且是在機器學習演算法中應用得最多和最成功的演算法。隨機森林和GBTs都是整合學習演算法,它們通過整合多棵決策樹來實現強分類器。這篇博文中,我們會闡述這些模型及其他們在MLlib中的分散式實現。我們也給出一些簡單例子和要點以便你知道如何上手。
整合學習方法
簡單來說,整合學習方法就是基於其他的機器學習演算法,並把它們有效的組合起來的一種機器學習演算法。組合產生的演算法相比其中任何一種演算法模型更強大、準確。
在MLlib 1.2中,我們使用決策樹作為基礎模型。我們提供兩種整合演算法:隨機森林和梯度提升樹(GBTs)。兩者之間主要差別在於每棵樹訓練的順序。
隨機森林通過對資料隨機取樣來單獨訓練每一棵樹。這種隨機性也使得模型相對於單決策樹更健壯,且不易在訓練集上產生過擬合。
GBTs則一次只訓練一棵樹,後面每一棵新的決策樹逐步矯正前面決策樹產生的誤差。隨著樹的新增,模型的表達力也愈強。
最後,兩種方法都生成了一個決策樹的權重集合。該整合模型通過組合每棵獨立樹的結果來進行預測。下圖顯示一個由3棵決策樹整合的簡單例項。
在上述例子的迴歸集合中,每棵樹都預測出一個實值。這些預測值被組合起來產生最終整合的預測結果。這裡,我們通過取均值的方法來取得最終的預測結果(當然不同的預測任務需要用到不同的組合演算法)。
整合學習的分散式學習演算法
在MLlib中,隨機森林和GBTs的資料都是按例項(行)儲存的。演算法的實現以原始的決策樹程式碼為基礎,每棵決策樹採用分散式學習(早前的部落格中有提到)。我們的許多演算法優化都是參考Google’s PLANET project,特別是其中一篇關於分散式環境下的整合學習的文章。
隨機森林:隨機森林中的每棵樹都是單獨訓練,多棵樹可以並行訓練(除此之外,單獨的每棵樹的訓練也可以並行化)。MLlib也確實是這麼做的:根據當前迭代記憶體的限制條件,動態調整可並行訓練的子樹的數量。
GBTs:因為GBTs只能一次訓練一棵樹,因此並行訓練的粒度也只能到單棵樹。
我們在這裡強調一下MLlib中用到的兩項重要的優化技術
- 記憶體:隨機森林使用一個不同的樣本資料訓練每一棵樹。我們利用TreePoint這種資料結構來儲存每個子取樣的資料,替代直接複製每份子取樣資料的方法,進而節省了記憶體。
- 通訊:儘管決策樹經常通過選擇樹中每個決策點的所有功能進行訓練,但隨機森林則往往在每一個節點限制選擇一個隨機子集。MLlib的實現中就充分利用了這個子取樣特點來減少通訊:例如,若每個節點值用到1/3的特徵,那麼我們就會減少1/3的通訊。
詳細部分請見MLlib程式設計指南的整合章節。
使用MLlib整合學習
我們將演示如何使用MLlib進行學習整合模型。下面的Scala例子說明了怎麼讀取資料集,將資料集分割為訓練集和測試集,學習一個模型以及列印出模型及其測試精度。Java和Pyton的例子請參閱MLlib程式設計指南。需要注意的是GBTs暫時還沒有Python介面,但是我們期望Spark1.3釋出版中會包含。(via Github PR 3951)
隨機森林例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import org.apache.spark.mllib.tree.RandomForest import org.apache.spark.mllib.tree.configuration.Strategy import org.apache.spark.mllib.util.MLUtils // Load and parse the data file. val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt") // Split data into training/test sets val splits = data.randomSplit(Array(0.7, 0.3)) val (trainingData, testData) = (splits(0), splits(1)) // Train a RandomForest model. val treeStrategy = Strategy.defaultStrategy("Classification") val numTrees = 3 // Use more in practice. val featureSubsetStrategy = "auto" // Let the algorithm choose. val model = RandomForest.trainClassifier(trainingData, treeStrategy, numTrees, featureSubsetStrategy, seed = 12345) // Evaluate model on test instances and compute test error val testErr = testData.map { point => val prediction = model.predict(point.features) if (point.label == prediction) 1.0 else 0.0 }.mean() println("Test Error = " + testErr) println("Learned Random Forest:n" + model.toDebugString) |
GBTs例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import org.apache.spark.mllib.tree.GradientBoostedTrees import org.apache.spark.mllib.tree.configuration.BoostingStrategy import org.apache.spark.mllib.util.MLUtils // Load and parse the data file. val data = MLUtils.loadLibSVMFile(sc, "data/mllib/sample_libsvm_data.txt") // Split data into training/test sets val splits = data.randomSplit(Array(0.7, 0.3)) val (trainingData, testData) = (splits(0), splits(1)) // Train a GradientBoostedTrees model. val boostingStrategy = BoostingStrategy.defaultParams("Classification") boostingStrategy.numIterations = 3 // Note: Use more in practice val model = GradientBoostedTrees.train(trainingData, boostingStrategy) // Evaluate model on test instances and compute test error val testErr = testData.map { point => val prediction = model.predict(point.features) if (point.label == prediction) 1.0 else 0.0 }.mean() println("Test Error = " + testErr) println("Learned GBT model:n" + model.toDebugString) |
Scalability 擴充套件性
通過二分類問題的實證結果,我們證明了MLlib的擴充套件性。以下的各張圖表分別對GBTs和隨機森林的特性進行比較,其中每棵樹都有不同的最大深度。
這些測試是一個迴歸的任務,即從音訊特徵從預測出歌曲的釋出日期(YearPredictionMSD資料集來自UCI ML repository)。我們使用EC2 r3.2xlarge機器,演算法的引數除非特別說明都使用預設值。
模型大小的伸縮:訓練時間和測試誤差
下面的兩張圖表顯示了增加樹的數量對整合效果的影響。對於GBTs和隨機森林這兩者而言,增加樹的數量都會增加訓練的時間(第一張圖所示),同時樹的數量增加也提高了預測精度(以測試的平均均方誤差為衡量標準,圖二所示)。
兩者相比,隨機森林訓練的時間更短,但是要達到和GBTs同樣的預測精度則需要更深的樹。GBTs則能在每次迭代時顯著地減少誤差,但是經過過多的迭代,它又太容易過擬合(增加了測試誤差)。隨機森林則不太容易過擬合,測試誤差也趨於穩定。
下面為均方誤差隨單棵決策樹深度(深度分別為2,5,10)變化曲線圖。
說明:463,715 個訓練例項. 16個節點。
訓練集的伸縮:訓練時間和測試誤差
下面兩張圖表顯示了使用不同的訓練集對演算法結果產生的影響。圖表表明,雖然資料集越大,兩種方法的訓練時間更長,但是卻能產生更好的測試結果。
進一步伸縮:更多的節點,更快的訓練速度
最後一張圖表展示了使用更大的計算機叢集來解決上述問題的效果,結論是GBTs和隨機森林在大叢集上速度得到顯著提升。例如說,單樹深度為2的GBTs在16個節點上的訓練速度大約是在2個節點上的4.7倍。資料集越大則效果提升的越明顯。
展望
GBTs不久就會提供Python的API。未來的另一個開發議題就是可插入性:整合方法不僅僅可以整合決策樹,它可以整合幾乎所有的分類和迴歸演算法。在Spark 1.2中,處於實驗中的spark.ml包中引入的Pipelines API將使得整合方法通用化,並做到真正的可插入。
進一步瞭解
API和相關例子詳見MLlib整合學習文件。
要想了解更多用於構建集合的決策樹相關背景知識,詳見之前的部落格。
致謝 MLlib整合演算法由本部落格的作者們合作開發完成,他們是Qiping Li (Alibaba), Sung Chung (Alpine Data Labs), and Davies Liu (Databricks).我們也感謝Lee Yang, Andrew Feng, and Hirakendu Das (Yahoo) ,他們幫助設計與測試。我們也歡迎你來貢獻一份力量!