Spark 數據挖掘 - 利用決策樹預測森林覆蓋面積

jopen 9年前發布 | 32K 次閱讀 Spark

Spark 數據挖掘—利用決策樹預測森林覆蓋面積

1 前言

預測問題記住一點:最垃圾的預測就是使用平均值,如果你的預測連比直接給出平均值效果都要差,那就省省吧!
統計學誕生一個多世紀之后,隨著現在機器學習和數據科學的產生,我們依舊使用回歸的思想來進行預測,盡管回歸就是用平均值向后不斷回滾來預測。回歸的技術和分類的技術緊密相關。通常情況下,當目標變量是連續數值時指的是回歸,例如預測身高和體重。當預測的目標變量是名義或者說是類別變量時,指的就是分類,例如預測郵件是否是垃圾郵件。
無論是分類還是回歸,都需要給定已知信息去預測未知信息,所以它們都需要從輸入輸出來學習。它們需要包括問題和答案。這類算法因此也稱為監督學習的方法。
回歸和分類是使用年代最近研究的最充分的預測分析技術。很多算法開源包都包含通用的這些方法。比如:支持向量機,邏輯回歸,樸素貝葉斯,神經網絡和深度學習。
本文的重點是討論:決策樹和它的擴展隨機森林。決策樹是通用而且靈活的分類回歸算法。

2 一些基本概念

注意:特別小心分類變量(尤其是那些用數字表示的分類變量,不要隨便放到算法中去訓練)和數值變量
注意:不是所有的算法都能處理分類變量,或者都能處理回歸分類問題,但是放心決策樹都可以

3 算法簡介

決策樹



算法名字 決策樹,DT,Decision Trees
算法描述 適合任何數據類型的分類和回歸算法 關鍵概念:樹、信息增益、停止條件、過擬合
算法原理 根據已有樣本訓練得到一顆樹,樹的葉子節點就是預測的值,新的樣本將按照樣本的值遍歷樹的相對應節點最后到達葉子節點,葉子節點的值就是新樣本的預測值。<br/> 決策樹原理非常簡單,如下圖:<br/> 決策樹示例圖
使用場景 強大的算法,適合回歸和預測的很多場合
算法優缺點 優點: 1. 能夠自然處理分類和數值特征 2. 容易并行計算 3. 對異常值具有很好的魯棒性(意味著小量的異常值或者錯誤數據不影響分類結果) 4. 可以處理不同類型和不同維度的數據不需要數據預處理或者預先正則化數據 5. 結果容易理解和解釋<br /> 缺點: 1. 容易過擬合(不過它的變種隨機森林已經改進了這個問題)
數據類型 數值型和標稱型數據
參考資料 1. 算法原理 機器學習實戰 <br /> 2. MLlib實現 DesisionTree<br /> 3. MLlib實現 RandomForest

4 數據集

本文將使用著名的 Covtype 數據集合,可以在 http://bit.ly/1KiJRfg 這里下載。下載之后是一個壓縮的 csv 文件, linux 用戶可以用命令:tar -xzvf 解壓縮,windows用戶可以使用 .7-zip 解壓縮,同時下載數據集的描述文件 covtype.info 數據集記錄的是美國 Colorado 植被覆蓋類型數據,也是唯一一個關心真實森林的數據。每條記錄都包含很多指標描述每一塊土地。例如:高度、坡度、到水的距離、樹蔭下的面積、土壤的類型等等。森林的覆蓋類型是需要根據其他54個特征進行預測的特征。這是一個有趣的數據集,它包含分類和數值特征。總共有581012條記錄。每條記錄有55列,其中一列是土壤的類型,其他54列是輸入特征。雖然這個數據集還不能算得上真正的大數據,但是也能說明很多問題。很幸運,這個數據集已經是csv文件,所以不需要太多的數據清洗或者其他的準備就可以給 Spark Mllib 使用。數據集可上傳到 HDFS,當然也可以先放到本地進行這個有趣的測試。不管哪種方式,Spark 都只需要改變一兩個參數。這里不得不再次提醒一個問題,分類變量如何編碼,下面是編碼的方式:

  • 一個合適的編碼方式是:one-hot 或者 1 of n 編碼 一個分類變量:編碼為 n(分類特征個數)個變量
  • 另一種編碼方式:就是給每個值一個固定的數字,例如: 1, 2, 3, ..., n

當算法中把編碼當作數字的時候只能使用第一種編碼,第二種編碼會得出滑稽的結果。具體原因是沒有大小的東西被強制成有大小之分。
Covtype 數據集中有很多類型的特征,不過很幸運,它已經幫我們轉換成 one-hot 形勢,具體來說:

  • 11到14列,其實表示的是 Wilderness_Area,Wilderness_Area 本身有 4 個類別
  • 15到54列,其實表示的是 Soil_Type,Soil_Type 本身有 40個屬性值
  • 55列是表示目標值,當然它不需要表示成為 one-hot形式。

這個數據集每一列的變量單位都不一定相同,有的表示距離,有的表示度數等等

5 Spark 決策樹模型

