大資料人工智慧之隨機森林演算法(Random Forest)
隨機森林(Random Forest)是一個整合演算法,多棵決策樹就組成了一個森林,下面具體講一下這個演算法和應用的原始碼。
(1)隨機森林演算法介紹
隨機森林是以決策樹作為基礎模型的整合演算法。隨機森林是機器學習模型中用於分類和迴歸的最成功的模型之一。通過組合大量的決策樹來降低過擬合的風險。與決策樹一樣,隨機森林處理分類特徵,擴充套件到多類分類設定,不需要特徵縮放,並且能夠捕獲非線性和特徵互動。
隨機森林分別訓練一系列的決策樹,所以訓練過程是並行的。因演算法中加入隨機過程,所以每個決策樹又有少量區別。通過合併每個樹的預測結果來減少預測的方差,提高在測試集上的效能表現。
隨機性體現
1.每次迭代時,對原始資料進行二次抽樣來獲得不同的訓練資料
2.對於每個樹節點,考慮不同的隨機特徵子集來進行分裂
除此之外,決策時的訓練過程和單獨決策樹訓練過程相同。
對新例項進行預測時,隨機森林需要整合其各個決策樹的預測結果。迴歸和分類問題的整合的方式略有不同。分類問題採取投票制,每個決策樹投票給一個類別,獲得最多投票的類別為最終結果。迴歸問題每個樹得到的預測結果為實數,最終的預測結果為各個樹預測結果的平均值。
Spark的隨機森林演算法支援二分類、多分類以及迴歸的隨機森林演算法,適用於連續特徵以及類別特徵。
(2)隨機森林應用場景
分類任務:
2、推薦系統的二次rerank排序
3、金融行業可以用隨機森林做貸款風險評估
4、保險行業可以用隨機森林做險種推廣預測
5、醫療行業可以用隨機森林生成輔助診斷處置模型
迴歸任務
1、預測一個孩子的身高
2、電商網站的商品銷量預測
隨機森林是由多顆決策樹組成,決策能做的隨機森林也都能做,並且效果更好。
(3) Spark隨機森林訓練和預測過程
隨機森林分別訓練一組決策樹,因此訓練可以並行完成。該演算法將隨機性注入訓練過程,以使每個決策樹略有不同。結合每棵樹的預測可以減少預測的方差,提高測試資料的效能。
訓練
注入訓練過程的隨機性包括:
在每次迭代時對原始資料集進行二次取樣,以獲得不同的訓練集(例如,bootstrapping)
考慮在每個樹節點處分割的不同隨機特徵子集
除了這些隨機化之外,決策樹訓練的方式與單個決策樹的方式相同
預測
要對新例項進行預測,隨機森林必須整合各個決策樹的預測。對於分類和迴歸,這種整合的方式不同
分類
多數票原則。每棵樹的預測都算作一個類的投票。預計該標籤是獲得最多選票的類別
迴歸
平均。每棵樹預測一個真實的值。預測標籤是各個樹預測的平均值
(4) Spark隨機森林模型引數詳解
隨機森林的引數比較多,我們實際工作中經常會調整引數值,讓模型達到一個最優的狀態,除了調參的方法,還有就是通過手工改進每個特徵的計算公式,增加資料特徵,不斷的優化模型。引數調優是實際工作中不可或缺的一個必要環節,讓我們看一下都有哪些引數:
1
checkpointInterval:
型別:整數型
含義:設定檢查點間隔(>=1),或不設定檢查點(-1)
2
featureSubsetStrategy:
型別:字串型
含義:每次分裂候選特徵數量
3
featuresCol:
型別:字串型
含義:特徵列名
4
impurity:
型別:字串型
含義:計算資訊增益的準則(不區分大小寫)
5
impurity:
型別:字串型
含義:計算資訊增益的準則(不區分大小寫)
6
labelCol:
型別:字串型
含義:標籤列名
7
maxBins:
型別:整數型
含義:連續特徵離散化的最大數量,以及選擇每個節點分裂特徵的方式
8
maxDepth:
型別:整數型
含義:樹的最大深度(>=0)
決策樹最大深度max_depth, 預設可以不輸入,如果不輸入的話,決策樹在建立子樹的時候不會限制子樹的深度。一般來說,資料少或者特徵少的時候可以不管這個值。如果模型樣本量多,特徵也多的情況下,推薦限制這個最大深度,具體的取值取決於資料的分佈。常用的可以取值10-100之間
引數效果:值越大,決策樹越複雜,越容易過擬合
9
minInfoGain:
型別:雙精度型
含義:分裂節點時所需最小資訊增益
10
minInstancesPerNode:
型別:整數型
含義:分裂後自節點最少包含的例項數量
11
numTrees:
型別:整數型
含義:訓練的樹的數量
12
predictionCol:
型別:字串型
含義:預測結果列名
13
probabilityCol:
型別:字串型
含義:類別條件概率預測結果列名
14
rawPredictionCol:
型別:字串型
含義:原始預測
15
seed:
型別:長整型
含義:隨機種子
16
subsamplingRate:
型別:雙精度型
含義:學習一棵決策樹使用的訓練資料比例,範圍[0,1]
17
thresholds:
型別:雙精度陣列型
含義:多分類預測的閥值,以調整預測結果在各個類別的概率
上面的引數有的對準確率影響很大,有的比較小。其中maxDepth最大深度這個引數對精準度影響很大,但設定過高容易過擬合。應該根據實際情況設定一個合理的值,但一般不超過20。
(5) Spark隨機森林原始碼實戰
訓練資料格式和上面講的決策樹是一樣的,隨機森林可以用來做二值分類,也可以多分類,還可以用它來做迴歸,用來做迴歸的應用場景,比如做銷量預測,也能起到非常好的效果,雖然做銷量預測用時間序列演算法比較多,但隨機森林的效果不遜色於時間序列,這得在引數調優和特徵工程調優上下功夫。下面的程式碼演示了如何訓練資料模型,根據模型預測特徵屬於哪個分類,並且演示了模型如何做持久化和載入的完整過程。
上面講的隨機森林演算法是有多棵決策樹組成的,是一個整合演算法,屬於Bagging詞袋模型,我們看下它是如何工作的。
工作原理
基於Bagging的隨機森林(Random Forest)是決策樹集合。在隨機森林中,我們收集了許多決策樹(被稱為“森林”)。為了根據屬性對新物件進行分類,每個樹都給出分類,然後對這些樹的結果進行“投票”,最終選擇投票得數最多的哪一類別。
每棵樹按以下方法構建:
如果取 N 例訓練樣本作為來訓練每棵樹,則隨機抽取1例樣本,再有放回地進行下一次抽樣。每次抽樣得到的 N 個樣本作為一棵樹的訓練資料。
如果存在 M 個輸入變數(特徵值),則指定一個數字 m(遠小於 M),使得在每個節點處,隨機地從 M 中選擇 m 個特徵,並使用這些m 個特徵來對節點進行最佳分割。在森林生長過程中,m 的值保持不變。
每棵樹都儘可能自由生長。沒有修剪。
隨機森林的優勢
該演算法可以解決兩類問題,即分類和迴歸,並在兩個方面進行了不錯的估計。
最令我興奮的隨機森林的好處之一是處理具有更高維度的大資料集的能力。它可以處理數千個輸入變數並識別最重要的變數,因此它被視為降維方法之一。此外,模型輸出變數的重要性, 這可以是一個非常方便的功能(在一些隨機資料集上)。
它有一種估算缺失資料的有效方法,並在大部分資料丟失時保持準確性。
它具有平衡類不平衡的資料集中的錯誤的方法。
上述功能可以擴充套件到未標記的資料,從而導致無監督的聚類,資料檢視和異常值檢測。
隨機森林涉及輸入資料的取樣,替換稱為自舉取樣。這裡有三分之一的資料不用於培訓,可用於測試。這些被稱為袋外樣品。對這些袋外樣品的估計誤差稱為 袋外誤差。通過Out of bag進行誤差估計的研究,證明了袋外估計與使用與訓練集相同大小的測試集一樣準確。因此,使用out-of-bag誤差估計消除了對預留測試集的需要。
隨機森林的缺點
它確實在分類方面做得很好,但不如迴歸問題好,因為它沒有給出精確的連續性預測。在迴歸的情況下,它不會超出訓練資料的範圍進行預測,並且它們可能過度擬合特別嘈雜的資料集。
隨機森林可以感覺像統計建模者的黑盒子方法 - 你幾乎無法控制模型的作用。你最多可以嘗試不同的引數和隨機種子!
在實際使用中還發現Spark隨機森林有一個問題, Spark預設的隨機森林的二值分類預測只返回0和1,不能返回概率值。比如預測廣告被點選的概率,如果都是1的話哪個排在前面,哪個排在後面呢?我們需要更嚴謹的排序,必須是一個連續的小數值。因此,需要對原始的Spark隨機森林演算法做二次開發,讓它能返回一個支援概率的數值。
改原始碼一般來說會比較複雜,因為再改之前,得能看懂它的原始碼。否則你不知道從哪兒下手。看懂後,找到最關鍵的需要修改的函式後,儘可能較小改動來實現你的業務功能,以免改動較多產生別的bug。下面我們講一下如果做二次開發,使隨機森林能滿足我們的需求。
(3) Spark隨機森林訓練和預測過程
Spark隨機森林改成支援概率值只需要改動一個類treeEnsembleModels.scala即可。
修改原來的兩個函式如下:
/**
* Predict values for a single data point using the model trained.
*
* @param features array representing a single data point
* @return predicted category from the trained model
*/
def predict(features: Vector): Double = {
(algo, combiningStrategy) match {
case (Regression, Sum) =>
predictBySumming(features)
case (Regression, Average) =>
predictBySumming(features) / sumWeights
case (Classification, Sum) => // binary classification
val prediction = predictBySumming(features)
// TODO: predicted labels are +1 or -1 for GBT. Need a better way to store this info.
if (prediction > 0.0) 1.0 else 0.0
case (Classification, Vote) =>
predictByVoting(features)
case _ =>
throw new IllegalArgumentException(
"TreeEnsembleModel given unsupported (algo, combiningStrategy) combination: " +
s"($algo, $combiningStrategy).")
}
}
/**
* Classifies a single data point based> */
private def predictByVoting(features: Vector): Double = {
val votes = mutable.Map.empty[Int, Double]
trees.view.zip(treeWeights).foreach { case (tree, weight) =>
val prediction = tree.predict(features).toInt
votes(prediction) = votes.getOrElse(prediction, 0.0) + weight
}
votes.maxBy(_._2)._1
}
修改後的兩個函式:
def predictChongDianLeMe(features: Vector): Double = {
(algo, combiningStrategy) match {
case (Regression, Sum) =>
predictBySumming(features)
case (Regression, Average) =>
predictBySumming(features) / sumWeights
case (Classification, Sum) => // binary classification
val prediction = predictBySumming(features)
// TODO: predicted labels are +1 or -1 for GBT. Need a better way to store this info.
if (prediction > 0.0) 1.0 else 0.0
case (Classification, Vote) =>
//我們用的是基於投票的分類演算法,關鍵改這裡。用我們自己實現的投票演算法。
predictByVotingChongDianLeMe(features)
case _ =>
throw new IllegalArgumentException(
"TreeEnsembleModel given unsupported (algo, combiningStrategy) combination: " +
s"($algo, $combiningStrategy).")
}
}
private def predictByVotingChongDianLeMe(features: Vector): Double = {
val votes = mutable.Map.empty[Int, Double]
trees.view.zip(treeWeights).foreach { case (tree, weight) =>
val prediction = tree.predict(features).toInt
votes(prediction) = votes.getOrElse(prediction, 0.0) + weight
}
//通過filter篩選找到投票結果後的投贊成票的樹的記錄
val zVotes = votes.filter(p => p._1==1)
var zTrees = 0.0
if (zVotes.size > 0) {
zTrees = zVotes.get(1).get
}
//返回投贊成票的樹的數量zTrees,我們訓練設定樹的個數是總數total,zTrees*1.0/total=概率,就是廣告被點選的一個概率小數值。
zTrees
}
這樣我們就修改完程式碼,預測函式返回的是投贊成票的樹的數量zTrees,如果我們在呼叫端的時候改成我們的概率值,我們訓練設定樹的個數是總數total,zTrees*1.0/total=概率,就是廣告被點選的一個概率小數值。當然你也可以不改成小數,就按這個zTrees的贊成票數量來排序也是可以的。修改完之後需要對專案編譯打包。Spark的工程非常大,要是把原始碼環境都調好了,不是那麼容易。實際上會遇到很多的問題,才能把環境搞好。另外一個就是修改完程式碼,打包的話如果之前沒搞過,也得摸索下。把編譯打好的jar包替換掉線上叢集的對應的jar包即可。
(7) 隨機森林和GBDT的聯絡和區別
上面講的隨機森林是基於Bagging的詞袋模型,同樣在Spak裡面有多棵樹組成整合演算法還有GradientBoostedTrees演算法,GradientBoostedTrees可以簡稱為GBDT,它也是整合演算法,屬於Boosting整合演算法,但它和Bagging有什麼區別呢?
Bagging的方式算是比較簡單的,訓練多個模型,利用每個模型進行投票,每個模型的權重都一樣,對於分類問題,取總票數最多作為分類,對於迴歸,取平均值。利用多個弱分類器,整合一個性能高的分類器。典型代表是隨機森林。隨機森林在訓練每個模型的時,增加隨機的因素,對特徵和樣本進行隨機抽樣,然後把各顆樹訓練的結果整合融合起來。隨機森林可以進行並行訓練多顆樹。
Boosting的方式也是訓練多個決策樹模型,是一種迭代的演算法模型,在訓練過程中更加關注錯分的樣本,對於越是容易錯分的樣本,後續的模型訓練越要花更多精力去關注,提高上一次分錯的資料權重,越在意那些分錯的資料。在整合融合時,每次訓練的模型權重也會不一樣,最終通過加權的方式融合成最終的模型。Adaboost、GBDT採用的都是boosting的思想。