Spark Mllib 下的決策樹二元分類 —— 網站分類(2)

模型評估

在上一章節的末尾我們提到過模型的評估,那時只是簡單的求了一下百分比,那種方式只能粗略的反映模型的準確率,針對二元分類算法,我們有AUC(Area under the Curve of ROC)即ROC曲線下的面積來評估模型的好壞在計算AUC之前應該先理解下面的幾個概念:

/
TP FP
TN FN
  • 真陽性 True Positives ( TP ): 預測爲 1 ,實際爲 1.
  • 假陽性 False Positives ( FP ): 預測爲 1 ,實際爲 0.
  • 真陰性 True Negatives ( TN ): 預測爲 0 ,實際爲 0.
  • 假陰性 True Negatives ( FN ): 預測爲 0 ,實際爲 1.
  • TPR:在所有實際爲 1 的樣本中被正確判斷爲 1 的比例.
    TPR = TP/( TP + FN )
  • FPR: 在所有實際爲 0 的樣本中被錯誤判斷爲 1 的比例.
    FPR = FP/( FP + TN)

有了 TPR 和 FPR就可以繪製ROC曲線了,如下圖所示:

在這裏插入圖片描述
ROC曲線與XY軸正方向圍成的面積即爲AUC的值

1、訓練模型

model = DecisionTree.trainClassifier(train_data,numClasses=2,categoricalFeaturesInfo={},impurity="entropy",maxDepth=5,maxBins=5)

2、將預測結果和真實值壓縮在一起

score = model.predict(validation_data.map(lambda p:p.features))
score_and_labels = score.zip(validation_data.map(lambda p:p.label))
score_and_labels.take(5)
[(0.0, 1.0), (0.0, 0.0), (1.0, 0.0), (1.0, 0.0), (0.0, 1.0)]

3、導入評估二元分類器模型的包

from pyspark.mllib.evaluation import BinaryClassificationMetrics

# 計算模型的AUC值(ROC曲線囊括的面積)
metrics = BinaryClassificationMetrics(score_and_labels)
metrics.areaUnderROC
0.6199946682707425

4、集成二元分類的AUC值計算

def evaluationBinaryClassification(model,validation_data):
    # 將驗證數據的features傳入通過模型進行評估,然後得到預測結果
    score = model.predict(validation_data.map(lambda p:p.features))
    # 構造爲 (預測值,真實值) 的集合
    score_and_labels = score.zip(validation_data.map(lambda p:p.label))
    # 計算出評估矩陣
    metrics = BinaryClassificationMetrics(score_and_labels)
    # 返回矩陣的ROC曲線面積也就是AUC的值
    return metrics.areaUnderROC

evaluationBinaryClassification(model,validation_data)
0.6769676269676269

5、影響模型準確率的因素

因爲訓練模型時我們可以改變的參數有三個,impurity(分支方式),maxDepth(樹的最大深度),maxBins(每個節點的最大分支數),如果簡單的將三個變量進行排列組合加枚舉的方式列進行訓練模型而找最大值的話,肯定準確是準確,但是太耗費資源了,所以這裏採用控制變量的方法(進行單一變量原則)進行測試,分別探尋每個參數的最優解;

5.1 impurity 參數影響

5.1.2 定義訓練評估模型

import time

# 訓練並評估模型
def evaluationTrainModel(train_data,validation_data,impurity,maxDepth,maxBins):
    # 記錄開始時間
    start_time = time.time()
    # 根據傳入參數訓練模型
    model = DecisionTree.trainClassifier(train_data,numClasses=2,categoricalFeaturesInfo={},impurity=impurity,maxDepth=maxDepth,maxBins=maxBins)
    # 記錄模型的訓練時間
    duration = time.time() - start_time
    # 根據訓練出的模型使用驗證數據算出AUC值
    AUC = evaluationBinaryClassification(model,validation_data)
    return (model,duration,AUC,impurity,maxDepth,maxBins)

evaluationTrainModel(train_data,validation_data,"entropy",10,10)
(DecisionTreeModel classifier of depth 10 with 591 nodes,
 0.641793966293335,
 0.6459731773005045,
 'entropy',
 10,
 10)

5.1.3創建模型評估矩陣

maxBinsList = [10]
maxDepthList = [10]
impurityList = ["gini","entropy"]
# 排列組合參數進行構造評估矩陣
metrics = [
        evaluationTrainModel(train_data,validation_data,impurity,maxDepth,maxBins)
        for maxBins in maxBinsList
        for maxDepth in maxDepthList
        for impurity in impurityList]
