預測是很是困難的,更別提預測將來。java
隨着現代機器學習和數據科學的出現,咱們依舊把從「某些值」預測「另外某個值」的思想稱爲迴歸。迴歸是預測一個數值型數量,好比大小、收入和溫度,而分類則指預測標號或類別,好比判斷郵件是否爲「垃圾郵件」,拼圖遊戲的圖案是否爲「貓」。算法
將回歸和分類聯繫在一塊兒是由於二者均可以經過一個(或更多)值預測另外一個(或多個)值。爲了可以作出預測,二者都須要從一組輸入和輸出中學習預測規則。在學習的過程當中,須要告訴它們問題及問題的答案。所以,它們都屬於所謂的監督學習。apache
分類和迴歸是分析預測中最古老的話題。支持向量機、邏輯迴歸、樸素貝葉斯算法、神經網絡和深度學習都屬於分類和迴歸技術。網絡
本章將重點關注決策樹算法和它的擴展隨機決策森林算法,這兩個算法靈活且應用普遍,便可用於分類問題,也可用於迴歸問題。更使人興奮的是,它們能夠幫助咱們預測將來,至少是預測咱們尚不願定的事情。好比,根據線上行爲來預測購買汽車的機率,根據用詞預測郵件是不是垃圾郵件,根據地理位置和土壤的化學成分預測哪塊耕地的產量可能更高。dom
天氣的特徵有:機器學習
最低氣溫學習
最高氣溫測試
平均氣溫大數據
多雲、有雨、晴朗編碼
特徵有時也被稱爲維度、預測指標或簡單地稱爲變量。本書將特徵只分爲兩大類:類別型特徵和數值型特徵。
特徵值按順序排列,就是所謂的特徵向量。
某一天,氣溫12~16攝氏度,溼度10%,晴朗,沒有寒流預報,次日最高氣溫爲17.2攝氏度。
特徵向量爲學習算法的輸入提供一種結構化的方式(好比輸入:12.5,15.5,0.10,晴朗,0)。預測的輸出(即目標)也被稱爲一個特徵,它是一個數值特徵:17.2。
一般咱們把目標做爲特徵向量的一個附加特徵。所以能夠把整個訓練樣本列示爲:12.5,15.5,0.10,晴朗,0,17.2。全部這些樣本的集合稱爲訓練集。
迴歸和分析的區別在於:迴歸問題的目標爲數值型特徵,而分類問題的目標爲類別型特徵。並非全部的迴歸或分類算法都可以處理類別型特徵或類別型目標,有些算法只能處理數值型特徵。
決策樹算法家族能天然地處理類別型和數值型特徵。決策樹算法容易並行化。它們對數據中的離羣點具備魯棒性,這意味着一些極端或可能錯誤的數據點根本不會對預測產生影響。算法能夠接受不一樣類型和量綱的數據,對數據類型和尺度不一樣的狀況不須要作預處理和規範化。
決策樹能夠推廣爲更強大的決策森林算法。本章將會把Spark MLib的DecisionTree和RandomForest算法實現應用到一個數據集上。
基於決策樹的算法還有一個優勢,那就是理解和推理起來相對直觀。
下載地址是https://archive.ics.uci.edu/ml/machine-learning-databases/covtype/
或者http://pan.baidu.com/s/1gfkDKlH
covtype.data.gz爲壓縮數據文件,附帶一個描述數據文件的信息文件covtype.txt。記錄了美國科羅拉多州不一樣地塊森林植被特徵:海拔、坡度、到水源的距離、遮陽狀況和土壤類型,而且隨同給出了地塊的已知森林植被類型。有581012個樣本。
Spark MLib將特徵向量抽象爲LabeledPoint,它由一個包含對個特徵值的Spark MLib Vector和一個稱爲標號(label)的目標值組成。該目標爲Double類型,而Vector本質上是對多個Double類型值的抽象。這說明LabeledPoint只適用於數值型特徵。但只要通過適當編碼,LabeledPoint也可用於類別型特徵。
其中一種編碼是one-hot或1-of-n編碼。在這種編碼中,一個有N個不一樣取值的類別型特徵能夠變成N個數值型特徵,變換後的每一個數值型特徵的取值爲0或1.在這N個特徵中,有且只有一個取值爲1,其餘特徵取值都爲0。
開始時咱們原樣使用數據。
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%的準確度看起來還不錯。若是在決策樹構建過程當中試試超參數的其餘值,準確度還能夠提升。
控制決策樹選擇過程的參數爲最大深度、最大桶數和不純性度量。
最大深度只是對決策樹的層數作出限制。限制判斷次數有利於避免對訓練數據產生過擬合。
好規則把訓練集數據的目標值分爲相對是同類或「純」(pure)的子集。選擇最好的規則也就意味着最小化規則對應的兩個子集的不純性。不純性有兩種經常使用度量方式:Gini不純度或熵。
Gini不純度直接和隨機猜想分類器的準確度相關。熵是另外一種度量不純性的方式,它來源於信息論。Spark的實現默認採用Gini不純度。
咱們取不一樣的參數進行測驗:
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%。估計還算可靠吧。決策樹在必定程度上存在對訓練數據的過擬合。減少最大深度可能會使過擬合問題有所改善。
數據中類別型特徵使用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%。比之前更好。
隨機決策森林是由多個決策樹獨立構造而成。
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這樣的大數據技術本質上適合數據並行問題。
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ㄒ )。
本章介紹分類和迴歸。介紹概念:特徵、向量、訓練和交叉檢驗。
通常狀況,準確度超過95%是很難達到的。一般,經過包括更多特徵,或將已有特徵轉換成預測性更好的形式,咱們能夠進一步提升準確度。
分類和迴歸算法不僅包括決策樹和決策森林,Spark Mlib 實現的算法也不限於決策樹和決策森林。對分類問題,Spark MLib提供的實現包括:
樸素貝葉斯
支持向量機
邏輯迴歸
他們接受一個LabeledPoint類型的RDD做爲輸入,須要經過將輸入數據劃分爲訓練集、交叉檢驗集和測試集來選擇超參數。