決策樹演算法預測森林植被

xiaohei.info發表於2016-05-07
版權宣告:本文為博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/qq1010885678/article/details/51336094

演算法介紹

迴歸和分類

迴歸演算法和分類演算法通常會被聯絡在一起,因為兩者都可以通過一個或者多個值來預測一個或者多個值 he
為了能夠做出預測,兩者需要從一組輸入和輸出中學習預測規則,在學習過程中需要告訴它們問題以及問題的答案
因此,迴歸和分類都屬於監督學習類的演算法

迴歸是預測一個數值型的結果,例如溫度,成績等
分類是預測一個標號或者類別,例如郵件是否為辣雞郵件,一個人是屬於哪個人種

這裡將使用決策樹隨機森林演算法來完成工作,這兩種演算法靈活且被廣泛應用
既可以用於分類問題也可以用於迴歸問題

特徵識別

假設我們要通過某飯店商家今天的銷售額來預測明天的銷售額,使用機器學習演算法完全沒有問題
但是在這之前我們要做一些操作,今天的銷售額是一個非常廣泛的概念,無法直接應用到演算法中
那麼我們就需要對今天的銷售額進行特徵提取和識別,這些特徵有助於我們預測明天的銷售額
例如:

  • 今天的最低人流量:20
  • 今天的最高人流量:200
  • 今天平均上菜速度:3
  • 忙時是早上、中午還是晚上:中午
  • 隔壁飯店的平均人流量:60

等等,特徵有時候也被稱為維度或者預測指標,以上的每個特徵都能夠被量化,例如
今天的最低人流量為20,單位人
今天平均上菜速度為3,單位分鐘

因此,今天的銷售額就可以簡化為一個值列表:
20,200,3,中午,60

這幾個特徵順序排列就組成了特徵向量,一個特徵向量可以用來描述該商家每天的銷售額

關於如何提取特徵,要根據實際的業務場景來操作,提取出的特徵能夠貼切的描述具體的業務場景,這個過程甚至需要業務專家的參與

相信你已經注意到了,示例中的特徵向量中並不是每個值都是數值型,例如中午
在實際應用中,這種情況很常見,通常把特徵分為兩種:

  • 數值型特徵:可以直接使用數字表示,數字的小大是有意義的
  • 類別型特徵:在幾個離散值中選取一個,離散值之間的大小是沒有意義的

訓練樣本

為了進行預測,監督學習演算法需要在大量的資料集上進行訓練,這些資料集不僅要包含資料特徵輸入,還要包含正確的輸入
如上例中的特徵向量是不能夠被作為訓練樣本的,因為其沒有正確的輸出–今天的銷售額
如果將今天的銷售額2000也作為一個特徵加入該資料中,該資料就能夠作為一個訓練樣本,為機器學習演算法提供了一個結構化的輸入:
20,200,3,中午,60,2000

注意,這裡的輸出是一個數值型
迴歸和分類的區別在於:迴歸的目標為數值型特徵,分類的目標為類別型特徵
並不是所有的迴歸、分類演算法都能夠處理類別型特徵或者類別型目標,有些演算法只能處理數值型

我們可以通過適當的轉換規則將類別型特徵轉換為數值型特徵,例如:

  • 早上:1
  • 中午:2
  • 晚上:3

用數字123分別代表早上、中午和晚上,那麼該訓練樣本就可以表示為:
20,200,3,2,60,2000

注意:有些演算法對對數值型特徵的大小進行猜測,一旦將類別型特徵轉換為數值型特徵可能會造成一些影響,因為類別型特徵本身的大小是沒有意義的

決策樹

決策樹演算法家族能夠自然的處理署執行和類別型特徵
什麼是決策樹呢?
事實上,我們在生活中無意中就會用到決策樹所體現的推理方法,例如:
看到一部滿意的新手機,但是舊手機還能用,買還是不買呢?
於是我會經歷一下的過程:

1.舊手機是否已經無法忍受?是,就買
2.上一步為否,那麼新手機是否能夠達到非買不可的地步?是,就買
3.上一步為否,新手機的價格是否達到可以隨意購買的地步?是,就買
4.上一步為否,新手機的綜合價效比能否和舊手機甩開一個距離?是,就買
5.上一步為否,那麼就不買

可以看到,決策樹推理的過程就是一系列的是/否,在程式中表達就是一堆if/else
但是決策樹有個很嚴重的缺點過度擬合,如何理解?
即,用訓練資料訓練出來的模型在訓練資料中表現優異,但是對新的資料無法做出合理的預測
決策樹訓練出來的模型可能會對訓練資料有過度擬合的問題

隨機森林

