4-Spark高級數據分析-第四章 用決策樹算法預測森林植被

預測是很是困難的,更別提預測將來。java

 

4.1 迴歸簡介

 

        隨着現代機器學習和數據科學的出現,咱們依舊把從「某些值」預測「另外某個值」的思想稱爲迴歸。迴歸是預測一個數值型數量,好比大小、收入和溫度,而分類則指預測標號或類別,好比判斷郵件是否爲「垃圾郵件」,拼圖遊戲的圖案是否爲「貓」。算法

        將回歸和分類聯繫在一塊兒是由於二者均可以經過一個(或更多)值預測另外一個(或多個)值。爲了可以作出預測,二者都須要從一組輸入和輸出中學習預測規則。在學習的過程當中,須要告訴它們問題及問題的答案。所以,它們都屬於所謂的監督學習。apache

       分類和迴歸是分析預測中最古老的話題。支持向量機、邏輯迴歸、樸素貝葉斯算法、神經網絡和深度學習都屬於分類和迴歸技術。網絡

        本章將重點關注決策樹算法和它的擴展隨機決策森林算法,這兩個算法靈活且應用普遍,便可用於分類問題,也可用於迴歸問題。更使人興奮的是,它們能夠幫助咱們預測將來,至少是預測咱們尚不願定的事情。好比,根據線上行爲來預測購買汽車的機率,根據用詞預測郵件是不是垃圾郵件,根據地理位置和土壤的化學成分預測哪塊耕地的產量可能更高。dom

 

4.2 向量和特徵

 

      天氣的特徵有:機器學習

         最低氣溫學習

         最高氣溫測試

         平均氣溫大數據

         多雲、有雨、晴朗編碼

       特徵有時也被稱爲維度、預測指標或簡單地稱爲變量。本書將特徵只分爲兩大類:類別型特徵和數值型特徵。

       特徵值按順序排列,就是所謂的特徵向量。

 

4.3 樣本訓練

       

        某一天,氣溫12~16攝氏度,溼度10%,晴朗,沒有寒流預報,次日最高氣溫爲17.2攝氏度。

        特徵向量爲學習算法的輸入提供一種結構化的方式(好比輸入:12.5,15.5,0.10,晴朗,0)。預測的輸出(即目標)也被稱爲一個特徵,它是一個數值特徵:17.2。

        一般咱們把目標做爲特徵向量的一個附加特徵。所以能夠把整個訓練樣本列示爲:12.5,15.5,0.10,晴朗,0,17.2。全部這些樣本的集合稱爲訓練集。

        迴歸和分析的區別在於:迴歸問題的目標爲數值型特徵,而分類問題的目標爲類別型特徵。並非全部的迴歸或分類算法都可以處理類別型特徵或類別型目標,有些算法只能處理數值型特徵。

 

4.4 決策樹和決策森林

 

        決策樹算法家族能天然地處理類別型和數值型特徵。決策樹算法容易並行化。它們對數據中的離羣點具備魯棒性,這意味着一些極端或可能錯誤的數據點根本不會對預測產生影響。算法能夠接受不一樣類型和量綱的數據,對數據類型和尺度不一樣的狀況不須要作預處理和規範化。

        決策樹能夠推廣爲更強大的決策森林算法。本章將會把Spark MLib的DecisionTree和RandomForest算法實現應用到一個數據集上。

        基於決策樹的算法還有一個優勢,那就是理解和推理起來相對直觀。

 

4.5 Covtype數據集

 

       下載地址是https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/

       或者http://pan.baidu.com/s/1gfkDKlH

       covtype.data.gz爲壓縮數據文件,附帶一個描述數據文件的信息文件covtype.txt。記錄了美國科羅拉多州不一樣地塊森林植被特徵:海拔、坡度、到水源的距離、遮陽狀況和土壤類型,而且隨同給出了地塊的已知森林植被類型。有581012個樣本。

 

