Kaggle挑戰賽：使用Decision Tree做冰與火之歌結局預測(超新手入門版)
By: Riley Huang
Tags: Game of Thrones, Classification, Confusion Matrix, Decision Tree, Machine Learning

Decision Tree最著名的例子，是鐵達尼號的生存者預測或Iris花朵分類，第二個有名的大概就是看冰與火之歌中的角色是誰活到最後。雖然有滿多新聞報導，但關於如何實作的中文資料好像很少，趁著寫課堂作業，紀錄一下我（應該滿笨又不是很成熟的）的作法，希望看到這篇的人可以給我更多更好的建議。

下載資料集

首先，先至Kaggle網站下載這次需要的資料集。分別有“battle”、“character-deaths”、“character-prediction”，這篇用的是“character-deaths”這個資訊較簡單的檔案，也有在國外網站上看到有人只使用“prediction”檔案(*參考資料1)。檔案中有917個角色，他們每一個人都已經被標註了最後是存活或死亡，因此後面切割訓練集與測試集的X與Y時要從同一份檔案中做分配。

冰與火之歌網頁：https://www.kaggle.com/mylesoneill/game-of-thrones

載入會用到的函數庫

這篇會使用pandas來讀取資料集，以及做一些資料前處理的動作，另外需要透過sklearn載入決策樹分類器這個函數，最後，若要檢查做出來的model究竟準確度如何，我們也要載入Confusion Matrix來繪製誤差矩陣。

import pandas as pd

import numpy as np from sklearn import tree

from sklearn.tree import DecisionTreeClassifier

from sklearn.metrics import confusion_matrix

讀取資料

使用Pandas套件將“character-deaths”這個檔案讀取進來，然後看一下他前三行長怎樣。

df = pd.read_csv("character-deaths.csv")

Data Processing

進行資料前處理的第一步，我喜歡先檢查一下空值，讓心裡有個底之後有多少事情要做。從下圖看起來，僅「死亡年份」、「死亡集數」與「死亡章節」和「登場章節介紹」的資料有空值，好險好險，跟鐵達尼號相比少很～多。

df.info()

有空值的其中三個欄位：「死亡年份」、「死亡集數」與「死亡章節」，對於最後預測的結果來說都是代表一樣的意思，不論是哪一年死亡，最後結果即為死亡，反之，就是存活，集數與章節亦同。因此可以選擇其中一個欄位，把空值的地方填入0，代表存活，有值的地方填入1，代表死亡。這樣一來就把資料集變得更簡潔了！

df = df.rename(columns = {‘Death Year’:’Death’})

df[‘Death’] = df.Death.fillna(0)

df.Death[df.Death>0] = 1

另外一個有空值的欄位「登場章節介紹」，我自作主張的把空值填入0，把原本有值的帶入1，這個想法是建立在，假設他有被慎重的介紹登場，或許死亡機率低一點，反之，沒有被好好介紹過的角色，可能是跑龍套隨便就死亡的機率高一些。

df = df.rename(columns = {'Book Intro Chapter':'Intro'})

df['Intro'] = df.Intro.fillna(0)

df.Intro[df.Intro>0] = 1

把死亡年份與登場章節介紹rename為Death與Intro，並把值改為類別變數

Allegiances這個欄位是代表該角色效忠於哪一個家族，也是個挺重要的特徵，(按照老師作業要求)將這個欄位轉為dummy特徵，依據底下的值有幾種分類，會轉換成同樣數目的特徵欄位，每一欄位的值會再視其是否為該特徵，轉換成0或1。把這個欄位get dummy後，本來的資料集就會再增加約20種特徵。這時候來整理整理，把這些多出來的特徵欄位與原本的資料集合併，刪掉不會再用到的欄位。

df1 = pd.get_dummies(df.Allegiances)

df = pd.concat([df, df1], axis = 1)

df = df.drop([‘Allegiances’,‘Book of Death’, ‘Death Chapter’], axis = 1)

將Allegiances這個特徵欄位Get Dummy後，多出了超級多欄位，高達31 Columns

切割訓練集與測試集

首先，把x值(input)和y值(output)分離出來，從上圖可以看出，要預測的結果為「Death」這個欄位，目前被放在第二欄，作為y，也就是說第3欄(Intro)以後的特徵值就是x值。在這邊使用iloc與loc兩個函數來抓取位置。

再使用sklearn的train_test_split函數，以隨機方式切分訓練集與測試集，在這篇的數量分配，75%為訓練集，25%為測試集。

from sklearn.model_selection import train_test_split

x = df.iloc[:, 2:]

y = df.loc[:, ‘Death’]

x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.25)

製作決策樹模型

接著就可以來套用DecitionTreeClassifier來製作決策樹模型！以下式子中有些網路教學會分成兩行寫，先建構這棵樹模型的樣子，篩選條件使用entropy，(另外也可以用gini index)，限制模型深度為5層，不然會跑到天荒地老。接著再把模型拿去應用於訓練集上以進行訓練。

clf = DecisionTreeClassifier(criterion = ‘entropy’, max_depth = 5, random_state = 0).fit(x_train, y_train)

將樹可視化

網路上可以找到許多範例使用pydotplus將樹畫出來(如下程式碼)，但實際使用後發現還要安裝一個非python工具的dot資料檔，不然會出現error message，超～～麻煩！不過這個的好處就是可以輸出成pdf檔，若沒有這需求的話就可以再往下看更簡單的程式碼。

dot_data = tree.export_graphviz(clf, out_file = None)

graph = pydotplus.graph_from_dot_data(dot_data)

graph.write_pdf(‘predict.pdf’)

後來發現原來從scikit-learn 21.0版本後，可以直接使用裡面的 tree.plot_tree 函數，利用matplotlib將決策樹可視化，只要一行！不過在畫圖之前先設定圖片大小，再設定字體大小等，就可以把圖畫出來了。

fig, ax = plt.subplots(figsize=(25, 25))

tree.plot_tree(clf, ax=ax, fontsize=12)

plt.show()

將決策樹利用tree.plot_tree可視化(以2層作為範例)

評估模型

建構完模型後就要來看看這個模型是好是壞，可以先用簡單的score這個功能來看一下套用到測試集的分數，看起來好爛喔天，之後再來想想要怎麼優化模型。

clf.score(x_test,y_test)

另一個方式是比較嚴謹的，透過混淆矩陣(confusion matrix)的各項指標，包括Recall、Precision、F1-score等等，來計算準確率。

混淆矩陣

・Recall(召回率) = TP/(TP+FN)

・Precision(準確率) = TP/(TP+FP)

・F1-score = 2 * Precision * Recall / (Precision + Recall)

程式碼有兩種，一種是簡簡單單的以陣列方式呈現，另一種是瞎趴蝦趴的plot。

y_pred = clf.predict(x_test)

confusion_matrix(y_test, y_pred, labels=None, sample_weight=None)

混淆矩陣-1

蝦趴的這款程式碼如下，因為原本預設的顏色有夠醜，改成視覺上看起來更舒服的藍色，後面的normalize為是否要改成比例，若輸入true的話就會跑出左邊那張，刪掉的話就會跑出右邊的那張，用數值呈現。

y_pred = clf.predict(x_test) disp = plot_confusion_matrix(clf, x_test, y_test, cmap=plt.cm.Blues, normalize='true')