Hello Kitty到底是貓還是女孩?讓決策樹告訴我們真僞。
特徵選取
我們提取七個特徵,用來判斷一個形象,是人是貓。這七個特徵包括:有否蝴蝶結;是否穿衣服;是否高過5個蘋果;是否有鬍子;是否圓臉;是否有貓耳朵;是否兩腳走路。
用一個表格來表現這七個特徵則,如下圖所示(第一列爲 Label,第二至八列爲7個特徵,每個特徵只有兩個取值,Yes 或者 No):
Table-1
用 ID3 算法構造分類樹
本例中,我們選用最簡單的 ID3 算法,代入數據進行計算。
(1)根據信息熵的概念,我們先來計算 Entropy(S)。因爲總共只有兩個類別:人和貓,因此 n==2。
(2)然後我們再分別計算各個特徵的:
因爲無論哪個特徵,都只有兩個特徵值:Yes 或者 No,因此value(T)總共只有兩個取值。
下面以“Has a bow”爲例來示意其計算過程。
依次計算其他幾項,得出如下結果:
(3)進一步計算,得出 InfoGain(Has cat ears) 最大,因此“Has cat ears”是第一個分裂節點。
而從這一特徵對應的類別也可以看出,所有特徵值爲 No 的都一定是 Girl;特徵值爲 Yes,可能是 Girl 也可能是 Cat,那麼第一次分裂,我們得出如下結果:
現在“Has cat ears”已經成爲了分裂點,則下一步將其排除,用剩下的6個 Feature 繼續分裂成樹:
Table-2
Table-2 爲第二次分裂所使用的訓練數據,相對於 Table-1,“Has cat ears”列,和前7行對應“Has cat ears”爲 No 的數據都已經被移除,剩下部分用於第二次分裂。
如此反覆迭代,最後使得7個特徵都成爲分裂點。
需要注意的是,如果某個特徵被選爲當前輪的分裂點,但是它在現存數據中只有一個值,另一個值對應的記錄爲空,則這個時候針對不存在的特徵值,將它標記爲該特徵在所有訓練數據中所佔比例最大的類型。
對本例而言,當我們將“Wear Clothes”作爲分裂點時,會發現該特徵只剩下了一個選項——Yes(如下 Table-3 所示)。此時怎麼給“Wear Clothes”爲 No 的分支做標記呢?
Table-3
這時就要看在 Table-1 中,“Wear Clothes”爲 No 的記錄中是 Girl 多還是 Cat 多。一目瞭然,在 Table-1 中這兩種記錄數量爲 0:6,因此“Wear Clothes”爲 No 的分支直接標誌成 Cat。
根據上述方法,最終我們構建出瞭如下決策樹:
決策樹構建過程,如下代碼所示:
DecisionTree induceTree(training_set, features) { If(training_set中所有的輸入項都被標記爲同一個label){ return 一個標誌位該label的葉子節點; } else if(features爲空) { # 默認標記爲在所有training_set中所佔比例最大的label return 一個標記爲默認label的葉子節點; } else { 選取一個feature,F; 以F爲根節點創建一棵樹currentTree; 從Features中刪除F; foreach(value V of F) { 將training_set中feature F的取值爲V的元素全部提取出來,組成partition_v; branch_v= induceTree(partition_V, features); 將branch_v添加爲根節點的子樹,根節點到branch_v的路徑爲F的V值; } returncurrentTree; } }
後剪枝優化決策樹
決策樹剪枝
剪枝是優化決策樹的常用手段。剪枝方法大致可以分爲兩類:
- 先剪枝(局部剪枝):在構造過程中,當某個節點滿足剪枝條件,則直接停止此分支的構造。
- 後剪枝(全局剪枝):先構造完成完整的決策樹,再通過某些條件遍歷樹進行剪枝。
後剪枝優化 Hello Kitty 樹
現在,決策樹已經構造完成,所以我們採用後剪枝法,對上面決策樹進行修剪。
如圖中顯示,最後兩個分裂點“Has round face”和“Has a bow”存在並無意義——想想也是啊,無論人貓,都有可能是圓臉,也都可以戴蝴蝶結啊。
以我們遍歷所有節點,將沒有區分作用的節點刪除。完成後,我們的決策樹變成了下面這樣:
代碼實現
下面的代碼就是用 numpy 和 sklearn 來實現例子中的訓練分類樹來判斷 Hello Kitty 種族所對應的程序。
from sklearn import tree from sklearn.model_selection im port train_test_split import numpy as np #9個女孩和8只貓的數據,對應7個feature,yes取值爲1,no爲0 features = np.array([ [1, 1, 0, 0, 1, 0, 1], [1, 1, 1, 0, 0, 0, 1], [0, 1, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 0, 1], [0, 1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 1, 0, 1], [1, 1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 1, 0, 1], [0, 1, 0, 1, 1, 1, 1], [1, 0, 1, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 0], [1, 0, 1, 1, 1, 1, 0], [0, 0, 0, 1, 1, 1, 0], [1, 0, 0, 1, 1, 1, 0], [0, 0, 1, 0, 1, 1, 0], [1, 1, 1, 1, 1, 1, 0], [1, 0, 1, 1, 1, 1, 0] ]) #1 表示是女孩,0表示是貓 labels = np.array([ [1], [1], [1], [1], [1], [1], [1], [1], [1], [0], [0], [0], [0], [0], [0], [0], [0], ]) # 從數據集中取20%作爲測試集,其他作爲訓練集 X_train, X_test, y_train, y_test = train_test_split( features, labels, test_size=0.2, random_state=0, ) # 訓練分類樹模型 clf = tree.DecisionTreeClassifier() clf.fit(X=X_train, y=y_train) # 測試 print(clf.predict(X_test)) # 對比測試結果和預期結果 print(clf.score(X=X_test, y=y_test)) # 預測HelloKitty HelloKitty = np.array([[1,1,1,1,1,1,1]]) print(clf.predict(HelloKitty))
最後輸出爲:
[1 1 0 0] 0.75 [0]