引入:去不去相親?
我們現實生活中經常見到自己或者其他人做許多決策:今天是否去打球?明天要不要逃課?家人介紹的相親對象要不要去見?
我們以最後一個爲例:
比如說小好媽媽對小好說:“我同事家一男孩兒還不錯,你週末去見見。”
小好問:“他多大了?超過30我不見”
媽媽:“沒有,和你同歲”
小好:“長得帥不帥?低於一般水平我不見”
媽媽:“長得可帥了,像古天樂呢!”
小好:“O(∩_∩)O哈哈~那他收入怎樣?”
(PS.emmm…像古天樂肯定就去啦,但是,爲了例子的完整性,還是要象徵性的再問一個問題~)
媽媽:“一般般”
小好:“是公務員嗎?”
媽媽:“是的”
小好:“那我去見~”
上述對話可用決策樹表示爲:
這就是決策樹比較簡單的表現形式,用樹的形式更直觀的展現決策流程。
可能有人會問:這我幹嘛要做成樹的形式,我不用樹也可以清楚的知道結果。但是這是針對很小樣本的情況,如果樣本量很大,或者小好的要求很零散、很多,這時不用決策樹很難一目瞭然的得到一般規律和結果。
這就是決策樹算法存在的意義,那麼決策樹算法到底是什麼?讓我們來推開決策樹的大門!
決策樹(Decision Tree)
定義
決策樹(Decision Tree),又稱爲判定樹,是數據挖掘技術中的一種重要的分類方法,它是一種以樹結構(包括二叉樹和多叉樹)形式來表達的預測分析模型。
決策樹方法是一種比較通用的分類函數逼近法,它是一種常用於預測模型的算法,通過將大量數據有目的分類,找到一些有潛在價值的信息
。
具體用到的知識我們下一步說,先來看看決策樹的構造。
決策樹的構造
就像上圖是否去相親的決策樹,其中綠色節點表示判斷條件,橙色節點表示決策結果,箭頭表示在一個判斷條件在不同情況下的決策路徑,圖中紅色箭頭表示了上面例子中女孩的決策過程。
由上可知,決策樹需要有內部節點:判別條件(年齡、長相、收入、是否是公務員);葉節點:決策結果(是否去相親);分支:不同判別條件下的決策路徑;而根節點就是樹最頂層的節點,可以理解爲最先的分裂屬性,也是所有內部節點的父節點。
Note.什麼是父子節點?
由上述圖形,年齡節點是長相節點的父節點,也是根節點,子節點由父節點根據某分類規則得來。決策樹的樹結構⎩⎪⎨⎪⎧根節點內部節點葉節點
決策樹算法原理
在介紹決策樹算法原理之前,需要了解一點:決策樹的構建準則是使信息量以最快的速度降低到0。
那麼,什麼是信息量?如何衡量信息量?
在信息論中,認爲信源輸出的消息是隨機的。即在未收到消息之前,是不能肯定信源到底發送什麼樣的消息。而通信的目的也就是要使接收者在接收到消息後,儘可能多的解除接收者對信源所存在的疑義(不定度),因此這個被解除的不定度實際上就是在通信中所要傳送的信息量。”
簡而言之,信息量是用來描述信息的不確定程度。比如說,'太陽東昇西落’這件事是必然事件,對於這種大家都已知的必然事件帶來的信息量就爲0,因爲沒有消除大家的任何疑慮,也就是說這個信息不存在任何不確定性程度的變動(因爲他就是確定的,必然發生的)。
再比如:專業射擊選手小王(成績平均在9.0)和射擊新手小明(成績平均在5.0)分別進行射擊,對於觀衆來說,小王射擊這個事件的不確定性較小(通常在9.0附近),而小明射擊的不確定性較大(可能時好時壞),所以可以認爲小明射擊事件的信息量更大。
那麼信息量是用什麼來衡量的呢?就像我們剛纔舉的例子,“信息的不確定程度的變化”,我們使用信息的不確定程度的降低來衡量信息量的大小。
由於上述的例子中,必然事件(概率爲1)的信息量最小,爲零。不確定性越大的事件(小明射擊)的信息量較大。因此,信息量和概率有着必然的聯繫。
信息量的公式爲:h(x)=−log2p(x)
信息熵的公式爲:H(X)=−∑iNpi(xi)log(pi(xi))
信息熵:信息對可能產生的信息量的期望——信息量的所有可能取值,即所有可能發生事件所帶來的信息量的期望。
注:信息熵用來衡量事件不確定性。其值越大,不確定性越高。
回到這句話:決策樹的構建準則是使信息量以最快的速度降低到0
可見,決策樹的構建過程是一種優化思想:確定性增益最大化!(將‘不確定性’看做‘不純度’,‘確定性’看做‘純度’)即決策樹構建要使純度增益最大化。
綜上可得,決策樹的算法原理
:根據不純度函數,使每一次選擇分裂屬性帶來的數據不純度下降最大(純度增益最大化)。
那麼,決策樹算法的重點就到了不純度函數
和分裂屬性的選擇
上。
決策樹算法
根據決策樹的發展史,經典算法的演變和發展分別爲:ID3——>C4.5——>CART,本文主要介紹這三個算法。
對於不同方法的分裂屬性的選擇方法先總結至下表:
算法 |
分裂屬性度量 |
ID3 |
信息增益 |
C4.5 |
信息增益率 |
CART |
基尼係數 |
下面,我們根據一個例子,分別詳細講解ID3,C4.5,CART算法的分類過程。
例:根據日誌密度,好友密度、是否使用真實頭像、來判斷賬戶是否真實,如下是10條數據(無實意)
日誌密度 |
好友密度 |
是否使用真實頭像 |
賬戶是否真實 |
s |
l |
yes |
yes |
m |
l |
no |
yes |
l |
m |
yes |
yes |
m |
m |
yes |
yes |
l |
m |
yes |
yes |
l |
m |
no |
yes |
s |
s |
no |
no |
m |
s |
no |
no |
m |
s |
no |
yes |
s |
s |
yes |
no |
下面通過計算來深入瞭解每一個算法。
(1)ID3算法
1、原理介紹:若選擇一個屬性,使數據分類效果最好,其產生的信息增益最大。(因此,ID3算法以信息增益最大化來選擇分裂屬性)
對信息增益的理解——信息增益:若原數據信息熵爲I0,根據屬性A劃分後得到信息熵I1,I2;信息增益爲:I0−(I1+I2)
首先,介紹ID3算法的計算公式:
總信息熵:H(D)=−i=1∑cp(xi)logp(xi)某屬性分裂後的信息熵:H(D∣分裂屬性A)=j=1∑m∣D∣∣Dj∣∗i=1∑cp(xi)logp(xi)信息增益:gain(分裂屬性A)=H(D)−H(D∣分裂屬性A)
2、案例計算,
信息熵的計算——
賬戶是否真實:{yes:7no:3
目標變量的信息熵:H(D)=−107log2107−103log2103=0.8813
①日誌密度:
H(D∣日志密度)=103[−31log231−32log232]+104[−41log241−43log243]+103[−33log233−0]=0.6gain(日志密度)=H(D)−H(D∣日志密度)=0.8813−0.6=0.2813
②好友密度:
H(D∣好友密度)=104[−41log241−43log243]+104[−44log244−0]+102[−22log222−0]=0.3245 gain(好友密度)=H(D)−H(D∣好友密度)=0.8813−0.3245=0.5568
③是否使用真實頭像:
H(D∣是否使用真實頭像)=105[−51log251−54log254]+105[−53log253−52log252]=0.8464 gain(是否使用真實頭像)=H(D)−H(D∣是否使用真實頭像)=0.8813−0.8464=0.0349
gain(好友密度)>gain(日志密度)>gain(是否使用真實頭像)因此,首先選擇好友密度作爲分裂屬性,後面步驟相同,本文略。
3、ID3算法缺陷
對於ID3算法,若我們將數據增加一列ID:1,2,3,4,5,6,7,8,9,10
此時,H(D∣ID)=j=1∑m∣D∣∣Dj∣∗i=1∑cp(xi)logp(xi)=101[−11log211−0]+...+101[−11log211−0]]=0 gain(ID)=H(D)−H(D∣ID)=0.8813−0=0.8813此時信息增益總是最大的,而我們知道按照ID劃分是沒有意義的。由此,我們得出ID3算法的一個缺陷:偏向於選擇多值屬性!
爲了解決這個問題,C4.5算法在其基礎上做出了改進,選擇分裂信息量來消除屬性取值越多信息增益越大的缺陷。
(2)C4.5算法
1、算法原理:引入分裂信息熵splitInf來消除信息增益的計算缺陷(若一個屬性的取值較多,會使Gain變大,splitinf也會越大,相互抵消。),選擇信息增益率越大的屬性作爲分裂屬性。
對信息增益率的理解——信息增益率:若原數據信息熵爲I0,根據屬性A劃分後得到信息熵I1,I2,計算得到每個劃分的分裂信息熵splitInf;信息增益率爲:[I0−(I1+I2)]/splitInf
首先,介紹ID3算法的計算公式:
總信息熵:H(D)=−i=1∑cp(xi)logp(xi)某屬性分裂後的信息熵:H(D∣分裂屬性A)=j=1∑m∣D∣∣Dj∣∗i=1∑cp(xi)logp(xi)某屬性分裂後的分裂信息熵:SplitInfA=−j=1∑m∣D∣∣Dj∣log2∣D∣∣Dj∣信息增益率:gain_ratio(分裂屬性A)=SplitInfAgain(分裂屬性A)
2、案例計算
SplitInf(日志密度)=−103log2103−104log2104−103log2103=1.5709 SplitInf(好友密度)=−104log2104−104log2104−102log2102=1.5219 SplitInf(是否使用真實頭像)=−105log2105−105log2105=1 Gain_Ratio(日志密度)=splitInf(日志密度)gain(日志密度)=1.57090.2813=0.1791 Gain_Ratio(好友密度)=splitInf(好友密度)gain(好友密度)=1.52190.5568=0.3659 Gain_Ratio(是否使用真實頭像)=splitInf(是否使用真實頭像)gain(是否使用真實頭像)=10.0349=0.0349 Gain_Ratio(好友密度)>Gain_Ratio(日志密度)>Gain_Ratio(是否使用真實頭像)因此,首先選擇好友密度作爲分裂屬性。
3、C4.5算法的優點
①解決了ID3算法偏向於多值屬性的缺陷(引入SplitInf)
②能夠處理缺失值
對於上例,若日誌密度存在缺失值如下:
日誌密度 |
好友密度 |
是否使用真實頭像 |
賬戶是否真實 |
s |
l |
yes |
yes |
m |
l |
no |
yes |
l |
m |
yes |
yes |
m |
m |
yes |
yes |
l |
m |
yes |
yes |
? |
m |
no |
yes |
s |
s |
no |
no |
m |
s |
no |
no |
? |
s |
no |
yes |
s |
s |
yes |
no |
日誌密度 |
分類:yes |
分類:no |
合計 |
s |
1 |
2 |
3 |
m |
2 |
1 |
3 |
l |
2 |
0 |
2 |
Step1′去除缺失值後此屬性中各水平的比例
對任意未知的日誌密度的值。取“s”的概率爲3/8;取“m”的概率爲3/8;取“l”的概率爲2/8。
Step2′按比例填充
S1:日誌密度=s的數量——3+2*(3/8)
S2:日誌密度=m的數量——3+2*(3/8)
S3:日誌密度=l的數量——2+2*(2/8)
③能處理連續變量
C4.5算法處理連續屬性的原理是:對不同的可能劃分,分別計算所得的信息增益率,選擇信息增益率最大的那個劃分區間。
Step1′ 將屬性取值排序,計算相鄰值的中點 vi;
Step2′對其中每個取值vi作爲閾值 將數據集劃分爲兩個部分;
Step3′ 計算每個分支的信息增益率;
Step4′ 選擇最大信息增益的分支作爲閾值,將數據分裂爲離散型
PS.詳情可見《數據挖掘導論》P99-P100
(3)CART算法(Classification and Regression Tree)
1、分類樹
CART算法原理:使用二叉樹將預測空間劃分爲若干子集,隨着從根節點到葉節點的移動,每個節點選出最優的分支規則對應的劃分區域,目標變量在該節點上的條件分佈也隨之被確定。
分類準則——基尼係數
首先,介紹CART算法的計算公式:
總Gini:Gini(D)=1−i=1∑cp(xi)2 某屬性分裂後的Gini:Gini(D∣分裂屬性A)=j=1∑m∣D∣∣Dj∣∗Gini(Dj) Gini增益:ΔGini(D∣分裂屬性A)=Gini(D)−Gini(D∣分裂屬性A)
以是否使用真實頭像爲例:
Gini(是否使用真實頭像)=105∗(1−(54)2−(51)2)+105∗(1−(53)2−(52)2) 樣本分佈均勻時Gini指標最大;分類越純Gini越小。選擇Gini係數增益最大的變量最爲分類屬性。
2、迴歸樹
分類準則:最小方差原理
①基於樹的方法:將特徵空間劃分成一系列長方形,然後對每個長方形擬合簡單的模型(常數)
擬合的模型爲:f^(x)=(m=1)∑5cmI{X1,X2∈Rm} ②迴歸樹的分裂準則——最小方差法
對每個變量、根據不同的分割點計算二叉樹兩邊分裂的數值均值,計算其方差,方差最小的分裂點爲其最終分裂點,各個變量方差值對比,選擇最小方差的變量作爲分裂屬性。
迴歸樹的生成計算請戳☞[傳送門]☜
決策樹剪枝
決策樹剪枝方法{預剪枝:在樹完全生長之前停止部分分裂後剪枝:在樹完全生長之後減去部分枝丫
預剪枝(Pre-Pruning)
①若節點中所有觀測屬於一類[沒必要再分],停止劃分
②若樹的深度達到預定閾值,停止劃分
③若該節點所含觀測值小於設定父節點應含觀測值的閾值,停止劃分
④若該節點子節點所含觀測數小於設定閾值,停止劃分
⑤若沒有屬性滿足設定的分裂準則的閾值[純度增加量不夠],停止劃分
後剪枝(Post-Pruning)
以CCP(Cost Complexity Pruning)爲例——
CCP剪枝:選擇節點表面誤差率增益值最小的非葉子節點,刪除該非葉子節點的左右子節點,若有多個非葉子節點的表面誤差率增益值相同小,則選擇非葉子節點中子節點數最多的非葉子節點進行剪枝。
CCP剪枝的步驟:
1)從原始的決策樹開始生成一個子樹序列(均非葉子節點){T0,T1,…,Tn},其中T(i+1) 從Ti 產生,Tn 爲根節點
2)計算所有節點的誤差率增益值,選擇誤差率增益值最小的非葉子節點
3)對選中節點進行剪枝
4)重複
誤差率增益值的計算公式爲:α=N(T)−1R(t)−R(T) ((非葉節點誤差-葉節點誤差)/(葉節點數-1))
其中,
-
α 衡量的是每個節點所能減少的分類誤差率。α越小,說明該節點所能減少的分類誤差率越小,繼續往下分類意義不大,可以剪枝,將該結點作爲葉結點。
-
R(t)-表示非葉子節點的誤差代價,R(t)=r(t)*p(t), r(t)爲該節點的錯誤率, p(t) 爲節點的數據量佔比
-
R(T)-表示子樹葉子節點的誤差代價,R(T)=∑(i=1)mri(t)pi(t), ri(t)
爲子節點i的錯誤率, pi(t) 表示節點i的數據量佔比
-
N(T)-表示子樹的葉結點個數
eg.
總樣本量:55+25=80
① 對t4節點,
錯誤佔比:46+44
R(t)=r(t)∗p(t),t4錯誤率佔所有觀測的佔比情況:R(t4)=504∗8050=201
R(T)=∑(i=1)mri(t)pi(t),
t8和t9葉節點錯誤率:R(T4)=451∗8045+52∗805=803 (N(t)=2,兩個葉節點)
CCP誤差爲:α=N(T)−1R(t)−R(T)=2−1201−803=0.0125
② 對於非葉子節點t3的子樹
節點錯誤率:15+55
t3節點錯誤佔比:R(t3)=205∗8020=161
t6,t7的錯誤率:R(T3)=51∗805+151∗8015=401 (N(t)=2,兩個葉節點)
CCP誤差爲: α=N(T)−1R(t)−R(T)=2−1161−401=0.0375
③ 對於非葉子節點t2的子樹,
節點誤差佔比:R(t)=r(t)∗p(t)=6010∗8060=81
對應葉子節點誤差率:R(T)=∑(i=1)mri(t)∗pi(t)=451∗8045+52∗805+60∗806+40∗804=803,N(T)=4
CCP誤差爲: α=(N(T)−1R(t)−R(T)=4−181−803=0.0292 關於上面決策樹的所有節點的α值計算結果爲:(自下而上剪枝)
T0:α(t4)=0.0125α(t5)=0.05α(t2)=0.0292α(t3)=0.0375(減去t4枝葉變爲葉節點)
T1:α(t5)=0.05α(t2)=0.0375α(t3)=0.0375(t2與t3的α 值相同,但是裁剪掉前者可以得到更小的決策樹,減去t2變爲葉節點)
以上就是CCP代價複雜度剪枝的全過程。
決策樹的Python實現
例子依然採用上述手算例:
日誌密度 |
好友密度 |
是否使用真實頭像 |
賬戶是否真實 |
s |
l |
yes |
yes |
m |
l |
no |
yes |
l |
m |
yes |
yes |
m |
m |
yes |
yes |
l |
m |
yes |
yes |
l |
m |
no |
yes |
s |
s |
no |
no |
m |
s |
no |
no |
m |
s |
no |
yes |
s |
s |
yes |
no |
1、導入需要的包
import pandas as pd
import numpy as np
from sklearn import tree
from sklearn.preprocessing import LabelEncoder
2、讀入數據
data = pd.read_csv('data/example.csv',engine='python')
data
3、數據處理
x1 = pd.get_dummies(pd.DataFrame(data['日誌密度'].values.tolist()),prefix='日誌密度',prefix_sep='_',drop_first=True).\
groupby(axis=1,level=0).max()
x2 = pd.get_dummies(pd.DataFrame(data['好友密度'].values.tolist()),prefix='好友密度',prefix_sep='_',drop_first=True).\
groupby(axis=1,level=0).max()
del data['日誌密度']
del data['好友密度']
data = pd.concat([data,x1,x2],axis=1)
data
data['是否使用真實頭像'] = LabelEncoder().fit_transform(np.array(data['是否使用真實頭像']))
data['賬戶是否真實'] = LabelEncoder().fit_transform(np.array(data['賬戶是否真實']))
4、數據轉化
y = data['賬戶是否真實']
x = data.drop(['賬戶是否真實'],axis=1)
5、建模
tree_model = tree.DecisionTreeClassifier(criterion='gini')
tree_model.fit(x,y)
6、畫決策樹
import graphviz
dot_data = tree.export_graphviz(tree_model,
out_file=None,
feature_names= ['是否使用真實頭像', '日誌密度_m', '日誌密度_s', '好友密度_m', '好友密度_s'],
class_names= ['no','yes'],
filled=True,
rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
得到如下圖:
可見,和上述手算例得到相同結果!