隨機森林是由多個隨機的決策樹組成的,何為隨機的決策樹?
隨機森林中的每顆決策樹中,使用的訓練資料是總訓練資料中隨機的一部分,每層使用的決策規則都是隨機選定的
隨機森林的做法是屬於集體智慧的,集體的平均預測應該比任何一個個體的預測要準確
正是因為隨機森林構建過程中的隨機性,才有了這種獨立性,這是隨機森林的關鍵

因為每顆決策樹所使用的決策都是隨機的一部分,所以隨機森林得以有足夠的時間來構建多顆決策樹
也正是因為如此,森林中的每棵樹更不會產生過度擬合的問題,因為使用的決策是隨機的一部分

隨機森林的預測是所有決策樹的加權平均

程式開發

資料集

示例中將使用Covtype的森林植被資料集,該資料集是公開的,可以線上下載
該資料集的每個樣本描述每塊土地的若干特徵,包括海拔、坡度、到水源的距離、遮陽情況和土壤型別等54個特徵,並給出了目標特徵–每塊土地的森林植被型別

Spark Mllib的決策樹

這裡將使用Spark中的Mllib機器學習庫,Mllib將特徵向量抽象為LabeledPoint,關於LabeledPoint的具體介紹請看:
Spark(十一) – Mllib API程式設計 線性迴歸、KMeans、協同過濾演示

LabeledPoint中的Vector本質上是對多個Double型別的抽象,所以LabeledPoint只能處理數值型特徵,樣本資料集中已經將類別型的特徵轉換為數值型特徵了
Mllib中的DecisionTree的輸入為LabeledPoint型別,所以讀取資料之後,我們需要將資料轉換為LabeledPoint

val conf = new SparkConf().setAppName("DecisionTree")
val sc = new SparkContext(conf)
//讀取資料
val rawData = sc.textFile("/spark_data/covtype.data")
//轉換為為LabeledPoint
val data = rawData.map { line =>
  val values = line.split(",").map(_.toDouble)
  //init返回除了最後一個元素的所有元素,作為特徵向量
  val feature = Vectors.dense(values.init)
  //返回最後一個目標特徵,由於決策樹的目標特徵規定從0開始,而資料是從1開始的,所以要-1
  val label = values.last - 1
  LabeledPoint(label, feature)
}

為了能夠評估訓練出來的模型的準確度,我們可以將資料集劃分為三部分:訓練集、交叉驗證集和測試集,各佔80%,10%和10%

val Array(trainData, cvData, testData) = data.randomSplit(Array(0.8, 0.1, 0.1))
trainData.cache()
cvData.cache()
testData.cache()

接下來我們先訓練出一個決策樹模型來看看豬長什麼樣

/**
  * 獲得評估指標
  *
  * @param model 決策樹模型
  * @param data  用於交叉驗證的資料集
  **/
def getMetrics(model: DecisionTreeModel, data: RDD[LabeledPoint]): MulticlassMetrics = {
  //將交叉驗證資料集的每個樣本的特徵向量交給模型預測,並和原本正確的目標特徵組成一個tuple
  val predictionsAndLables = data.map { d =>
    (model.predict(d.features), d.label)
  }
  //將結果交給MulticlassMetrics,其可以以不同的方式計算分配器預測的質量
  new MulticlassMetrics(predictionsAndLables)
}

val model = DecisionTree.trainClassifier(trainData, 7, Map[Int, Int](), "gini", 4, 100)
val metrics = getMetrics(model, cvData)

DecisionTree有兩種訓練模型的方法:trainClassifier和trainRegressor分別對應分類和迴歸問題
trainClassifier的引數:

1.訓練資料集
2.目標類別個數,即結果有幾種選擇
3.Map中的鍵值分別對應Vector下標和該下標對應類別特徵的取值情況,空表示所有特徵都是數值型(為了方便,示例中直接取空,實際當中並不能這麼使用)
4.不純性(impurity)度量:gini或者entropy,不純度用來衡量一個規則的好壞,好的規則可以將資料劃分為等值的兩部分,壞規則則相反
5.決策樹的最大深度,越深的決策樹越有可能產生過度擬合的問題
6.決策樹的最大桶數,每層使用的決策規則的個數,越多就可能精確,花費的時候也就越多,最小的桶數應該不小於類別特徵中最大的選擇個數

現在來看看metrics指標中的混淆矩陣:

System.out.println(metrics.confusionMatrix.toString())

結果為:

14411.0  6564.0   17.0    1.0    0.0   0.0  317.0
5444.0   22158.0  449.0   22.0   4.0   0.0  43.0
0.0      415.0    3022.0  95.0   0.0   0.0  0.0
0.0      0.0      159.0   112.0  0.0   0.0  0.0
0.0      895.0    34.0    0.0    14.0  0.0  0.0
0.0      422.0    1228.0  108.0  0.0   0.0  0.0
1112.0   28.0     0.0     0.0    0.0   0.0  903.0

