Spark隨機深林擴展—OOB錯誤評估和變量權重

本文目的 git

當前spark(1.3版)隨機森林實現,沒有包括OOB錯誤評估和變量權重計算。而這兩個功能在實際工做中比較經常使用。OOB錯誤評估能夠代替交叉檢驗,評估模型總體結果,避免交叉檢驗帶來的計算開銷。如今的數據集,變量動輒成百上千,變量權重有助於變量過濾,去掉無用變量,提升計算效率,同時也能夠幫助理解業務。因此,本人在原始代碼基礎上,擴展了這兩個功能,下面記錄實現過程,做爲備忘錄(參考代碼)。github

 

總體思路 算法

Random Forest實現中,大多數內部對象是私有(private[tree])的,因此擴展代碼使用了org.apache.spark.mllib.tree的命名空間,複用這些內部對象。實現過程當中,須要放回抽樣數據,Random Forest的原始實現是放在局部變量baggedInput中,外部沒法訪問,因此擴展代碼必須冗餘部分原始代碼,用於訪問baggedInput變量。原始實現中,會先將LabeledPoint轉成裝箱對象TreePoint,可是在計算OOB時,須要LabeledPoint,因此須要實現TreePoint到LabeledPoint的轉換邏輯。對於連續變量,取每一個箱的中間值做爲反轉後的結果;離散變量不須要作修改。固然爲何取中間值,爲何取三分之一或其餘什麼地方?由於在裝箱過程當中,數據的分佈信息已丟失,因此取0.5是一個能夠接受的選擇。apache

 

OOB錯誤評估 服務器

原理 數據結構

OOB是Out-Of-Bag的縮寫,顧名思義,使用那些out of bag的數據進行錯誤度量。隨機森林在訓練開始時,會根據樹的數量n,進行n次有放回採樣,用於訓練每一棵樹。放回採樣,必然致使一部分數據被選中,另一部分數據沒有選中,選中了就放到bag中,沒有選中的就是out of bag。平均而言,每次放回採用中,37%的數據不會被選中,詳細推導見附錄【Out Of Bag機率】。這些沒有選中的數據不參與建模,因此能夠做爲驗證數據,評估模型效果。對於每一條記錄,若參與了m棵樹的建模,則n-m樹沒有參與建模,那麼就能夠將這剩下的n-m棵樹做爲子森林,進行分類驗證,列子以下:併發

 

Tree 1dom

Tree 2機器學習

Tree 3分佈式

Tree 4

Tree 5

Tree 6

Tree 7

Data 1

 

*

 

*

 

*

 

Data 2

*

*

   

*

*

 

Data 3

*

 

*

     

*

Data 4

*

 

*

*

   

*

Data 5

 

*

   

*

 

*

上面的表格中,每行表示一個記錄,每列表示一顆樹,"*"表示該記錄參與了某棵樹的建模。對於Data 1,參與了Tree 2,4,6的建模過程,那麼可選取Tree 3,5,7做爲子隨機森林模型,並計算Data 1的分類結果。

 

實現

上面提到了OOB的原理和評估方法,下面介紹如何實現,

private def computeOobError(

strategy: Strategy,

baggedInput: RDD[BaggedPoint[TreePoint]],

bins: EnhancedRandomForest.BinList,

forest: RandomForestModel): Double = {

 

val actualPredictPair = baggedInput

.map(x => {

val labeledPoint = EnhancedTreePoint.treePointToLabeledPoint(

x.datum, bins, strategy)

(x.subsampleWeights.zipWithIndex.filter(_._1 == 0)

.map(_._2).map(index => forest.trees(index)), labeledPoint)

})

.filter(_._1.size > 0) // 過濾掉無樹的森林

.map({

case (oobTrees, labeledPoint) => {

val subForest = new RandomForestModel(strategy.algo, oobTrees)

(labeledPoint.label, subForest.predict(labeledPoint.features))

}

})

 

val totalCount = actualPredictPair.count

assert(totalCount > 0,

s"OOB error measure: data number is zeror")

strategy.algo match {

case Regression => actualPredictPair.map(x => Math.pow(x._1 -

x._2, 2)).reduce(_ + _) / totalCount

case Classification => actualPredictPair.filter(x => x._1 !=

x._2).count.toDouble / totalCount

}

 

}

上面函數截取了oob實現的主邏輯,baggedInput是一個特殊的數據結構,用於記錄每一條記錄選中的信息。好比須要創建10棵樹,那麼bagged有10列,每一列記錄了在當前這棵樹,該記錄被選中了幾回。因此,使用0標示那些沒有選中的記錄。獲得了那些當前數據沒有參與建模的樹後,構建字隨機森林模型並預測結果,最後經過真實數據與預測數據,計算OOB錯誤評估。對於迴歸問題,使用平方錯誤;分類問題,使用錯誤率。固然,錯誤的評估方式,後面還能夠擴展。

 

變量重要性

原理

