MLlib中決策樹演算法的實現原理解析
決策樹作為一種分類迴歸演算法,在處理非線性、特徵值缺少的資料方面有很多的優勢,能夠處理不相干的特徵,並且對分類的結果通過樹的方式有比較清晰的結構解釋,但是容易過擬合,針對這個問題,可以採取對樹進行剪枝的方式,還有一些融合整合的解決方案,比如隨機森林RandomForest、GBDT (Gradient Boost Decision Tree)等,對於隨機森林、GBDT在後面的章節進行介紹
模型的訓練過程其實是決策樹的構造過程,它採用自頂向下的遞迴方式,在決策樹的內部結點進行屬性值的比較,並根據不同的屬性值判斷從該結點向下分支,進行遞迴進行劃分,直到滿足一定的終止條件(可以進行自定義),其中葉結點是要學習劃分的類。在當前節點用哪個屬性特徵作為判斷進行切分(也叫分裂規則),取決於切分後節點資料集合中的類別(分割槽)的有序(純)程度,劃分後的分割槽資料越純,那麼當前分裂規則也合適。衡量節點資料集合的有序無序性,有熵、基尼Gini、方差,其中熵和Gini是針對分類的,方差是針對迴歸的。
這裡介紹一下常用的熵以及資訊增益。
熵代表集合的無序性的引數,熵越大,代表越無序、越不純。熵的公式如下:
其中c表示類別,是樣本集合中屬於類別i的概率
在決策樹分類中,一般是用資訊增益infoGain來作為決策樹節點特徵屬性劃分的依據,採用使得資訊增益最大的屬性作為資料劃分的度量依賴。資訊增益infoGain定義如下:
其中V(A)代表屬性A的分割槽,S代表樣本集合,是S中屬性A的值屬於v分割槽的樣本集合
決策樹的演算法實現在學術界有ID3,C4.5,CART等, ID3採用資訊增益作為屬性選擇的度量,參見上面的Gain,這種方式的一個缺點是在計算資訊增益時,傾向於選擇具有大量值的屬性,因此提出了C4.5的基於資訊增益率的度量,而CART使用基尼Gini指數作為屬性選擇的度量,這些演算法之間的差別主要包括在訓練建立決策樹過程中如何選擇屬性特徵,以及剪枝的機制處理。這些演算法相關的定義可以參考相關的材料,這裡不做介紹了。
Spark MLlib對決策樹提供了二元以及多label的分類以及迴歸的支援,支援連續型和離散型的特徵變數。這裡的決策樹是一顆二叉樹的,因此資訊增益infoGain就為:
這裡講一下MLlib中的bin和split概念,split為切割點,對應二叉的決策樹,bin為桶或者箱子數,一個split把資料集劃分成2個桶,所以bin是split的2倍。
在決策樹的訓練時,需要兩個重要的引數,
maxBins:每個特徵分裂時,最大劃分(桶)數量
maxDepth:樹的最大高度
在MLlib中,基本的樣本訓練決策樹的構建流程為:
尋找所有特徵的可能的劃分
為了防止過擬合,需要考慮剪枝,這裡採用的是前向剪枝,當任一以下情況發生,MLlib的決策樹節點就終止劃分,形成葉子節點:
1、 樹高度達到maxDepth
2、 minInfoGain,當前節點的所有屬性分割帶來的資訊增益都比這個值要小
3、 minInstancesPerNode,需要保證節點分割出的左右子節點的最少的樣本數量達到這個值
針對下面的一個樣本集合:- age revenue stdudent …. Label
- youth 1 yes 1
- middle 8 no 1
- senior 2 no 0
- senior 7 yes 0
- middle 8 no …. 1
- ……
選擇哪個特徵進行劃分,需要針對當前節點資料集合的每一個特徵,求得資訊增益,而根據公式3的資訊增益的計算,需要尋找切分點split來計算分割槽;對當前特徵的每一個劃分進行對應的資訊增益計算,然後取當前特徵的最大的資訊增益值作為該特徵的資訊增益;依次求候選的特徵的資訊增益,再選取具有最大的資訊增益的那個候選特徵作為當前節點的屬性選擇度量即可。
對於連續型的特徵變數,從實現上來講,需要對樣本中的當前特徵值排序,把每一個值都可能作為切分點split_poin,那麼樹的左邊結合為A<=split_poin,右邊集合為>split_poin;但是在實際的場景中樣本量往往多到對特徵值的排序影響系統的開銷,做法是對樣本進行抽樣,對抽樣的樣本對於這一維度的特徵值做排序,然後選擇處於分位位置上的值作為分割值。
比如例子中的revenue特徵,假設一共9個樣本,採用3分位,那麼分割點有以下兩種候選的切分點。
1,1,2,|4,4,6,|7,8,8,
對於離散的分類變數,如若特徵有m個值,最多- //取樣本數量和maxBin的最小值
- val maxPossibleBins = math.min(strategy.maxBins, numExamples).toInt
- //根據maxPossibleBins,利用已下的公式(該公式是無序的公式2* 2^{M-1}-1的反向推導 ),來求m值
- val maxCategoriesForUnorderedFeature =
- ((math.log(maxPossibleBins / 2 + 1) / math.log(2.0)) + 1).floor.toInt
- //當前的特徵數量小於m值,則認為無序
- if (numCategories <= maxCategoriesForUnorderedFeature)
利用spark的分散式、高效迭代的計算框架實現對MLlib決策樹的訓練,有一些可以提及關鍵的優化點:
對於查詢分割點split的並行操作,不是在一個node節點級別的並行,而是在樹的level級別的並行,對於處於同一層次的節點,分割點的查詢是同時並行操作的。也就是查詢次數的複雜度依賴於層級L,而不是節點數2的L次方-1,這樣可以減少IO、計算、以及相關的通訊開銷。
前面講到對於連續的特徵,用排序後的唯一特徵值作為計算最佳分割點的候選,對於大量的分散式的樣本集,這個開銷是很大的。為了提高這方面的效能,同時也不會嚴重降低準確度,MLlib決策樹採用分位數作為分割點候選。
查詢每個節點最佳的分割點split,早期的版本是用map和reduce實現,而目前的實現是通過利用預先計算的分割點候選避免了map操作步驟,來提高計算和通訊的開銷。
對於一些統計資訊的計算都是在桶bins中進行的,由於預先對這些樣本中bin的資訊進行計算,勿需再每次迭代中計算,節省了計算開銷。