metrics
[(DecisionTreeModel classifier of depth 10 with 699 nodes,
  0.7073895931243896,
  0.6269453519453518,
  'gini',
  10,
  10),
 (DecisionTreeModel classifier of depth 10 with 673 nodes,
  0.7021915912628174,
  0.63500891000891,
  'entropy',
  10,
  10)]

5.1.4 將矩陣轉換爲DataFrame

import pandas as pd
# 轉換爲pandans的DataFrame
df = pd.DataFrame(data=metrics,index=impurityList,columns=["Model","Duration","AUC","Impurity","maxDepth","maxBins"])
df
Model Duration AUC Impurity maxDepth maxBins
gini DecisionTreeModel classifier of depth 10 with ... 0.707390 0.626945 gini 10 10
entropy DecisionTreeModel classifier of depth 10 with ... 0.702192 0.635009 entropy 10 10

5.1.5 繪製圖像

關於繪製圖像和pyplot的一些常用方法可以參照博主的另一篇文章:
Pyplot 常見繪圖方法

from matplotlib import pyplot as plt
# 繪製時間曲線
plt.plot(df["Duration"],ls="-",lw=2,c="r",label="Duration",marker="o")
# 繪製模型AUC分數柱
plt.bar(df["Impurity"],df["AUC"],ls="-",lw=2,color="c",label="AUC")
# 顯示圖例
plt.legend()
# 設置x軸方向的範圍
plt.xlim(-1,2)
# 設置 y軸方向範圍
plt.ylim(0.6,0.75)
# 繪製網格
plt.grid(ls=":",lw=1,c="gray")
# impurity = 'entropy'

在這裏插入圖片描述
通過圖像我們可以很明顯的看出entropy (熵) 用來作爲分支的依據,無論是從模型訓練時間上,和模型的準確率都比 “gini” 要好一些,所以分支方法就可以選定爲"entropy"了

5.2 maxDepth參數影響

maxBinsList = [10]
maxDepthList = [i for i in range(1,21)]
impurityList = ["entropy"]
metrics = [
        evaluationTrainModel(train_data,validation_data,impurity,maxDepth,maxBins)
        for maxBins in maxBinsList
        for maxDepth in maxDepthList
        for impurity in impurityList]
df = pd.DataFrame(data=metrics,index=maxDepthList,columns=["Model","Duration","AUC","Impurity","maxDepth","maxBins"])

# 繪製時間曲線
plt.plot(df["Duration"],ls="-",lw=2,c="r",label="Duration")
# 繪製模型AUC分數柱
plt.bar(df["maxDepth"],df["AUC"],ls="-",lw=2,color="c",label="AUC",tick_label=df["maxDepth"])
# 顯示圖例
plt.legend()
# 設置x軸方向的範圍
plt.xlim(0,21)
# 設置 y軸方向範圍
plt.ylim(0.4,0.7)
# 繪製網格
plt.grid(ls=":",lw=1,c="gray")
# maxDepth = 8

在這裏插入圖片描述
從圖中可以看到模型訓練時間隨樹的深度增加而增加,但是我們發現深度在4~10時,模型訓練時間有一段低谷時期,而且碰巧的是AUC的值也在這一個區間內達到了最大,所以我們就選擇8作爲maxDepth的最優解

5.3 maxBins參數影響

maxBinsList = [i for i in range(2,21)]
maxDepthList = [8]
impurityList = ["entropy"]
metrics = [
        evaluationTrainModel(train_data,validation_data,impurity,maxDepth,maxBins)
        for maxBins in maxBinsList
        for maxDepth in maxDepthList
        for impurity in impurityList]
df = pd.DataFrame(data=metrics,index=maxBinsList,columns=["Model","Duration","AUC","Impurity","maxDepth","maxBins"])

# 繪製時間曲線
plt.plot(df["Duration"],ls="-",lw=2,c="r",label="Duration")
# 繪製模型AUC分數柱
plt.bar(df["maxBins"],df["AUC"],ls="-",lw=2,color="c",label="AUC",tick_label=df["maxBins"])
# 顯示圖例
plt.legend()
# 設置x軸方向的範圍
plt.xlim(0,21)
# 設置 y軸方向範圍
plt.ylim(0.6,0.7)
# 繪製網格
plt.grid(ls=":",lw=1,c="gray")
# maxBins = 7

在這裏插入圖片描述
從圖中可以看到,基本上模型的訓練時間和每個節點的最大分支數沒太大的關係,單個模型的訓練時間基本都在 0.6s 以下,所以我們僅需找AUC值最大的點即可,那就是maxBins = 7 時

6、模型最終結果

6.1 導入測試文件的數據

def loadTestData(sc):
    raw_data_and_header = sc.textFile("file:/home/zh123/.jupyter/workspace/stumbleupon/test.tsv")
    # 文件頭部
    header_data = raw_data_and_header.first()
    # 取頭
    raw_non_header_data = raw_data_and_header.filter(lambda l:l != header_data)
    # 去引號
    raw_non_quot_data = raw_non_header_data.map(lambda s:s.replace("\"",""))
    # 最終測試文件數據
    data = raw_non_quot_data.map(lambda l:l.split("\t"))
    # 初始化類型映射字典
    categories_dict = data.map(lambda fields:fields[3]).distinct().zipWithIndex().collectAsMap()
    # 這裏是因爲我前面訓練過程中沒有保存那時的類型映射字典,所以這裏就補了兩位不然會報錯
    categories_dict.update({"t1":-1,"t2":-1})
    
    label_point_rdd = data.map(lambda fields:(
                        fields[0],
                        extract_features(fields,categories_dict,len(fields))
    ))
    return label_point_rdd

test_file_data = loadTestData(sc)
print(test_file_data.take(1))
print(test_file_data.count())
[('http://www.lynnskitchenadventures.com/2009/04/homemade-enchilada-sauce.html', array([0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
       0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
       0.00000000e+00, 1.00000000e+00, 0.00000000e+00, 0.00000000e+00,
       0.00000000e+00, 0.00000000e+00, 4.43906000e-01, 2.55813954e+00,
       3.89705882e-01, 2.57352941e-01, 4.41176470e-02, 2.20588240e-02,
       4.89572471e-01, 0.00000000e+00, 0.00000000e+00, 6.71428570e-02,
       0.00000000e+00, 2.30285215e-01, 1.99438202e-01, 1.00000000e+00,
       1.00000000e+00, 1.50000000e+01, 0.00000000e+00, 5.64300000e+03,
       1.36000000e+02, 3.00000000e+00, 2.42647059e-01, 8.05970150e-02]))]
3171

6.2 加載模型

# 使用最終確認的的參數進行訓練模型
model = evaluationTrainModel(train_data,validation_data,impurity="entropy",maxDepth=8,maxBins=7)[0]

6.3 隨機選擇10組數據進行預測

# 從測試文件集中隨機抽取10個數據
for f in test_file_data.randomSplit([10,3171-10])[0].collect():
    # 打印網站名稱和預測結果
    print(f[0],bool(model.predict(f[1])))
http://culinarycory.com/2008/12/30/pear-apple-crumb-pie/ True
http://mobile.seriouseats.com/2011/03/ramen-hacks-30-easy-ways-to-upgrade-your-instant-noodles-japanese-what-to-do-with-ramen.html False
http://blogs.babble.com/family-kitchen/2011/10/03/halloween-hauntingly-beautiful-candied-apples/ True
http://www.ivillage.com/paprika-potato-frittata-0/3-r-60973 True
http://www.goodlifeeats.com/2011/06/chocolate-covered-brownie-ice-cream-sandwich-recipe.html False
http://redux.com/stream/item/2071196/Two-Women-Fight-for-a-Parking-Spot-Then-Brilliance-Happens False
http://news.boloji.com/2008/10/25084.htm False
http://azoovo.com/a-corporate-web-design/ True
http://funstuffcafe.com/need-to-want-less False
http://www.insidershealth.com/article/nestle_cookie_dough_recall/3601 False
http://bakingbites.com/category/recipes/bar-cookies/ True

上面即是我們預測的結果,格式爲(url, 是否爲長期推薦網頁),我們可以人爲的點擊網頁鏈接進行查看網頁是否和後面預測的結果一樣屬於 非/可以 長期推薦的網頁;

最後網頁分類的項目到此就完結了 撒花!
博客中有問題的地方歡迎大佬指出,當然還有疑問的小夥伴也可以私聊或者評論區留言喲!
最重要還是 點贊!評論!收藏!三連哦,謝謝.

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