下面給出一個初步的利用Spark MLlib 實驗的決策樹模型,具體的意圖,代碼都有詳細的注釋:

 //本地測試
  val rootDir = "your sample data directory"
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("SparkInAction").setMaster("local[4]")
    val sc = new SparkContext(conf)
    val covTypeData = sc.textFile(rootDir + "/covtype.data")
    val data = dataPrepare(covTypeData)
    //選擇測試集合和訓練集合
    val Array(train, cvData, test) =
      data.randomSplit(Array(0.8, 0.1, 0.1))
    train.cache()
    cvData.cache()
    test.cache()

    val model = buildDecisionTree(train, cvData)
  }


  /**
   * Spark MLlib 表示特征向量的對象是 LabeledPoint
   * 這個對象由表示特征的向量和目標變量組成
   */
  def dataPrepare(data: RDD[String]) = {
    val sample = data.map{
      line =>
        //全部數據轉化為 double
        val array = line.split(",").map(_.toDouble)
        //前面54列是特征向量,后面一列是目標變量 label
        val featureVector = Vectors.dense(array.init)
        //決策樹目標變量必須從0開始,按照1遞增
        LabeledPoint(array.last - 1, featureVector)
    }
    sample
  }

  /**
   * 決策樹模型建立,這里很容易改進為十擇交叉驗證。
   * 對每一份數據建立模型時,都需要隨機選出部分數據來調整模型參數到最優。
   * 通過交叉驗證的方式調整參數。
   * @param train
   * @param cvData
   */
  def buildDecisionTree(train: RDD[LabeledPoint], cvData: RDD[LabeledPoint]) = {
    def getMetrics(model: DecisionTreeModel, data: RDD[LabeledPoint]) = {
      val predictionsAndLabels = data.map {
        example =>
          (model.predict(example.features), example.label)
      }
      new MulticlassMetrics(predictionsAndLabels)
    }
    val model = DecisionTree.trainClassifier(
      train, 7, Map[Int, Int](), "gini", 4, 100
    )
    val matrics = getMetrics(model, cvData)
    println(matrics.confusionMatrix)
    (0 until 7).map(
      cat => (matrics.precision(cat), matrics.recall(cat))
    ).foreach(println)
  }

這個是初步的運行結果:

#整體的準確率和召回率
(0.7012384971978136,0.7012384971978136)
#每一個類別的準確率和召回率
(0.685108051158916,0.6668097486526446)
(0.7255299659774928,0.7930627570177007)
(0.6194455768446799,0.8685338668190912)
(0.3771043771043771,0.39436619718309857)
(0.55,0.011727078891257996)
(0.0,0.0)
(0.7174657534246576,0.4134188455846078)


70%的準確率和召回率似乎效果還不錯,但是我們現在不能盲目的認為我們的效果就真的不錯了,有時候瞎猜效果也會不錯。例如:70%的數據屬于類別1,每次都猜測類別是1,那么效果也能達到70%的準確率,下面我們確定一下瞎猜的準確率:回答瞎猜猜對的概率,這個問題也不是簡單的,回到概率論課堂上,在訓練樣本每類概率已知的情況下,測試樣本瞎猜對的概率有多大呢?隨機給出一個樣本:猜測類A的概率是由訓練樣本決定的,同時猜對的概率是由測試樣本決定的,所以瞎猜猜對的概率是訓練樣本每類的概率分別乘以測試樣本對應類的概率之和

 /**
   * 獲取模型瞎猜的概率
   * @param train 測試數據集
   * @param cvData 驗證數據集
   */
  def guessProb(train: RDD[LabeledPoint], cvData: RDD[LabeledPoint]) {
    /**
     * 返回數據集合中,每一個類別的概率
     * @param data 訓練數據集
     */
    def labelProb(data: RDD[LabeledPoint]): Array[Double] = {
      val labelCnt = data.map(_.label).countByValue()
      val labelProb = labelCnt.toArray.sortBy(_._1).map(_._2)
      labelProb.map(_.toDouble/labelProb.sum)
    }

    val trainProb = labelProb(train)
    val cvProb = labelProb(cvData)
    val prob = trainProb.zip(cvProb).map {
      case (a, b) => a * b
    }.sum
    println(prob)
  }

可以看到瞎猜的結果只有:0.3771281350885772 的準確率。說明70%的準確率效果確實不錯,但是請注意,我們還沒有優化參數,說明我們的模型還有優化的空間。

6 決策樹參數選擇

主要的參數有下面幾個:

  • Maximum Depth: 決策樹樹的最大深度,控制深度防止過擬合
  • 決策樹訓練算法迭代最大次數
  • 純度測量算法 Gini Entropy (Gini純度和熵)通過反復查看不同參數模型評估效果,下面給出測試代碼:
/**
  * 模型評估
  * @param trainData 訓練數據
  * @param cvData 交叉驗證數據
  */
 def evaluate(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 predictionsAndLabels = cvData.map(example =>
           (model.predict(example.features), example.label)
         )
         val accuracy =
           new MulticlassMetrics(predictionsAndLabels).precision
         ((impurity, depth, bins), accuracy)
       }
   evaluations.sortBy(_._2).reverse.foreach(println)
 }

來自:http://my.oschina.net/u/1244232/blog/525145

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!