一、背景及問題
決策樹演算法是為了解決二分類問題出現的,是根據歷史經驗(或訓練集)來做判斷,生成決策結果(或樹狀圖)的過程。
二、決策樹原理
決策樹演算法分為兩個階段:構造和剪枝。
1.構造
什麼是構造呢?構造就是生成一棵完整的決策樹。簡單來說, 構造的過程就是選擇什麼屬性作為節點的過程 ,那麼在構造過程中,會存在三種節點:
1)根節點:就是樹的最頂端,最開始的那個節點。
2)內部節點:就是樹中間的那些節點,比如說“溫度”、“溼度”、“颳風”;
3)葉節點:就是樹最底部的節點,也就是決策結果。
節點之間存在父子關係。比如根節點會有子節點,子節點會有子子節點,但是到了葉節點就停止了,葉節點不存在子節點。那麼在構造過程中,你要解決三個重要的問題:
1)選擇哪個屬性作為根節點;
2)選擇哪些屬性作為子節點;
3)什麼時候停止並得到目標狀態,即葉節點。
2.剪枝
決策樹構造出來之後是不是就萬事大吉了呢?也不盡然,我們可能還需要對決策樹進行剪枝。剪枝就是給決策樹瘦身,這一步想實現的目標就是,不需要太多的判斷,同樣可以得到不錯的結果。之所以這麼做,是為了防止“過擬合”(Overfitting)現象的發生。
既然要對決策樹進行剪枝,具體有哪些方法呢?一般來說,剪枝可以分為“預剪枝”(Pre-Pruning)和“後剪枝”(Post-Pruning)。
預剪枝是在決策樹構造時就進行剪枝。方法是在構造的過程中對節點進行評估,如果對某個節點進行劃分,在驗證集中不能帶來準確性的提升,那麼對這個節點進行劃分就沒有意義,這時就會把當前節點作為葉節點,不對其進行劃分。
後剪枝就是在生成決策樹之後再進行剪枝,通常會從決策樹的葉節點開始,逐層向上對每個節點進行評估。如果剪掉這個節點子樹,與保留該節點子樹在分類準確性上差別不大,或者剪掉該節點子樹,能在驗證集中帶來準確性的提升,那麼就可以把該節點子樹進行剪枝。方法是:用這個節點子樹的葉子節點來替代該節點,類標記為這個節點子樹中最頻繁的那個類。
三、決策樹分類
現有打籃球的資料集,訓練資料如下:
我們該如何構造一個判斷是否去打籃球的決策樹呢?再回顧一下決策樹的構造原理,在決策過程中有三個重要的問題:將哪個屬性作為根節點?選擇哪些屬性作為後繼節點?什麼時候停止並得到目標值?
顯然將哪個屬性(天氣、溫度、溼度、颳風)作為根節點是個關鍵問題,在這裡我們先介紹兩個指標:純度和資訊熵。
先來說一下純度。你可以把決策樹的構造過程理解成為尋找純淨劃分的過程。數學上,我們可以用純度來表示,純度換一種方式來解釋就是讓目標變數的分歧最小。
我在這裡舉個例子,假設有 3 個集合:
集合 1:6 次都去打籃球;
集合 2:4 次去打籃球,2 次不去打籃球;
集合 3:3 次去打籃球,3 次不去打籃球。
按照純度指標來說,集合 1> 集合 2> 集合 3。因為集合 1 的分歧最小,集合 3 的分歧最大。
然後我們再來介紹資訊熵(entropy)的概念, 它表示了資訊的不確定度。
在資訊理論中,隨機離散事件出現的概率存在著不確定性。為了衡量這種資訊的不確定性,資訊學之父夏農引入了資訊熵的概念,並給出了計算資訊熵的數學公式:
p(i|t) 代表了節點 t 為分類 i 的概率,其中 log2 為取以 2 為底的對數。這裡我們不是來介紹公式的,而是說存在一種度量,它能幫我們反映出來這個資訊的不確定度。當不確定性越大時,它所包含的資訊量也就越大,資訊熵也就越高。
我舉個簡單的例子,假設有 2 個集合
集合 1:5 次去打籃球,1 次不去打籃球;
集合 2:3 次去打籃球,3 次不去打籃球。
在集合 1 中,有 6 次決策,其中打籃球是 5 次,不打籃球是 1 次。那麼假設:類別 1 為“打籃球”,即次數為 5;類別 2 為“不打籃球”,即次數為 1。那麼節點劃分為類別 1 的概率是 5/6,為類別 2 的概率是 1/6,帶入上述資訊熵公式可以計算得出:
同樣,集合 2 中,也是一共 6 次決策,其中類別 1 中“打籃球”的次數是 3,類別 2“不打籃球”的次數也是 3,那麼資訊熵為多少呢?我們可以計算得出:
從上面的計算結果中可以看出,資訊熵越大,純度越低。當集合中的所有樣本均勻混合時,資訊熵最大,純度最低。
我們在構造決策樹的時候,會基於純度來構建。而經典的 “不純度”的指標有三種,分別是資訊增益(ID3 演算法)、資訊增益率(C4.5 演算法)以及基尼指數(Cart 演算法)。
1.ID3
ID3 演算法計算的是 資訊增益 ,資訊增益指的就是劃分可以帶來純度的提高,資訊熵的下降。它的計算公式,是父親節點的資訊熵減去所有子節點的資訊熵。在計算的過程中,我們會計算每個子節點的歸一化資訊熵,即按照每個子節點在父節點中出現的概率,來計算這些子節點的資訊熵。所以資訊增益的公式可以表示為:
公式中 D 是父親節點,Di 是子節點,Gain(D,a) 中的 a 作為 D 節點的屬性選擇。
假設天氣 = 晴的時候,會有 5 次去打籃球,5 次不打籃球。其中 D1 颳風 = 是,有 2 次打籃球,1 次不打籃球。D2 颳風 = 否,有 3 次打籃球,4 次不打籃球。那麼 a 代表節點的屬性,即天氣 = 晴。
針對這個例子,D 作為節點的資訊增益為:
也就是 D 節點的資訊熵 -2 個子節點的歸一化資訊熵。2 個子節點歸一化資訊熵 =3/10 的 D1 資訊熵 +7/10 的 D2 資訊熵。
我們基於 ID3 的演算法規則,完整地計算下我們的訓練集,訓練集中一共有 7 條資料,3 個打籃球,4 個不打籃球,所以根節點的資訊熵是:
如果你將天氣作為屬性的劃分,會有三個葉子節點 D1、D2 和 D3,分別對應的是晴天、陰天和小雨。我們用 + 代表去打籃球,- 代表不去打籃球。那麼第一條記錄,晴天不去打籃球,可以記為 1-,於是我們可以用下面的方式來記錄 D1,D2,D3: D1(天氣 = 晴天)={1-,2-,6+}
D2(天氣 = 陰天)={3+,7-}
D3(天氣 = 小雨)={4+,5-}
我們先分別計算三個葉子節點的資訊熵:
因為 D1 有 3 個記錄,D2 有 2 個記錄,D3 有 2 個記錄,所以 D 中的記錄一共是 3+2+2=7,即總數為 7。所以 D1 在 D(父節點)中的概率是 3/7,D2 在父節點的概率是 2/7,D3 在父節點的概率是 2/7。那麼作為子節點的歸一化資訊熵 = 3/7*0.918+2/7*1.0+2/7*1.0=0.965。
因為我們用 ID3 中的資訊增益來構造決策樹,所以要計算每個節點的資訊增益。
天氣作為屬性節點的資訊增益為,Gain(D , 天氣)=0.985-0.965=0.020。
同理我們可以計算出其他屬性作為根節點的資訊增益,它們分別為 :
Gain(D , 溫度)=0.128
Gain(D , 溼度)=0.020
Gain(D , 颳風)=0.020
我們能看出來溫度作為屬性的資訊增益最大。因為 ID3 就是要將資訊增益最大的節點作為父節點,這樣可以得到純度高的決策樹,所以我們將溫度作為根節點。
然後我們要將第一個葉節點,也就是 D1={1-,2-,3+,4+}進一步進行分裂,往下劃分,計算其不同屬性(天氣、溼度、颳風)作為節點的資訊增益,可以得到:
Gain(D , 天氣)=0
Gain(D , 溼度)=0
Gain(D , 颳風)=0.0615
我們能看到颳風為 D1 的節點都可以得到最大的資訊增益,這裡我們選取颳風作為節點。同理,我們可以按照上面的計算步驟得到完整的決策樹。
於是我們通過 ID3 演算法得到了一棵決策樹。ID3 的演算法規則相對簡單,可解釋性強。同樣也存在缺陷,比如我們會發現 ID3 演算法傾向於選擇取值比較多的屬性。這樣,如果我們把“編號”作為一個屬性(一般情況下不會這麼做,這裡只是舉個例子),那麼“編號”將會被選為最優屬性 。但實際上“編號”是無關屬性的,它對“打籃球”的分類並沒有太大作用。
所以 ID3 有一個缺陷就是,有些屬性可能對分類任務沒有太大作用,但是他們仍然可能會被選為最優屬性。這種缺陷不是每次都會發生,只是存在一定的概率。在大部分情況下,ID3 都能生成不錯的決策樹分類。針對可能發生的缺陷,後人提出了新的演算法進行改進。
2.C4.5
C4.5是在ID3的基礎上做了改進。 那麼 C4.5 都在哪些方面改進了 ID3 呢?
1)採用資訊增益率
因為 ID3 在計算的時候,傾向於選擇取值多的屬性。為了避免這個問題,C4.5 採用資訊增益率的方式來選擇屬性。資訊增益率 = 資訊增益 / 屬性熵,具體的計算公式這裡省略。
當屬性有很多值的時候,相當於被劃分成了許多份,雖然資訊增益變大了,但是對於 C4.5 來說,屬性熵也會變大,所以整體的資訊增益率並不大。
2)採用悲觀剪枝
ID3 構造決策樹的時候,容易產生過擬合的情況。在 C4.5 中,會在決策樹構造之後採用悲觀剪枝(PEP),這樣可以提升決策樹的泛化能力。
悲觀剪枝是後剪枝技術中的一種,通過遞迴估算每個內部節點的分類錯誤率,比較剪枝前後這個節點的分類錯誤率來決定是否對其進行剪枝。這種剪枝方法不再需要一個單獨的測試資料集。
3)離散化處理連續屬性
C4.5 可以處理連續屬性的情況,對連續的屬性進行離散化的處理。比如打籃球存在的“溼度”屬性,不按照“高、中”劃分,而是按照溼度值進行計算,那麼溼度取什麼值都有可能。該怎麼選擇這個閾值呢, C4.5 選擇具有最高資訊增益的劃分所對應的閾值。
4)處理缺失值
針對資料集不完整的情況,C4.5 也可以進行處理。
假如我們得到的是如下的資料,你會發現這個資料中存在兩點問題。第一個問題是,資料集中存在數值缺失的情況,如何進行屬性選擇?第二個問題是,假設已經做了屬性劃分,但是樣本在這個屬性上有缺失值,該如何對樣本進行劃分?
我們不考慮缺失的數值,可以得到溫度 D={2-,3+,4+,5-,6+,7-}。溫度 = 高:D1={2-,3+,4+} ;溫度 = 中:D2={6+,7-};溫度 = 低:D3={5-} 。這裡 + 號代表打籃球,- 號代表不打籃球。比如 ID=2 時,決策是不打籃球,我們可以記錄為 2-。
所以三個葉節點的資訊熵可以結算為:
這三個節點的歸一化資訊熵為 3/6*0.918+2/6*1.0+1/6*0=0.792。
針對將屬性選擇為溫度的資訊增益率為:
Gain(D′, 溫度)=Ent(D′)-0.792=1.0-0.792=0.208
D′的樣本個數為 6,而 D 的樣本個數為 7,所以所佔權重比例為 6/7,所以 Gain(D′,溫度) 所佔權重比例為 6/7,所以:
Gain(D, 溫度)=6/7*0.208=0.178
這樣即使在溫度屬性的數值有缺失的情況下,我們依然可以計算資訊增益,並對屬性進行選擇。
3.CART
CART 演算法,英文全稱叫做 Classification And Regression Tree,中文叫做分類迴歸樹。ID3 和 C4.5 演算法可以生成二叉樹或多叉樹,而 CART 只支援二叉樹。同時 CART 決策樹比較特殊,既可以作分類樹,又可以作迴歸樹。
那麼你首先需要了解的是,什麼是分類樹,什麼是迴歸樹呢?
我用下面的訓練資料舉個例子,你能看到不同職業的人,他們的年齡不同,學習時間也不同。如果我構造了一棵決策樹,想要基於資料判斷這個人的職業身份,這個就屬於分類樹,因為是從幾個分類中來做選擇。如果是給定了資料,想要預測這個人的年齡,那就屬於迴歸樹。
分類樹可以處理離散資料,也就是資料種類有限的資料,它輸出的是樣本的類別,而回歸樹可以對連續型的數值進行預測,也就是資料在某個區間內都有取值的可能,它輸出的是一個數值。
1)CART 分類樹的工作流程
CART 分類樹與 C4.5 演算法類似,只是屬性選擇的指標採用的是基尼係數。
你可能在經濟學中聽過說基尼係數,它是用來衡量一個國家收入差距的常用指標。當基尼係數大於 0.4 的時候,說明財富差異懸殊。基尼係數在 0.2-0.4 之間說明分配合理,財富差距不大。
基尼係數本身反應了樣本的不確定度。當基尼係數越小的時候,說明樣本之間的差異性小,不確定程度低。分類的過程本身是一個不確定度降低的過程,即純度的提升過程。所以 CART 演算法在構造分類樹的時候,會選擇基尼係數最小的屬性作為屬性的劃分。
我們接下來詳解了解一下基尼係數。基尼係數不好懂,你最好跟著例子一起手動計算下。
假設 t 為節點,那麼該節點的 GINI 係數的計算公式為:
這裡 p(Ck|t) 表示節點 t 屬於類別 Ck 的概率,節點 t 的基尼係數為 1 減去各類別 Ck 概率平方和。
通過下面這個例子,我們計算一下兩個集合的基尼係數分別為多少:
集合 1:6 個都去打籃球;
集合 2:3 個去打籃球,3 個不去打籃球。
針對集合 1,所有人都去打籃球,所以 p(Ck|t)=1,因此 GINI(t)=1-1=0。
針對集合 2,有一半人去打籃球,而另一半不去打籃球,所以,p(C1|t)=0.5,p(C2|t)=0.5,GINI(t)=1-(0.5*0.5+0.5*0.5)=0.5。
通過兩個基尼係數你可以看出,集合 1 的基尼係數最小,也證明樣本最穩定,而集合 2 的樣本不穩定性更大。
在 CART 演算法中,基於基尼係數對特徵屬性進行二元分裂,假設屬性 A 將節點 D 劃分成了 D1 和 D2,如下圖所示:
節點 D 的基尼係數等於子節點 D1 和 D2 的歸一化基尼係數之和,用公式表示為:
歸一化基尼係數代表的是每個子節點的基尼係數乘以該節點佔整體父親節點 D 中的比例。
上面我們已經計算了集合 D1 和集合 D2 的 GINI 係數,得到:
所以節點 D 的基尼係數為:
節點 D 被屬性 A 劃分後的基尼係數越大,樣本集合的不確定性越大,也就是不純度越高。
2) 如何使用 CART 演算法來建立分類樹
通過上面的講解你可以知道,CART 分類樹實際上是基於基尼係數來做屬性劃分的。在 Python 的 sklearn 中,如果我們想要建立 CART 分類樹,可以直接使用 DecisionTreeClassifier 這個類。建立這個類的時候,預設情況下 criterion 這個引數等於 gini,也就是按照基尼係數來選擇屬性劃分,即預設採用的是 CART 分類樹。
下面,我們來用 CART 分類樹,給 iris 資料集構造一棵分類決策樹。iris 這個資料集,我在 Python 視覺化中講到過,實際上在 sklearn 中也自帶了這個資料集。基於 iris 資料集,構造 CART 分類樹的程式碼如下:
1 # encoding=utf-8
2 from sklearn.model_selection import train_test_split
3 from sklearn.metrics import accuracy_score
4 from sklearn.tree import DecisionTreeClassifier
5 from sklearn.datasets import load_iris
6 # 準備資料集
7 iris=load_iris()
8 # 獲取特徵集和分類標識
9 features = iris.data
10 labels = iris.target
11 # 隨機抽取 33% 的資料作為測試集,其餘為訓練集
12 train_features, test_features, train_labels, test_labels = train_test_split(features, labels, test_size=0.33, random_state=0)
13 # 建立 CART 分類樹
14 clf = DecisionTreeClassifier(criterion='gini')
15 # 擬合構造 CART 分類樹
16 clf = clf.fit(train_features, train_labels)
17 # 用 CART 分類樹做預測
18 test_predict = clf.predict(test_features)
19 # 預測結果與測試集結果作比對
20 score = accuracy_score(test_labels, test_predict)
21 print("CART 分類樹準確率 %.4lf" % score)
執行結果:
1 CART 分類樹準確率 0.9600
3) CART 迴歸樹的工作流程
CART 迴歸樹劃分資料集的過程和分類樹的過程是一樣的,只是迴歸樹得到的預測結果是連續值,而且評判“不純度”的指標不同。在 CART 分類樹中採用的是基尼係數作為標準,那麼在 CART 迴歸樹中,如何評價“不純度”呢?實際上我們要根據樣本的混亂程度,也就是樣本的離散程度來評價“不純度”。
樣本的離散程度具體的計算方式是,先計算所有樣本的均值,然後計算每個樣本值到均值的差值。我們假設 x 為樣本的個體,均值為 u。為了統計樣本的離散程度,我們可以取差值的絕對值,或者方差。
其中差值的絕對值為樣本值減去樣本均值的絕對值:
方差為每個樣本值減去樣本均值的平方和除以樣本個數:
所以這兩種節點劃分的標準,分別對應著兩種目標函式最優化的標準,即用最小絕對偏差(LAD),或者使用最小二乘偏差(LSD)。這兩種方式都可以讓我們找到節點劃分的方法,通常使用最小二乘偏差的情況更常見一些。
我們可以通過一個例子來看下如何建立一棵 CART 迴歸樹來做預測。
4)如何使用 CART 迴歸樹做預測
這裡我們使用到 sklearn 自帶的波士頓房價資料集,該資料集給出了影響房價的一些指標,比如犯罪率,房產稅等,最後給出了房價。
根據這些指標,我們使用 CART 迴歸樹對波士頓房價進行預測,程式碼如下:
1 # encoding=utf-8
2 from sklearn.metrics import mean_squared_error
3 from sklearn.model_selection import train_test_split
4 from sklearn.datasets import load_boston
5 from sklearn.metrics import r2_score,mean_absolute_error,mean_squared_error
6 from sklearn.tree import DecisionTreeRegressor
7 # 準備資料集
8 boston=load_boston()
9 # 探索資料
10 print(boston.feature_names)
11 # 獲取特徵集和房價
12 features = boston.data
13 prices = boston.target
14 # 隨機抽取 33% 的資料作為測試集,其餘為訓練集
15 train_features, test_features, train_price, test_price = train_test_split(features, prices, test_size=0.33)
16 # 建立 CART 迴歸樹
17 dtr=DecisionTreeRegressor()
18 # 擬合構造 CART 迴歸樹
19 dtr.fit(train_features, train_price)
20 # 預測測試集中的房價
21 predict_price = dtr.predict(test_features)
22 # 測試集的結果評價
23 print('迴歸樹二乘偏差均值:', mean_squared_error(test_price, predict_price))
24 print('迴歸樹絕對值偏差均值:', mean_absolute_error(test_price, predict_price))
執行結果(每次執行結果可能會有不同):
1 ['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO' 'B' 'LSTAT']
2 迴歸樹二乘偏差均值: 23.80784431137724
3 迴歸樹絕對值偏差均值: 3.040119760479042
5) CART 決策樹的剪枝
CART 決策樹的剪枝主要採用的是 CCP 方法,它是一種後剪枝的方法,英文全稱叫做 cost-complexity prune,中文叫做代價複雜度。這種剪枝方式用到一個指標叫做節點的表面誤差率增益值,以此作為剪枝前後誤差的定義。用公式表示則是:
其中 Tt 代表以 t 為根節點的子樹,C(Tt) 表示節點 t 的子樹沒被裁剪時子樹 Tt 的誤差,C(t) 表示節點 t 的子樹被剪枝後節點 t 的誤差,|Tt|代子樹 Tt 的葉子數,剪枝後,T 的葉子數減少了|Tt|-1。
所以節點的表面誤差率增益值等於節點 t 的子樹被剪枝後的誤差變化除以剪掉的葉子數量。
因為我們希望剪枝前後誤差最小,所以我們要尋找的就是最小α值對應的節點,把它剪掉。這時候生成了第一個子樹。重複上面的過程,繼續剪枝,直到最後只剩下根節點,即為最後一個子樹。
得到了剪枝後的子樹集合後,我們需要用驗證集對所有子樹的誤差計算一遍。可以通過計算每個子樹的基尼指數或者平方誤差,取誤差最小的那個樹,得到我們想要的結果。
四、總結
最後我們來整理下三種決策樹之間在屬性選擇標準上的差異:
ID3 演算法,基於資訊增益做判斷;
C4.5 演算法,基於資訊增益率做判斷;
CART 演算法,分類樹是基於基尼係數做判斷。迴歸樹是基於偏差做判斷。