一、前述
決策樹是一種非線性有監督分類模型,隨機森林是一種非線性有監督分類模型。線性分類模型比如說邏輯迴歸,可能會存在不可分問題,但是非線性分類就不存在。
二、具體原理
ID3演算法
1、相關術語
根節點:最頂層的分類條件
葉節點:代表每一個類別號
中間節點:中間分類條件
分枝:代表每一個條件的輸出
二叉樹:每一個節點上有兩個分枝
多叉樹:每一個節點上至少有兩個分枝
2、決策樹的生成:
資料不斷分裂的遞迴過程,每一次分裂,儘可能讓類別一樣的資料在樹的一邊,當樹的葉子節點的資料都是一類的時候,則停止分類。(if else 語句)
3、如何衡量純粹度
舉例:
箱子1:100個紅球
箱子2:50個紅球 50個黑球
箱子3:10個紅球 30個藍球 60綠球
箱子4:各個顏色均10個球
憑人的直覺感受,箱子1是最純粹的,箱子4是最混亂的,如何把人的直覺感受進行量化呢?
將這種純粹度用資料進行量化,計算機才能讀懂
舉例:
度量資訊混亂程度指標:
熵的介紹:
熵公式舉例:
熵代表不確定性,不確定性越大,熵越大。代表內部的混亂程度。
比如兩個集合 A有5個類別 每個類別2個值 則每個概率是0.2
比如B有兩個類別,每個類別5個 ,則每個概率是0.5
顯然0.5大於0.2所以熵大,混亂程度比較大。
資訊熵H(X):資訊熵是夏農在1948年提出來量化資訊的資訊量的。熵的定義如下
n代表種類,每一個類別中,p1代表某個種類的概率*log當前種類的概率,然後將各個類別計算結果累加。
以上例子車禍的資訊熵是-(4/9log4/9+5/9log5/9)
條件熵:H(X,Y)類似於條件概率,在知道X的情況下,Y的不確定性
以上例子在知道溫度的情況下,求車禍的概率。
hot 是3個,其中兩個沒有車禍,一個車禍,則3/9(2/3log2/3+1/3log1/3)。
mild是2個,其中一個車禍,一個沒有車禍,則2/9(1/2log1/2+1/2log1/2)
cool是4個,其中三個車禍,一個沒有車禍,則4/9(3/4log3/4+1/4log1/4)。
以上相加即為已知溫度的情況下,車禍的條件熵。
資訊增益:代表的熵的變化程度
特徵Y對訓練集D的資訊增益g(D,Y)= H(X) - H(X,Y)
以上車禍的資訊熵-已知溫度的條件熵就是資訊增益。
資訊增益即是表示特徵X使得類Y的不確定性減少的程度。
(分類後的專一性,希望分類後的結果是同類在一起)
資訊增益越大,熵的變化程度越大,分的越乾脆,越徹底。不確定性越小。
在構建決策樹的時候就是選擇資訊增益最大的屬性作為分裂條件(ID3),使得在每個非葉子節點上進行測試時,都能獲得最大的類別分類增益,使分類後資料集的熵最小,這樣的處理方法使得樹的平均深度較小,從而有效提高了分類效率。
C4.5演算法:有時候給個特徵,它分的特別多,但是完全分對了,比如訓練集裡面的編號
資訊增益特別大,都甚至等於根節點了,那肯定是不合適的
問題在於行編號的分類數目太多了,分類太多意味著這個特徵本身的熵大,大到都快是整個H(X)了
為了避免這種現象的發生,我們不是用資訊增益本身,而是用資訊增益除以這個特徵本身的熵值,看除之後的值有多大!這就是資訊增益率,如果用資訊增益率就是C4.5
CART演算法:
CART使用的是GINI係數,相比ID3和C4.5,CART應用要多一些,既可以用於分類也可以用於迴歸。
CART假設決策樹是二叉樹,內部結點特徵的取值為“是”和“否”,左分支是取值為“是”的分支,右分支是取值為“否”的分支。這樣的決策樹等價於遞迴地二分每個特徵,將輸入空間即特徵空間劃分為有限個單元,並在這些單元上確定預測的概率分佈,也就是在輸入給定的條件下輸出的條件概率分佈。
CART演算法由以下兩步組成:
- 決策樹生成:基於訓練資料集生成決策樹,生成的決策樹要儘量大;
- 決策樹剪枝:用驗證資料集對已生成的樹進行剪枝並選擇最優子樹,這時損失函式最小作為剪枝的標準。
CART決策樹的生成就是遞迴地構建二叉決策樹的過程。CART決策樹既可以用於分類也可以用於迴歸。本文我們僅討論用於分類的CART。對分類樹而言,CART用Gini係數最小化準則來進行特徵選擇,生成二叉樹。GINI係數其實是用直線段代替曲線段的近似,GINI係數就是熵的一階近似。
公式如下:
比如兩個集合 A有5個類別 每個類別2個值 則每個概率是0.2
比如B有兩個類別,每個類別5個 ,則每個概率是0.5
假設C有一個類別,則基尼係數是0 ,類別越多,基尼係數越接近於1,所以
我們希望基尼係數越小越好
CART生成演算法如下:
輸入:訓練資料集停止計算的條件:
輸出:CART決策樹。
過程:
損失函式:
以上構建好分類樹之後,評價函式如下:
(希望它越小越好,類似損失函式了)
三、解決過擬合問題方法
1、背景
葉子節點的個數作為加權,葉子節點的熵乘以加權的加和就是評價函式這就是損失函式,這個損失函式肯定是越小越好了
如何評價呢?
用訓練資料來計算損失函式,決策樹不斷生長的時候,看看測試資料損失函式是不是變得越低了,
這就是互動式的做調參的工作,因為我們可能需要做一些決策樹葉子節點的剪枝,因為並不是樹越高越好,因為樹如果非常高的話,可能過擬合了。
2、解決過擬合兩種方法
剪枝
隨機森林
3、解決過擬合方法之剪枝
為什麼要剪枝:決策樹過擬合風險很大,理論上可以完全分得開資料(想象一下,如果樹足夠龐大,每個葉子節點不就一個資料了嘛)
剪枝策略:預剪枝,後剪枝
預剪枝:邊建立決策樹邊進行剪枝的操作(更實用)
後剪枝:當建立完決策樹後來進行剪枝操作
預剪枝(用的多)
邊生成樹的時候邊剪枝,限制深度,葉子節點個數,葉子節點樣本數,資訊增益量等樹的高度,每個葉節點包含的樣本最小個數,每個葉節點分裂的時候包含樣本最小的個數,每個葉節點最小的熵值等max_depth min_sample_split min_sample_leaf min_weight_fraction_leaf,max_leaf_nodes max_features,增加min_超參 減小max_超參
後剪枝
葉子節點個數越多,損失越大
還是生成一顆樹後再去剪枝,alpha值給定就行
後剪枝舉例:
4、解決過擬合方法之隨機森林
思想Bagging的策略:
從樣本集中重取樣(有可能存在重複)選出n個樣本在所有屬性上,對這n個樣本建立分類器(ID3、C4.5、CART、SVM、Logistic迴歸等)
重複上面兩步m次,產生m個分類器將待預測資料放到這m個分類器上,最後根據這m個分類器的投票結果,決定待預測資料屬於那一類(即少數服從多數的策略)
在Bagging策略的基礎上進行修改後的一種演算法
從樣本集中用Bootstrap取樣選出n個樣本;
從所有屬性中隨機選擇K個屬性,選擇出最佳分割屬性作為節點建立決策樹;
重複以上兩步m次,即建立m棵CART決策樹;
這m個CART形成隨機森林(樣本隨機,屬性隨機),通過投票表決結果決定資料屬於那一類。
當資料集很大的時候,我們隨機選取資料集的一部分,生成一棵樹,重複上述過程,我們可以生成一堆形態各異的樹,這些樹放在一起就叫森林。
隨機森林之所以隨機是因為兩方面:樣本隨機+屬性隨機
選取過程:
取某些特徵的所有行作為每一個樹的輸入資料。
然後把測試資料帶入到每一個數中計算結果,少數服從多數,即可求出最終分類。
隨機森林的思考:
在隨機森林的構建過程中,由於各棵樹之間是沒有關係的,相對獨立的;在構建
的過程中,構建第m棵子樹的時候,不會考慮前面的m-1棵樹。因此引出提升的演算法,對分錯的樣本加權。
提升是一種機器學習技術,可以用於迴歸和分類的問題,它每一步產生弱預測模型(如決策樹),並加權累加到總模型中;如果每一步的弱預測模型的生成都是依
據損失函式的梯度方式的,那麼就稱為梯度提升(Gradient boosting)提升技術的意義:如果一個問題存在弱預測模型,那麼可以通過提升技術的辦法得到一個強預測模型。
四、程式碼
決策樹:
決策樹的訓練集必須離散化,因為如果不離散化的話,分類節點很多。
package com.bjsxt.rf import org.apache.spark.mllib.tree.DecisionTree import org.apache.spark.mllib.util.MLUtils import org.apache.spark.{SparkContext, SparkConf} object ClassificationDecisionTree { val conf = new SparkConf() conf.setAppName("analysItem") conf.setMaster("local[3]") val sc = new SparkContext(conf) def main(args: Array[String]): Unit = { val data = MLUtils.loadLibSVMFile(sc, "汽車資料樣本.txt") // Split the data into training and test sets (30% held out for testing) val splits = data.randomSplit(Array(0.7, 0.3)) val (trainingData, testData) = (splits(0), splits(1)) //指明類別 val numClasses=2 //指定離散變數,未指明的都當作連續變數處理 //1,2,3,4維度進來就變成了0,1,2,3 //這裡天氣維度有3類,但是要指明4,這裡是個坑,後面以此類推 val categoricalFeaturesInfo=Map[Int,Int](0->4,1->4,2->3,3->3) //設定評判標準 val impurity="entropy" //樹的最大深度,太深運算量大也沒有必要 剪枝 防止模型的過擬合!!! val maxDepth=3 //設定離散化程度,連續資料需要離散化,分成32個區間,預設其實就是32,分割的區間保證數量差不多 這裡可以實現把資料分到0-31這些數中去 這個引數也可以進行剪枝 val maxBins=32 //生成模型 val model =DecisionTree.trainClassifier(trainingData,numClasses,categoricalFeaturesInfo,impurity,maxDepth,maxBins) //測試 val labelAndPreds = testData.map { point => val prediction = model.predict(point.features) (point.label, prediction) } val testErr = labelAndPreds.filter(r => r._1 != r._2).count().toDouble / testData.count()//錯誤率的統計 println("Test Error = " + testErr) println("Learned classification tree model:\n" + model.toDebugString) } }
樣本資料:
將第5列資料離散化。
結果:
深度為3一共15個節點。
隨機森林:
package com.bjsxt.rf import org.apache.spark.{SparkContext, SparkConf} import org.apache.spark.mllib.util.MLUtils import org.apache.spark.mllib.tree.RandomForest object ClassificationRandomForest { val conf = new SparkConf() conf.setAppName("analysItem") conf.setMaster("local[3]") val sc = new SparkContext(conf) def main(args: Array[String]): Unit = { //讀取資料 val data = MLUtils.loadLibSVMFile(sc,"汽車資料樣本.txt") //將樣本按7:3的比例分成 val splits = data.randomSplit(Array(0.7, 0.3)) val (trainingData, testData) = (splits(0), splits(1)) //分類數 val numClasses = 2 // categoricalFeaturesInfo 為空,意味著所有的特徵為連續型變數 val categoricalFeaturesInfo =Map[Int, Int](0->4,1->4,2->3,3->3) //樹的個數 val numTrees = 3 //特徵子集取樣策略,auto 表示演算法自主選取 //"auto"根據特徵數量在4箇中進行選擇 // 1,all 全部特徵 2,sqrt 把特徵數量開根號後隨機選擇的 3,log2 取對數個 4,onethird 三分之一 val featureSubsetStrategy = "auto" //純度計算 val impurity = "entropy" //樹的最大層次 val maxDepth = 3 //特徵最大裝箱數,即連續資料離散化的區間 val maxBins = 32 //訓練隨機森林分類器,trainClassifier 返回的是 RandomForestModel 物件 val model = RandomForest.trainClassifier(trainingData, numClasses, categoricalFeaturesInfo, numTrees, featureSubsetStrategy, impurity, maxDepth, maxBins) //列印模型 println(model.toDebugString) //儲存模型 //model.save(sc,"汽車保險") //在測試集上進行測試 val count = testData.map { point => val prediction = model.predict(point.features) // Math.abs(prediction-point.label) (prediction,point.label) }.filter(r => r._1 != r._2).count() println("Test Error = " + count.toDouble/testData.count().toDouble) } }
結果:
根據DEBUGTREE畫圖舉例: