Spark機器學習不想跟你說話並向你扔了一個kaggle小例子

在前文中分別就Spark機器學習中的各個模塊進行逐個描述,本文將Kaggle中Flights and Airports Data數據集作爲研究對象,使用Spark對其進行簡單的pipline建模、指標評估和交叉驗證調參,構建一個較爲完整的Spark分析實例。

先開啓一系列Hadoop、Spark服務與Spark-shell窗口:

數據集:Flights and Airports Data

下載地址:https://www.kaggle.com/tylerx/flights-and-airports-data

軟件版本:Ubuntu 19.10、Jdk 1.8.0_241、Hadoop 3.2.1、Spark 2.4.5

使用Spark進行機器學習

導入Spark SQL和Spark ML庫

我們將使用Pipleline來準備數據,使用CrossValidator來訓練模型的參數,並使用BinaryClassificationEvaluator來訓練我們的訓練模型,以訓練LogisticRegression模型。

// 數據集操作庫
import org.apache.spark.sql.types._
import org.apache.spark.sql.functions._
import org.apache.spark.sql.SparkSession
// 特徵與模型庫
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.feature.{VectorAssembler,StringIndexer,VectorIndexer,MinMaxScaler}
import org.apache.spark.ml.classification.LogisticRegression
import org.apache.spark.ml.tuning.{ParamGridBuilder,CrossValidator}
import org.apache.spark.ml.evaluation.BinaryClassificationEvaluator

加載源數據

flight.csv文件數據中的數據包括每個航班的特定特徵,指示航班晚點或早點到達的時間。先將其保存至本地磁盤或hdfs中。

爲分類模型(決策樹學習模型)準備數據

我選擇了一部分列作爲特徵,並創建了一個名爲label的布爾標籤字段,其值爲1或0。具體來說,晚到達的航班爲1,早於飛行時間的航班爲0 。

val csv = spark.read.format("csv").option("header","true").option("inferSchema","true").load("file:///home/phenix/kaggle/input/flights.csv")
val data = csv.withColumn("label",(col("ArrDelay") > 15).cast("Int"))
data.show()

劃分數據

我將使用70%的數據進行訓練,並保留30%的數據進行測試。在測試數據中,label列被重命名爲trueLabel,因此我以後可以使用它來將預測的標籤與已知的實際值進行比較。

val splits = data.randomSplit(Array(0.7,0.3),seed = 100)
val train = splits(0)
val test = splits(1).withColumnRenamed("label","trueLabel")


val train_rows = train.count()
val test_rows = test.count()
println(f"Training Rows: $train_rows, Testing Rows: $test_rows")

定義管道

管道由一系列轉換器和估計器階段組成,這些階段通常準備用於建模的DataFrame,然後訓練預測模型。在這種情況下,您將創建一個包含七個階段的管道:

  • StringIndexer,可將字符串值轉換爲用於分類功能的索引

  • VectorAssembler,將分類特徵組合到單個矢量中

  • VectorIndexer,用於爲分類特徵的向量創建索引

  • VectorAssembler,用於創建連續數字特徵的矢量

  • MinMaxScaler,可標準化連續數字特徵

  • VectorAssembler,可創建具有分類特徵和連續特徵的向量

val strIdx = new StringIndexer().setInputCol("Carrier").setOutputCol("CarrierIdx")
val catVect = new VectorAssembler().setInputCols(Array("CarrierIdx", "DayofMonth", "DayOfWeek", "OriginAirportID", "DestAirportID")).setOutputCol("catFeatures")
val catIdx = new VectorIndexer().setInputCol(catVect.getOutputCol).setOutputCol("idxCatFeatures")
val numVect = new VectorAssembler().setInputCols(Array("DepDelay")).setOutputCol("numFeatures")
val minMax = new MinMaxScaler().setInputCol(numVect.getOutputCol).setOutputCol("normFeatures")
val featVect = new VectorAssembler().setInputCols(Array("idxCatFeatures", "normFeatures")).setOutputCol("features")

運行管道以訓練模型

在訓練數據上作爲估計器運行管道以訓練模型。

val lr = new LogisticRegression().setLabelCol("label").setFeaturesCol("features").setMaxIter(10).setRegParam(0.3)


val pipeline = new Pipeline().setStages(Array(strIdx, catVect, catIdx, numVect, minMax, featVect, lr))
val pipelineModel = pipeline.fit(train)

生成標籤預測

使用流水線中的所有階段和訓練模型來轉換測試數據,以生成預測標籤

val prediction = pipelineModel.transform(test)
val predicted = prediction.select("features", "prediction", "trueLabel")
predicted.show(numRows = 100, truncate = false)

查看結果,一些trueLabel 1預測爲0。讓我們評估模型。

評估分類模型

我們將計算混淆矩陣ROC下面積以評估模型。

計算混淆矩陣

分類器通常通過創建混淆矩陣進行評估,該矩陣表示以下數目:

  • TP真陽性

  • FP真陰性

  • TN假陽性

  • FN假陰性 

從這些核心指標中,可以計算出其他評估指標,例如精度召回率F1值

val tp = predicted.filter("prediction == 1.0 AND truelabel == 1").count()
val fp = predicted.filter("prediction == 1.0 AND truelabel == 0").count()
val tn = predicted.filter("prediction == 0.0 AND truelabel == 0").count()
val fn = predicted.filter("prediction == 0.0 AND truelabel == 1").count()
val pr = tp*1.0 / (tp + fp)
val re = tp*1.0 / (tp + fn)


val metrics = Seq(("TP",tp*1.0),("FP",fp*1.0),("TN",tn*1.0),("FN",fn*1.0),("Precision",pr),("Recall",re),("F1",2*pr*re/(re+pr))).toDF("metric","value")
metrics.show()

看起來我們的Precision很好,但是Recall卻很低,因此我們的F1並不是那麼好。

複查ROC曲線下面積

評估分類模型性能的另一種方法是測量模型的ROC曲線下的面積。spark.ml庫包含一個BinaryClassificationEvaluator類,我們可以使用它來進行計算。ROC曲線顯示了針對不同閾值繪製的真假率和假陽性率。

val evaluator = new BinaryClassificationEvaluator().setLabelCol("trueLabel").setRawPredictionCol("rawPrediction").setMetricName("areaUnderROC")
val aur = evaluator.evaluate(prediction)
println(f"AUR = $aur")

因此,AUR表明我們的模型還可以。讓我們深入瞭解。

查看原始預測和概率

該預測基於描述邏輯函數中標記點的原始預測分數。然後,根據表示每個可能標籤值(在這種情況下爲0和1)的置信度的概率矢量,將此原始預測轉換爲0或1的預測標籤。選擇具有最高置信度的值作爲預測。

prediction.select("rawPrediction", "probability", "prediction", "trueLabel").show(100, truncate=false)

請注意,結果包括其中0的概率(概率向量中的第一個值)僅略高於1的概率(概率向量中的第二個值)的行。默認判別閾值(決定概率被預測爲1還是0的邊界)設置爲0.5;因此,無論接近閾值多少,始終使用概率最高的預測。從上面的結果我們可以看到,對於那些我們預測爲0的truelabel爲1,其許多概率1略小於閾值0.5。

調整參數

爲了找到性能最好的參數,我們可以使用CrossValidator類來評估在ParameterGrid中定義的參數的每個組合,這些數據組合是分爲訓練和驗證數據集的多個數據摺疊的。請注意,這可能需要很長時間才能運行,因爲每個參數組合都會被嘗試多次。

更改分類閾值

AUC分數似乎表明模型相當合理,但是性能指標似乎表明它預測假陰性標籤的數量很高(即當真實標籤爲1時它預測爲0),從而導致較低的召回率 。我們可以通過降低閾值來改善這一點。相反,有時我們可能想通過提高閾值來解決大量的“誤報”問題。在這種情況下,我將讓CrossValidator從0.45、0.4和0.35中找到最佳閾值,從0.3和0.1中找到正則化參數,並從10和5中找到最大迭代次數。

val paramGrid = new ParamGridBuilder().addGrid(lr.regParam, Seq(0.3, 0.1)).addGrid(lr.maxIter, Seq(10, 5)).addGrid(lr.threshold, Seq(0.4, 0.3)).build()
val cv = new CrossValidator().setEstimator(pipeline).setEvaluator(new BinaryClassificationEvaluator()).setEstimatorParamMaps(paramGrid).setNumFolds(2)
val model = cv.fit(train)
val newPrediction = model.transform(test)
val newPredicted = newPrediction.select("features", "prediction", "trueLabel")
newPredicted.show()

請注意,以前預測爲0的某些rawPrediction和概率值現在預測爲1

// 重新計算混淆矩陣
val tp2 = newPredicted.filter("prediction == 1.0 AND truelabel == 1").count()
val fp2 = newPredicted.filter("prediction == 1.0 AND truelabel == 0").count()
val tn2 = newPredicted.filter("prediction == 0.0 AND truelabel == 0").count()
val fn2 = newPredicted.filter("prediction == 0.0 AND truelabel == 1").count()


val pr2 = tp2*1.0 / (tp2 + fp2)
val re2 = tp2*1.0 / (tp2 + fn2)
val metrics2 = Seq(("TP",tp2*1.0),("FP",fp2*1.0),("TN",tn2*1.0),("FN",fn2*1.0),("Precision",pr2),("Recall",re2),("F1",2*pr2*re2/(re2+pr2))).toDF("metric","value")
metrics2.show()


// Recalculate the Area Under ROC 重新計算ROC曲線下面積
val evaluator2 = new BinaryClassificationEvaluator().setLabelCol("trueLabel").setRawPredictionCol("rawPrediction").setMetricName("areaUnderROC")
val aur2 = evaluator2.evaluate(newPrediction)
println(f"AUR2 = $aur2")

    看起來還不錯!新模型將召回率從0.11提高到0.38,F1得分從0.20提高到0.55,而不會影響其他指標。下一步仍有很大的改進空間。例如,我可以嘗試使用更低閾值的更多選項,或者使用不同的分類模型,或者像添加新功能一樣更好地準備數據。有關Spark的基礎文章可參考

    Spark分佈式機器學習源碼分析:矩陣向量

    Spark分佈式機器學習源碼分析:基本統計

    Spark分佈式機器學習源碼分析:線性模型

    Spark分佈式機器學習源碼分析:樸素貝葉斯

    Spark分佈式機器學習源碼分析:決策樹模型

    Spark分佈式機器學習源碼分析:集成樹模型

    Spark分佈式機器學習源碼分析:協同過濾

    Spark分佈式機器學習源碼分析:K-means

    Spark分佈式機器學習源碼分析:LDA模型

    Spark分佈式機器學習源碼分析:SVD和PCA

    Spark分佈式機器學習源碼分析:特徵提取與轉換

    Spark分佈式機器學習源碼分析:頻繁模式挖掘

    Spark分佈式機器學習源碼分析:模型評估指標

    參考鏈接:

    https://www.kaggle.com/tylerx/machine-learning-with-spark

歷史推薦

“高頻面經”之數據分析篇

“高頻面經”之數據結構與算法篇

“高頻面經”之大數據研發篇

“高頻面經”之機器學習篇

“高頻面經”之深度學習篇

爬蟲實戰:Selenium爬取京東商品

爬蟲實戰:豆瓣電影top250爬取

爬蟲實戰:Scrapy框架爬取QQ音樂

數據分析與挖掘

數據結構與算法

機器學習與大數據組件

歡迎關注,感謝“在看”,隨緣稀罕~

 

一個贊,晚餐加雞腿

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章