因為目標類別有7個,所以混淆矩陣是一個7*7的矩陣,每一行對應一個正確的目標特徵值,每一列對應一個預測的目標特徵
第i行第j列表示一個正確類別為i的樣本被預測為j的次數,所以對角線上的元素代表預測正確的次數,其他表示預測錯誤的次數
另外,模型的精準度也可以用一個數字來表示:

System.out.println(metrics.precision)

結果為0.6996041965535718,準確率大概在69%左右

除了準確度,還有召回率等概念,還可以為每個目標特徵單獨檢視其準確度等資訊:

(0 until 7).map(target => (metrics.precision(target), metrics.recall(target))).foreach(println)

下面為輸出結果:

(0.6845289541918755,0.6708390193402664)
(0.7237410535817912,0.7904577464788732)
(0.6385618166526493,0.8483240223463687)
(0.5800865800865801,0.44966442953020136)
(0.0,0.0)
(0.7283950617283951,0.03460410557184751)
(0.6814580031695721,0.4405737704918033)

可以看到,每個類別的準確度都不同,而且很詭異的是第5個類別的準確度為0,所以這個模型並不是我們想要的
我們想要的是一個符合實際的,準確度儘可能高的模型,那麼該如何確定模型訓練的引數呢?
可以在訓練模型的時候選擇不同的組合,並反饋出每個組合訓練處的模型準確度結果,那麼我們就可以從中選出最好的那個模型來使用:

/**
  * 在訓練資料集上得到最好的引數組合
  * @param trainData 訓練資料集
  * @param cvData 交叉驗證資料集
  * */
def getBestParam(trainData: RDD[LabeledPoint], cvData: RDD[LabeledPoint]): Unit = {
  val evaluations = for (impurity <- Array("gini", "entropy");
                         depth <- Array(1, 20);
                         bins <- Array(10, 300)) yield {
    val model = DecisionTree.trainClassifier(trainData, 7, Map[Int, Int](), impurity, depth, bins)
    val metrics = getMetrics(model, cvData)
    ((impurity, depth, bins), metrics.precision)
  }
  evaluations.sortBy(_._2).reverse.foreach(println)
}

執行的結果為:

((entropy,20,300),0.9123611325743918)
((gini,20,300),0.9062140490049623)
((entropy,20,10),0.8948814368378578)
((gini,20,10),0.8902625388485379)
((gini,1,300),0.6352272532151995)
((gini,1,10),0.6349525232232697)
((entropy,1,300),0.4855337488624461)
((entropy,1,10),0.4855337488624461)

可以看到最好的組合為第一個,準確度在91%左右,我們可以再檢視一下使用最好引數訓練出來的模型,各個類別的準確度為多少:

(0.899412455934195,0.9029350698376746)
(0.9193229901269393,0.9203289914928166)
(0.9222857142857143,0.9238694905552376)
(0.8263888888888888,0.8623188405797102)
(0.8294663573085846,0.7663451232583065)
(0.8596802841918295,0.8491228070175438)
(0.9454369869628199,0.9275225011842728)

現在看來比之前的要好上很多了吧

直到這裡,我們一直沒有使用到10%的測試資料集,如果說10%的交叉驗證資料集的作用是確定在訓練資料集上訓練出來的模型的最好引數
那麼測試資料集的作用就是評估CV資料集的最好引數,可以將訓練集和CV集結合起來,然後對測試集進行以上的步驟驗證來得到一個合適的引數組合

隨機森林

在Spark Mllib中使用隨機森林的方式和決策樹差不多:

val forest = RandomForest.trainClassifier(trainData, 7, Map(10 -> 4, 11 -> 40), 20, "auto", "entropy", 30, 300)

和構架決策樹的時候不同,這裡不再使用空的Map
Map(10 -> 4, 11 -> 40)表示特徵向量中下標為10的是類別型特徵,類別取值有4個;下標為11的類別特徵向量取值有40個
此時,隨機森林不會再對10和11這兩個特徵向量做出“大小推測”等數值型特徵才會有的操作
這樣明顯更加貼近實際,所得到的模型質量也會更好

此外隨機森林還多了兩個引數:

1.森林中有多少顆決策樹,示例中取值為20
2.每層決策規則的選擇方法,示例中為”auto”

按照決策樹的評估流程對隨機森林模型進行測試,可以得到關於準確度的一些指標進而衡量模型質量

至此,關於決策樹和隨機森林演算法的模型已經構建完畢,可以使用這個模型對資料進行預測

Github原始碼地址

作者:@小黑


相關文章