4.6 準備數據

 

        Spark MLib將特徵向量抽象爲LabeledPoint,它由一個包含對個特徵值的Spark MLib Vector和一個稱爲標號(label)的目標值組成。該目標爲Double類型,而Vector本質上是對多個Double類型值的抽象。這說明LabeledPoint只適用於數值型特徵。但只要通過適當編碼,LabeledPoint也可用於類別型特徵。

 

        其中一種編碼是one-hot或1-of-n編碼。在這種編碼中,一個有N個不一樣取值的類別型特徵能夠變成N個數值型特徵,變換後的每一個數值型特徵的取值爲0或1.在這N個特徵中,有且只有一個取值爲1,其餘特徵取值都爲0。

 

4.7 第一棵決策樹

 

開始時咱們原樣使用數據。

 

Scala:

 

import org.apache.spark.mllib.linalg._

import org.apache.spark.mllib.regression._

 

val rawData = sc.textFile("D:/Workspace/AnalysisWithSpark/src/main/java/advanced/chapter4/covtype/covtype.data")

val data = rawData.map { line =>

         val values = line.split(',').map(_.toDouble)

         val featureVector = Vectors.dense(values.init)

         val label = values.last - 1

         LabeledPoint(label, featureVector)

}

  

Java:

 1 public static JavaRDD<LabeledPoint> readData(JavaSparkContext jsc) {
 2 
 3          JavaRDD<String> rawData =jsc.textFile("src/main/java/advanced/chapter4/covtype/covtype.data");
 4 
 5         
 6 
 7          JavaRDD<LabeledPoint> data = rawData.map(line -> {
 8 
 9                    String[] values = line.split(",");
10 
11                    double[] features = new double[values.length-1];
12 
13                    for (int i = 0; i < values.length-1; i++) {
14 
15                             features[i] = Double.parseDouble(values[i]);
16 
17                    }
18 
19                    Vector featureVector = Vectors.dense(features);
20 
21                    Double label = (double) (Double.parseDouble(values[values.length-1]) - 1);
22 
23                    return new LabeledPoint(label, featureVector);
24 
25          });
26 
27          return data;
28 
29 }
30 
31  

 

本章將數據分紅完整的三部分:訓練集、交叉檢驗集(CV)和測試集。在下面的代碼中你會看到,訓練集佔80%,交叉檢驗集和測試集各佔10%。

 

Scala:

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

trainData.cache()

cvData.cache()

testData.cache()

  

 

Java:

 1 //將數據分割爲訓練集、交叉檢驗集(CV)和測試集
 2 
 3 JavaRDD<LabeledPoint>[] splitArray = data.randomSplit(new double[]{0.8, 0.1, 0.1});
 4 
 5 JavaRDD<LabeledPoint> trainData = splitArray[0];
 6 
 7 trainData.cache();
 8 
 9 JavaRDD<LabeledPoint> cvData = splitArray[1];
10 
11 cvData.cache();
12 
13 JavaRDD<LabeledPoint> testData = splitArray[2];
14 
15 testData.cache();
16 
17  

 

和ALS實現同樣,DecisionTree實現也有幾個超參數,咱們須要爲它們選擇值。和以前同樣,訓練集和CV集用於給這些超參數選擇一個合適值。這裏第三個數據集,也就是測試集,用於對基於選定超參數的模型指望準確度作無偏估計。模型在交叉檢驗集上的準確度每每有點過於樂觀,不是無誤差的。本章咱們還將更進一步,以此在測試集上評估最終模型。

 

咱們先來試試在訓練集上構造一個DecisionTreeModel模型,參數採用默認值,並用CV集來計算結果模型的指標:

 

Scala:

import org.apache.spark.mllib.evaluation._

import org.apache.spark.mllib.tree._

import org.apache.spark.mllib.tree.model._

import org.apache.spark.rdd._

 

def getMetrics(model: DecisionTreeModel, data: RDD[LabeledPoint]):

                   MulticlassMetrics = {

         val predictionsAndLabels = data.map(example =>

                   (model.predict(example.features), example.label)

         )

         new MulticlassMetrics(predictionsAndLabels)

}

val model = DecisionTree.trainClassifier(

         trainData, 7, Map[Int,Int](), "gini", 4, 100)

val metrics = getMetrics(model, cvData)

  

 

Java:

 

 1 public static MulticlassMetrics getMetrics(DecisionTreeModel model, JavaRDD<LabeledPoint> data){
 2 
 3          JavaPairRDD<Object, Object> predictionsAndLabels = data.mapToPair(example -> {
 4 
 5                    return new Tuple2<Object, Object>(model.predict(example.features()), example.label());
 6 
 7          });
 8 
 9         
10 
11          return new MulticlassMetrics(JavaPairRDD.toRDD(predictionsAndLabels));
12 
13 }
14 
15  
16 
17 //構建DecisionTreeModel
18 
19 DecisionTreeModel model = DecisionTree.trainClassifier(trainData, 7, new HashMap<Integer, Integer>(), "gini", 4, 100);
20 
21  
22 
23 //用CV集來計算結果模型的指標
24 
25 MulticlassMetrics metrics = getMetrics(model, cvData);
26 
27  

 

這裏咱們使用trainClassifier,而不是trainRegressor,trainClassifier指示每一個LabeledPoint裏的目標應該當作不一樣的類別指標,而不是數值型特徵。

 

和MulticlassMetrics一塊兒,Spark還提供了BinaryClassificationMetrics。它提供相似MulticlassMetrics的評價指標實現,不過僅適用常見的類別型目標只有兩個可能取值的狀況。

 

查看混淆矩陣:

Scala:

metrics.confusionMatrix

 

Java:

 

1 //查看混淆矩陣
2 
3 System.out.println(metrics.confusionMatrix());

 

結果:

14395.0  6552.0   13.0    1.0    0.0   0.0  359.0 

5510.0   22020.0  447.0   29.0   6.0   0.0  36.0  

0.0      413.0    3033.0  82.0   0.0   0.0  0.0   

0.0      0.0      175.0   103.0  0.0   0.0  0.0   

0.0      897.0    33.0    0.0    15.0  0.0  0.0   

0.0      437.0    1210.0  99.0   0.0   0.0  0.0    

1148.0   41.0     0.0     0.0    0.0   0.0  822.0 

 

你獲得的值可能稍有不一樣。構造決策樹過程當中的一些隨機選項會致使分類結果稍有不一樣。

矩陣每一行對應一個實際的正確類別值,矩陣每一列按序對應預測值。第i行第j列的元素表明一個正確類別爲i的樣本被預測爲類別爲j的次數。所以,對角線上的元素表明預測正確的次數,而其餘元素則表明預測錯誤的次數。對角線上的次數可能是好的。但也確實出現了一些分類錯誤的狀況,好比上面結果中沒將任何一個樣本類別預測爲6。

 

查看準確度(或者說精確度):

Scala:

metrics.precision

 

Java:

1 //查看準確度
2 
3 System.out.println(metrics.precision());

 

結果:

0.7022196201332805

 

每一個類別相對其餘類別的精確度

Scala:

 

(0 until 7).map(

cat => (metrics.precision(cat), metrics.recall(cat))

).foreach(println)

  

Java:

 

1 //每一個類別相對其餘類別的精確度
2 
3 Arrays.asList(new Integer[]{0,1,2,3,4,5,6}).stream()
4 
5 .forEach(cat -> System.out.println(metrics.precision(cat) + "," + metrics.recall(cat)));

 

結果:

0.6738106679481018,0.6707807118254879

0.7300201914935192,0.7838858581619806

0.6303782269361617,0.8536585365853658

0.4672489082969432,0.3835125448028674

0.0,0.0

0.67,0.03877314814814815

0.6796116504854369,0.4454233969652472

 

有此能夠看到每一個類型準確度各有不一樣。

 

增長一個準確度評估:

 

Scala:

 

import org.apache.spark.rdd._

def classProbabilities(data: RDD[LabeledPoint]): Array[Double] = {

         val countsByCategory = data.map(_.label).countByValue()

         val counts = countsByCategory.toArray.sortBy(_._1).map(_._2)

         counts.map(_.toDouble / counts.sum)

}

 

val trainPriorProbabilities = classProbabilities(trainData)

val cvPriorProbabilities = classProbabilities(cvData)

trainPriorProbabilities.zip(cvPriorProbabilities).map {

         case (trainProb, cvProb) => trainProb * cvProb

}.sum

  

Java:

 1 public static List<Double> classProbabilities(JavaRDD<LabeledPoint> data) {
 2 
 3          //計算數據中每一個類的樣本數:(類別,樣本數)
 4          Map<Double, Long> countsByCategory = data.map( x -> x.label()).countByValue();
 5 
 6          //排序
 7          List<Map.Entry<Double, Long>> categoryList = new ArrayList<>(countsByCategory.entrySet());
 8          Collections.sort(categoryList, (m1, m2) -> m1.getKey().intValue()-m2.getKey().intValue());
 9 
10          //取出樣本數
11          List<Long> counts = categoryList.stream().map(x -> x.getValue()).collect(Collectors.toList());
12          Double sum = counts.stream().reduce((r, e) -> r = r + e ).get().doubleValue();
13          return counts.stream().map(x -> x.doubleValue()/sum).collect(Collectors.toList());
14 }
15 
16  
17 
18 List<Double> trainPriorProbabilities = classProbabilities(trainData);
19 List<Double> cvPriorProbabilities = classProbabilities(cvData);
20 
21 //把訓練集和CV集中的某個類別的機率結成對,相乘而後相加
22 List<Tuple2<Double, Double>> mergePriorProbabilities = new ArrayList<>();
23 Arrays.asList(new Integer[]{0,1,2,3,4,5,6}).stream().forEach(i -> mergePriorProbabilities.add(new Tuple2<Double, Double>(trainPriorProbabilities.get(i), cvPriorProbabilities.get(i))));
24 System.out.println(mergePriorProbabilities.stream().map(x -> x._1*x._2).reduce((r,e) -> r=r+e).get());

 

結果:

0.3762360562429304

 

隨機猜猜的準確度爲37%,因此咱們前面獲得的70%的準確度看起來還不錯。若是在決策樹構建過程當中試試超參數的其餘值,準確度還能夠提升。

 

4.8 決策樹的超參數

 

控制決策樹選擇過程的參數爲最大深度、最大桶數和不純性度量。

最大深度只是對決策樹的層數作出限制。限制判斷次數有利於避免對訓練數據產生過擬合。

好規則把訓練集數據的目標值分爲相對是同類或「純」(pure)的子集。選擇最好的規則也就意味着最小化規則對應的兩個子集的不純性。不純性有兩種經常使用度量方式:Gini不純度或熵。

Gini不純度直接和隨機猜想分類器的準確度相關。熵是另外一種度量不純性的方式,它來源於信息論。Spark的實現默認採用Gini不純度。

 

4.9 決策樹調優

 

咱們取不一樣的參數進行測驗:

 

Scala:

 

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)

  

Java新建一個類:Hyperparameters進行測試

Java:

 

 1 //決策樹調優
 2 
 3 List<Tuple2<Tuple3<String, Integer, Integer>, Double>> evaluations = new ArrayList<>();
 4 
 5  
 6 
 7 String[] impuritySet = new String[]{"gini", "entropy"};
 8 Integer[] depthSet = new Integer[]{1, 20};
 9 Integer[] binsSet = new Integer[]{10, 300};
10 for (String impurity : impuritySet) {
11          for (Integer depth : depthSet) {
12                    for (Integer bins : binsSet) {
13                             //構建DecisionTreeModel
14                             DecisionTreeModel model = DecisionTree.trainClassifier(trainData, 7, new HashMap<Integer, Integer>(), impurity, depth, bins);
15                             //用CV集來計算結果模型的指標
16                             MulticlassMetrics metrics = getMetrics(model, cvData); 
17 
18                             evaluations.add(new Tuple2<Tuple3<String, Integer, Integer>, Double>(new Tuple3<String, Integer, Integer>(impurity, depth, bins), metrics.precision()));
19                    }
20          }
21 }
22 
23 Collections.sort(evaluations, (m1, m2) -> (int)((m2._2-m1._2)*1000));
24 evaluations.forEach(x -> System.out.println(x._1._1() + "," + x._1._2() + "," + x._1._3() + "," + x._2));

 

結果:

entropy,20,300,0.909348831610984

gini,20,300,0.9034338084839314

entropy,20,10,0.8934436095396943

gini,20,10,0.8906752411575563

gini,1,10,0.6348504909125299

gini,1,300,0.634283061368365

entropy,1,10,0.4892962154168888

entropy,1,300,0.4892962154168888

 

前面的測試代表,目前最佳選擇是:不純性度量採用熵,最大深度爲20,桶數爲300,這時準確度爲90.9%。

 

想要真正評估這個最佳模型在未來的樣本上的表現,固然須要在沒有用於訓練的樣本上進行評估。也就是使用第三個子集進行測試:

 

Scala:

val model = DecisionTree.trainClassifier(

trainData.union(cvData), 7, Map[Int,Int](), "entropy", 20, 300)

 

Java:

 

1 DecisionTreeModel modelR = DecisionTree.trainClassifier(trainData.union(cvData), 7, new HashMap<Integer, Integer>(), "entropy", 20, 300);
2 MulticlassMetrics metricsR = getMetrics(modelR, testData);
3 System.out.println(metricsR.precision());

 

結果:

0.91562526784949

 

結果準確度爲91.6%。估計還算可靠吧。決策樹在必定程度上存在對訓練數據的過擬合。減少最大深度可能會使過擬合問題有所改善。

 

4.10 重談類別型特徵

 

數據中類別型特徵使用one-hot編碼,這種編碼迫使決策樹算法在底層要單獨考慮類別型特徵的每個值,增長內存使用量而且減慢決策速度。咱們取消one-hot編碼:

 

Scala:

 

val data = rawData.map { line =>

         val values = line.split(',').map(_.toDouble)

         val wilderness = values.slice(10, 14).indexOf(1.0).toDouble

         val soil = values.slice(14, 54).indexOf(1.0).toDouble

         val featureVector =

                   Vectors.dense(values.slice(0, 10) :+ wilderness :+ soil)

         val label = values.last - 1

         LabeledPoint(label, featureVector)

}

 

val evaluations =

         for (impurity <- Array("gini", "entropy");

                   depth <- Array(10, 20, 30);

                   bins <- Array(40, 300))

         yield {

                   val model = DecisionTree.trainClassifier(

                            trainData, 7, Map(10 -> 4, 11 -> 40),

                            impurity, depth, bins)

                   val trainAccuracy = getMetrics(model, trainData).precision

                   val cvAccuracy = getMetrics(model, cvData).precision

                   ((impurity, depth, bins), (trainAccuracy, cvAccuracy))

         }

  

複製Hyperparameters命名爲CategoricalFeatures,修改readData和決策樹參數。

Java:

 1 public static JavaRDD<LabeledPoint> readData(JavaSparkContext jsc) {
 2          JavaRDD<String> rawData =jsc.textFile("src/main/java/advanced/chapter4/covtype/covtype.data");
 3 
 4          JavaRDD<LabeledPoint> data = rawData.map(line -> {
 5                    String[] values = line.split(",");
 6                    double[] features = new double[12];
 7                    for (int i = 0; i < 10; i++) {
 8                             features[i] = Double.parseDouble(values[i]);
 9                    }
10                    for (int i = 10; i < 14; i++) {
11                             if(Double.parseDouble(values[i]) == 1.0){
12                                      features[10] = i-10;
13                             }
14                    }
15 
16                    for (int i = 14; i < 54; i++) {
17                             if(Double.parseDouble(values[i]) == 1.0){
18                                      features[11] = i-14;
19                             }
20                    }
21 
22                    Vector featureVector = Vectors.dense(features);
23                    Double label = (double) (Double.parseDouble(values[values.length-1]) - 1);
24                    return new LabeledPoint(label, featureVector);
25          });
26          return data;
27 }
28 
29  
30 
31 List<Tuple2<Tuple3<String, Integer, Integer>, Double>> evaluations = new ArrayList<>();
32 Map<Integer, Integer> map = new HashMap<Integer, Integer>();
33 map.put(10, 4);
34 map.put(11, 40);
35 String[] impuritySet = new String[]{"gini", "entropy"};
36 Integer[] depthSet = new Integer[]{10, 20, 30};
37 Integer[] binsSet = new Integer[]{40, 300};
38 for (String impurity : impuritySet) {
39          for (Integer depth : depthSet) {
40                    for (Integer bins : binsSet) {
41                             //構建DecisionTreeModel
42                             DecisionTreeModel model = DecisionTree.trainClassifier(trainData, 7, map, impurity, depth, bins);
43                             //用CV集來計算結果模型的指標
44                             MulticlassMetrics metrics = getMetrics(model, cvData);
45 
46                             evaluations.add(new Tuple2<Tuple3<String, Integer, Integer>, Double>(new Tuple3<String, Integer, Integer>(impurity, depth, bins), metrics.precision()));
47                    }
48          }
49 }
50 
51 Collections.sort(evaluations, (m1, m2) -> (int)((m2._2-m1._2)*1000));
52 evaluations.forEach(x -> System.out.println(x._1._1() + "," + x._1._2() + "," + x._1._3() + "," + x._2));
53 
54  

 

