一、背景及問題
決策樹算法是為了解決二分類問題出現的,是根據歷史經驗(或訓練集)來做判斷,生成決策結果(或樹狀圖)的過程。
/*請尊重作者勞動成果,轉載請標明原文鏈接:*/
/* https://www.cnblogs.com/jpcflyer/p/11037256.html * /
二、決策樹原理
決策樹算法分為兩個階段:構造和剪枝。
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 算法,分類樹是基於基尼系數做判斷。回歸樹是基於偏差做判斷。