【機器學習】--決策樹和隨機森林

LHBlog發表於2018-03-27

一、前述

決策樹是一種非線性有監督分類模型,隨機森林是一種非線性有監督分類模型。線性分類模型比如說邏輯迴歸,可能會存在不可分問題,但是非線性分類就不存在。
二、具體原理

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演算法由以下兩步組成:

  1. 決策樹生成:基於訓練資料集生成決策樹,生成的決策樹要儘量大;
  2. 決策樹剪枝:用驗證資料集對已生成的樹進行剪枝並選擇最優子樹,這時損失函式最小作為剪枝的標準。

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畫圖舉例:

 

相關文章