結果:

entropy,30,300,0.9420165175498968

entropy,30,40,0.9400378527185134

gini,30,40,0.9340674466620784

gini,30,300,0.9330006882312457

gini,20,40,0.9227288368891947

entropy,20,40,0.923554714384033

entropy,20,300,0.9233138334480385

gini,20,300,0.9211286992429456

gini,10,40,0.7867859600825877

gini,10,300,0.7862869924294563

entropy,10,40,0.7863214039917412

entropy,10,300,0.779525120440468

 

準確度爲94.2%。比之前更好。

 

4.11 隨機決策森林

 

隨機決策森林是由多個決策樹獨立構造而成。

 

Scala:

 

val forest = RandomForest.trainClassifier(

trainData, 7, Map(10 -> 4, 11 -> 40), 20,

"auto", "entropy", 30, 300)

  

Java:

 

 1 Map<Integer, Integer> map = new HashMap<Integer, Integer>();
 2 map.put(10, 4);
 3 map.put(11, 40);
 4 
 5 //構建RandomForestModel
 6 RandomForestModel model = RandomForest.trainClassifier(trainData, 7, map, 20, "auto", "entropy", 30, 300, Utils.random().nextInt());
 7 
 8 //用CV集來計算結果模型的指標
 9 MulticlassMetrics metrics = getMetrics(model, cvData);
10 System.out.println(metrics.precision());

結果:

0.964827502890772

 

準確率96.5%。

在大數據背景下,隨機決策森林很是有吸引力,由於決策樹每每是獨立構造的,諸如Spark和MapReduce這樣的大數據技術本質上適合數據並行問題。

4.12 進行預測

Scala:

val input = "2709,125,28,67,23,3224,253,207,61,6094,0,29"

val vector = Vectors.dense(input.split(',').map(_.toDouble))

forest.predict(vector)

 

Java:

 

1 double[] input = new double[] {(double) 2709,(double) 125,(double) 28,(double) 67,(double) 23,(double) 3224,(double) 253,(double) 207,(double) 61,(double) 6094,(double) 0,(double) 29};
2 Vector vector = Vectors.dense(input);
3 System.out.println(model.predict(vector));

 

結果:

4.0

 

4.0對應原始Covtype數據集中的類別5(元素特徵從1開始)。(在本地算了半個小時 ㄒoㄒ )。

 

4.13 小結

 

本章介紹分類和迴歸。介紹概念:特徵、向量、訓練和交叉檢驗。

通常狀況,準確度超過95%是很難達到的。一般,經過包括更多特徵,或將已有特徵轉換成預測性更好的形式,咱們能夠進一步提升準確度。

分類和迴歸算法不僅包括決策樹和決策森林,Spark Mlib 實現的算法也不限於決策樹和決策森林。對分類問題,Spark MLib提供的實現包括:

         樸素貝葉斯

         支持向量機

         邏輯迴歸

他們接受一個LabeledPoint類型的RDD做爲輸入,須要經過將輸入數據劃分爲訓練集、交叉檢驗集和測試集來選擇超參數。

相關文章
相關標籤/搜索