如何描述變量重要性,一種直觀的理解是變量越重要,若是混淆它,那麼模型效果會越差。混淆處理有幾種方案,好比根據某種隨機分佈,加減隨機值,或者隨機填充值。可是須要額外的參數,而且會影響原有的數據分佈。因此,採起隨機排序混淆變量,這樣不會改變原始的數據分佈,也不須要而外的參數。

具體作法是以下:

  1. 計算總體OOB(D)
  1. 選取變量i,隨機排序,計算OOB(Di)
  2. 針對全部變臉,重複步驟2,
  3. 重要性I(i) = OOB(Di)-OOB(D)

I(i)越高,說明變量i越重要;若是I(i) = 0,那麼說明變量i沒有什麼做用;若是I(i) < 0,那麼說明變量i有很明顯的噪音,對模型產生了負面影響。

 

實現

實現的難點是隨機排序,在大規模分佈式數據上,實現隨機排序至少須要一次排序,會很是消耗計算資源。擴展代碼中使用了一個小技巧,利用spark隨機森林的內部結構TreePoint,避免排序,提升隨機排序效率。由於TreePoint是裝箱數據,每一個變量的值是箱索引,通常不超過100個。因此只須要將箱索引進行隨機排序,就能夠達到對整個數據進行隨機排序的目的。

private def computeVariableImportance(

strategy: Strategy,

baggedInput: RDD[BaggedPoint[TreePoint]],

bins: EnhancedRandomForest.BinList,

forest: RandomForestModel,

oobError: Double): Array[Double] = {

 

(0 until bins.size).par.map(featureIndex => {

 

val binCount = if (strategy.categoricalFeaturesInfo.contains(featureIndex)) { // category feature

strategy.categoricalFeaturesInfo(featureIndex)

} else { // continuous feature

bins(featureIndex).size

}

val shuffleBinFeature = Random.shuffle((0 until binCount).toList) // 每一個元素對應shuffle後的數據

val shuffleOneFeatureBaggedInput = baggedInput.map(x => {

val currentFeatureBinIndex = x.datum.binnedFeatures(featureIndex)

x.datum.binnedFeatures(featureIndex) = shuffleBinFeature(currentFeatureBinIndex)

x

})

computeOobError(strategy, shuffleOneFeatureBaggedInput, bins, forest) - oobError

}).toArray

 

}

上面的使用中,使用list.par.map的併發操做,同時對全部的變量計算重要性,具體的調度有spark服務器控制,最大限度利用spark的資源。

 

聚合模型穩定的理論依據

隨機森林背後的主要思想是聚合模型(Ensemble Model)。爲何聚合模型效果好於單一模型(理論推導,請參考附錄【聚合模型錯誤評估】)?直觀的理解,當不少模型進行投票時,有一些模型會犯錯,另一些模型正確,那麼正確的投票會與錯誤的投票抵消,總體上只要最終正確的投票多於錯誤的投票,哪怕多一票,那麼就會獲得正確的結果。因爲相互抵消,因此聚合效果比單一模型穩定。聚合模型中,須要模型間具備較大差別,這樣才能覆蓋數據的不一樣方面,這也是爲何隨機森林在數據的行和列兩個維度上,添加隨機過程,用於增大模型之間的差別。

引用一句諺語,"三個臭皮匠,頂一個諸葛亮",能夠形象的解釋。好比這裏有101個臭皮匠,假設他們對一件事情的判斷正確的機率是0.57,而諸葛亮對這件事情判斷正確的機率是0.9。那麼,假設這101個臭皮匠經過投票判斷,那麼機率能夠達到0.92(R代碼:sum(dbinom(51:101,101,0.57))),比諸葛亮強!

 

總結

經過擴展這兩個功能,從新溫習了臺大《機器學習技法》相關課程,同時在真實數據上檢驗了Random Forest的模型效果。實踐檢驗了整理,學以至用,感受很知足。同時,在閱讀org.apache.spark.mllib.tree源代碼的時候,學習到了一些分佈式數據集上算法實現的技巧。但願這些分享對你有用。

 

參考資料

 

附錄

 

Out Of Bag機率

設N爲樣本大小,那麼N次有放回抽樣中,一次沒有選中的機率能夠表示以下,

   

當N趨近於無求大時,P(OOB)會收斂到常量,下面給出證實,

其中數學常數定義以下:

 

 

聚合模型錯誤評估

下面經過聚合迴歸模型進行簡單推導,gt是相同數據集D中,使用T個算法生成的T個模型中隨機選擇的一個模型,G是這T個模型聚合,使用平均做爲最終結果,有

 

f模型用於生成數據D,須要用T個機器學習算法逼近。如今指望研究(G(x)-f(x))2與avg((gt(x)-f(x))2)的關係。x是固定值,後面的公式爲了簡單,會省略。

 

 

直觀的理解,是誤差(Bias),是方差(Variance),任意單一模型的平方錯誤指望大於等於平均模型的錯誤的平方。並且,模型差別若是越大,那麼方差variance越大,那麼Bias越小,也就是聚合模型與f越接近。

相關文章
相關標籤/搜索