前言
這篇文章主要講述一下決策樹的基本算法,不要以為簡單,其實呀!決策樹這個算法說起來很簡單,思路也很簡單明了。但是如果你深入了解一下,里面的內容也相當的豐富,能細講的也很多。這一次我就以我最近復習的內容和面試時遇到的一些問題為線索,一一來解析決策樹里面深入一點的東西。后續我會接着把決策樹這一塊的算法全部講完,從決策樹到Random Forest, GBDT,XGBOOST。當然由於我的水平也十分有限,寫文章的時候一般不怎么喜歡翻書全是自己大腦里面理解的一些東西(其實是我嫌翻書太麻煩),很多內容可能和你所看到的書上的內容有所不同,當然也會存在一些錯誤。望指正!
正文
分類樹
說到決策樹我們首先會想到的是classification tree這是決策樹用到最廣的一個方面,這一塊的工作講起來也很容易理解。我也不打算在這里花費大量的時間,畢竟太簡單的東西講起來沒什么意義。我主要提幾點大家可能容易忽視掉的,沒有深入想的點。把這些點搞清楚,一般的分類決策樹基本不會有很大的問題。
分類決策樹的核心思想就是在一個數據集中找到一個最優特征,然后從這個特征的選值中找一個最優候選值(這段話稍后解釋),根據這個最優候選值將數據集分為兩個子數據集,然后遞歸上述操作,直到滿足指定條件為止。
上述這段話是我對分類決策樹的一個簡單概述,里面有幾個需要注意的點:
1.最優特征怎么找?這個問題其實就是決策樹的一個核心問題了。我們常用的方法是更具信息增益或者信息增益率來尋找最優特征,信息增益這東西怎么理解呢!搞清這個概念我們首先需要明白熵這個東西!熵簡單的講就是說我們做一件事需要的代價,代價越高肯定就越不好了。放到機器學習的數據集中來講就是我們數據的不確定性,代價越高對應的不確定就越高,我們用決策樹算法的目的就是利用數據的一些規則來盡可能的降低數據集的不確定性。好了,有了這個思想我們要做的事就很簡單了,給定一批數據集,我們可以很容易得到它的不確定性(熵),然后呢!我們要想辦法降低這個不確定性,我們需要挖掘數據集中有用的特征,在某個特征的限制下,我們又能得到數據集的不確定性(這個其實就是書上的條件熵),一般而言給定了一個有用的特征,數據的不確定性肯定降低了(因為有一個條件約束,比沒有條件約束效果肯定會好一點,當然你的特征沒有用,那就另說了)。我們用兩次的值作差,這個結果的含義很明了,給定了這個特征,讓我們數據集的不確定性降低了多少,當然降低的越多這個特征肯定就越好了。而我們講了這么多就是要找到那一個讓數據集不確定性降低最多的特征。我們認為這個特征是當前特征中最好的一個。
2.我們找到了最優特征,為什么還要找最優特征的最優候選值?其實呀,找不找主要看我們面對的問題,一般的二分類問題確實沒必要找(因為總共就兩個類),但對於多分類問題,這個還是建議找,為什么要找呢?我們來分析一下:假如我們的某個最優特征有三個類別:我們如果不找就直接分為三個子節點了。這樣會出現一個問題,就是我們的這個分類對特征值會變得敏感,為什么這么說,我們來講一個很簡答的例子:我們平時考試規定了60分及格,這個控制對於大多數學生很好把控,因為就一個條件,相當於一個二分類。但是如果我們把條件控制嚴格一些,比如超過60分,不超過80分為優秀學生(當然有點扯蛋了)。這個控制很多學霸就不好控制了,對於多分類問題也是這個道理,如果我們一下子從父節點直接分了多個子節點,那么我們的數據肯定會對這個控制很敏感,敏感就會導致出錯。出錯不是我們希望看到的,所以我們建議對多分類問題找最優候選值來轉化為二分類問題,同樣多個二分類問題其實也是一個多分類問題,只是多了幾個遞歸過程而已。
3.什么時候停止?停止條件是什么?這個問題其實書上也講得很簡單,基本都是一筆帶過的,我來稍微詳細說一下:我們從問題的根源出發去想一下,我們構造一顆決策樹的目的是什么?當然是為了能在數據集上取得最好的分類效果,很好這就是一個停止標准呀!當我們檢測到數據的分類效果已經夠好了,我們其實就可以停止了。當然我們常用的是控制葉節點,比如控制葉節點的樣本數目,比如當某個子節點內樣本數目小於某一個指定值,我們就決定不再分了。還有葉節點的純度,我們規定葉節點樣本必須屬於同一類才停止,這也是一個停止條件。還有最大樹的深度,比如我們規定樹的最大深度為某一個值,當樹深度到達這個值我們也要停止。還有比如:分裂次數,最大特征數等等。總之停止條件不是死的,我們可以更具自己的問題來隨意控制,開心就好!
講完上面幾個問題,基本分類決策樹的思想應該差不多了。這里為了閱讀方便我還是帖幾個公式僅供參考:
熵:$entropy(D) = -\sum_{i=1}^n P_i*log_2 P_i$
其中D為數據集,i為數據集D的可能分類標簽,$P_i$為該標簽的概率
條件熵:$entropy(D,A) = \sum_{i=1}^k \frac {D_{A_i}}{D} * log_2D_{A_i}$
其中A表示約束特征,k表示A特征的種類
信息增益:$gain(D,A) = entropy(D) - entropy(D,A)$
信息增益率: $gain_rate(D,A) = gain(D,A)/entropy(D,A)$
這里我們對於最基本的決策樹做了簡單的描述,對其中的幾個可能理解不深刻的點進行了詳細的說明,當然很多人覺得決策樹學的差不多了。確實,決策樹的思想就是這么簡單,但是接下來的內容才是決策樹的核心內容。首先介紹兩個決策樹的核心算法ID3和C4.5
ID3算法其實就是我們一般所理解的決策樹算法,其基本步驟就是我們上面所講的步驟,這個算法的核心思想就是用信息增益來選擇最優分類特征,信息增益的公式上面也給出了,這個公式我們仔細分析一下會發現一個問題:我們需要找到gain(D,A)最大的特征,對於一個數據集entropy(D)是給定的,也就是說我們需要entropy(D,A)最小,意思就是我們所選的特征是那些分完后子節點的純度最高的特征,什么樣的特征分完后子節點的特征純度比較高(熵比較小),該特征的子類別很多,即可取值很多的這一類特征。總結一下就是信息增益偏向於去那些擁有很多子類的特征。這也是這個算法的一大致命缺點,任何帶有主觀偏向性的算法都不是一個好的算法,當然ID3算法的另一個缺點也顯而易見,它只能處理那些分類的特征,對於連續值特征毫無辦法(其實我們可以人為的把連續屬性給離散化,但是人為必然會導致可能不准確)。因此就有了下面的這個算法
C4.5是對ID3算法的一個改進,主要改進點就是解決了ID3的幾個缺點。首先C4.5算法用的是信息增益率來代替ID3中的信息增益。從表達式可以看出來,這么做其實就是弱化那些偏向,讓選擇最優特征時更加公平。
另外C4.5的改進就是可以處理連續值(這個方法其實和上面我提到的連續值離散化很類似,可以理解為單點逐一離散化),具體步驟如下:
1.首先對於連續值屬性的值進行排序(A1,A2......An);
2.我們可以在每兩個值之間取一個點,用這個點就可以把該組連續值分為兩部分,比如我們去一個點a1($A1<a1<A2$),則小於a1的部分(A1)大於a1的部分(A2......An)。但是這個a1好嗎?不知道呀!那我們loop一下所有這樣的a(共有n-1個),每一個ai我們可以算出分裂前后的信息增益率,然后我們求一個max即可。很簡單吧!思路很好,但是呀很顯然有個問題,時間開銷有點大。
3.缺失值的處理,ID3不能直接處理(需要我們人工處理,比如單獨賦值或賦一個平均值),C4.5給出了一個很優雅的方式,為什么說優雅,這樣講吧!我們每一個特征的取值都有若干個,根據訓練集每個可能的取值都有一個概率,我們用這個概率來表示這個確實值得可能取值。這樣就顯得很合理了。
C4.5各方面完勝ID3,所以C4.5一出現就被廣泛應用,后面為了解決這個算法的時間開銷問題,推出了這個算法的改進版C5.0。國外有一篇比較C4.5和C5.0性能的blog寫的很好,可以搜一下。大體上C5.0的速度是C4.5的10倍以上。
我們講了這么多其實都沒逃脫classification tree。我們還有很大一塊問題沒有講,那就是regression。這里我們在來講講regression tree。講到regression tree我們就不能不提大名鼎鼎的CART(classification and regression tree)樹了,這個才是最為核心的決策樹。為什么這么說,因為呀我們以后使用到的random forest,gbdt, xgboost里面的base estimator 都是CART樹。所以這個東西我來認真講講。
CART樹
CART樹既可以用於分類問題也可以用於回歸問題,用於分類問題的思想和我們上面介紹的ID3,C4.5其實是一樣的,唯一的不同就是CART樹用的是基尼指數來確定最優划分點的。
基尼指數: $gini(D) = \sum_{i=1}^n p_k *(1-p_k)$
基尼指數的通俗解釋就是:表示一件事物的不確定性,基尼指數越大不確定性越大。我們要找基尼指數小的特征,這樣的特征對於划分數據集的准確性會更高(不確定性低嘛)
類似的有一個條件基尼指數:$gini(D,A) = \sum_{i=1}^k \frac {D_{A_i}}{D} *gini(D_{A_i})$
整體思路跟信息增益一樣,我就不浪費時間了。
對於回歸問題:首先簡單描述一下決策樹處理回歸問題的流程:對於一個數據集我們可以將其分為m個子區間(R1,R2......Rm)對於每一區間我們可以產生一個對應的輸出cm.我們的最終輸出$f(x)=\sum_{i=1}^mc_mI(x \in R_m)$.對於一個給定的回歸樹我們用平方誤差來表示每個單元的損失$\sum_{x_i \in R_m}(y_i-f(x_i))^2$,那么我們每個單元的最優輸出就是使該單元的損失函數最小。每個單元的最終輸出可以表示為$C = avg(y_i|x_i)(x_i \in R_m)$(區間$R_m$ 上所有$x_i$ 的輸出$y_i$的均值)
對於回歸問題,我們面臨的問題也是如何確定划分點(決策樹的核心)。這里CART樹的處理方式和C4.5處理連續變量的方式有點類似,即對於每個特征的取值,我們從中找一個點j,這個點j可以將該特征分為兩部分$R_1 = ({ x|x_i <j })$和$R_2=(x|x_i>j)$.我們的目標是使切分后的損失函數最小,即:
$$min { min (y_i - c_i)^2(x_i \in R_1) + min (y_i - c_i)^2(x_i \in R_2)} $$
我們loop一次該特征的所有取值,一定能找到一個使上式最小的j,這個j就是最優切分點,對應的特征就是最優特征(當然這個特征其實也需要loop一次才能找到),我們將數據集分為兩部分,然后遞歸直到停止。
這里我們必須強調一下,我們在使用決策樹尋找每一步的最優切分點時,常用的是貪心算法,貪心算法有一個問題就是局部最優,而不是全局最優。所以我們一定要記住,決策樹在選擇特征及切分點時考慮的是一個局部最優問題。
好了!上面基本就是決策樹最常用的三個算法,我們先介紹了分類樹及分類樹中兩個經典算法ID3和C4.5,然后我們又介紹了回歸樹CART樹,這個樹在目前是主流的決策樹,使用很廣泛,必須十分熟悉其中的一些關鍵問題。
剪枝
我們現在有了算法,可以生成一棵決策樹,但是這可決策樹對於未知數據能獲得到很好的性能嗎?出現過擬合該怎么辦?這些問題我們其實還沒有解決。對於任何一個機器學習問題,我們不光要找到一個好的算法,還要保證這個算法有很強的魯棒性,也就是泛化能力要很強。接下來我們講一講決策樹處理過擬合保證樹的泛化能力的方法,決策樹的剪枝。
我們首先看一下我們十分熟悉的機器學習損失函數表達式$f(x)=f_1(x) + \epsilon J(x)$
其中$f_1(x)$是模型的損失函數,$J(x)$是模型的復雜度。
一個機器學習模型的最終損失函數就是要既保證模型對數據集的損失函數最小,同時模型復雜度要低,但顯然這兩個之間是矛盾的,道理很簡單,模型越復雜意味着約束規則越多,分類效果肯定會更好,$f_1(x)$自然會更小,但是$J(x)$就會相應較大。而參數$\epsilon$就是控制兩個之間的均衡。我們所能做到的就是在兩者之間保持均衡。這些問題可以看看有關bias-variance的相關知識(理解這一點對學習機器學習算法很重要)。
接下來我們來看一下決策樹的整體損失函數:
$$C(x) =C_a(x) + \epsilon |T| $$
這個損失函數跟機器學習的損失函數很像,前一部分是樹的損失函數,后部分是樹模型的復雜度,$\epsilon$是一個控制參數
而$C_a(x) = \sum_{t=1}^k N_t H(t)$這個式子的$N_t$表示樹的葉節點數,$H(t)$是樹的葉節點的熵。整體含義就是表示每個葉節點的不確定性。在回過頭來看樹的損失函數就是:樹整體的不確定性加上樹模型的復雜度。我們的優化目標就是保證樹的整體不確定性盡量低,同時樹模型的復雜度也要低。
有了這個思路我們在來分析如何降低樹整體的損失函數:
1.減少第一部分樹的不確定性:我們可以增加一些特征,增加特征,意味着約束條件變強了,決策樹的分類效果自然而然的會變好。但是對於給定的數據集我們是很難再繼續添加新特征了,所以這個方式雖然可取但不可行。
2.減少第二部分樹的整體復雜度:我們需要對生成的樹進行剪枝。剪枝完后樹的規模剪小了,但是樹整體的不確定性可能會增大,這時我們就要尋找一個比較優雅的剪枝策略,在降低樹模型復雜度的情況下,盡量小的增加樹的不確定性。
最簡單的剪枝策略的介紹可以看看西瓜書上的介紹,分為預剪枝和后剪枝,書上的介紹過於簡單,沒有深究。只是給我們提供了一個樹剪枝的思路,這里我就不再贅述。如果尚不清楚的,可以翻一翻書。我這里主要講的是CART樹的剪枝策略,這個算法在《統計學習方法》中有一定篇幅的介紹,但感覺還是不夠細,光看這本書上的介紹,想要完全弄清楚也是比較難,這里我參考了幾篇論文和blog的介紹結合自己的理解來詳細講一講CART樹的剪枝。
我們首先還是來看一下決策樹的損失函數:$$C(x) =\sum_{t=1}^k N_t H(t) + \epsilon |T| $$
對於參數$\epsilon$,當其為0,意味着樹的損失函數即為樹整體的不確定性,當$\epsilon$無窮大時,樹的規模就必須很小,此時的樹即為根節點一個點。這樣的樹就是我們所謂的decision tree stump(決策樹樁).而對於CART樹的剪枝過程,一般情況使用的是后剪枝法,即先生成一顆足夠大的樹,然后在這個樹上進行適當剪枝,以提高樹的泛化能力.
假設我們已經生成了一顆足夠大的樹,我們接下來需要對樹進行剪枝,具體怎么做.首先我們假設我們對樹進行了一次剪枝,得到了一顆子樹T1,這個樹一定對於一個唯一的參數$\epsilon$,但這棵子樹是不是最好子樹我們並不能確定,我們首先是一個隨機剪的過程,對於這個隨機剪的過程,我們可以生成大量的子樹,到底那一顆子樹才是最優子樹T1,我們需要進行數學上的定量計算.假設剪枝節點為t.
剪枝前: $C_\alpha(T) = C(T) + \epsilon |T|$
剪枝后: $C_\alpha(T_t) = C(T_t) + \epsilon|t|$(其中t為剪枝后的葉節點,其規模為1)
上述兩個式子中,已知的變量為$C(T),C(T_t),|T|,|t|$,未知的變量只剩$\epsilon$,我們假設$\epsilon$從0開始增大,當$\epsilon$為0時,這個時候由於剪枝前樹的損失函數肯定小於剪枝后,所以有$C_\alpha(T) < C_\alpha(T_t)$,當$\epsilon$緩慢增大時,由於|T|>|t|所以$C_\alpha(T)$增長速度更快,那么一定存在一個臨界點使$C_\alpha(T)=C_\alpha(T_t)$.這個時候由於t的規模比T小所以,我們認為剪枝對樹整體的損失函數沒有影響,所以進行剪枝.我們可以用$g(t) = \frac{C(t)-C(T)}{|T|-1}$來衡量剪枝的整體損失函數的減少程度,剪枝后整體損失函數減少的越小,我們認為這個剪枝越好.
有了上述思路,我們第一次隨機選一個點剪枝,每次選則不同的點都能得到一個子樹T和對應的參數$\epsilon$,我們在所有剪枝中找到一個最小的g(t),其對應的剪枝點即為第一次剪枝的最優剪枝點,對應的子樹即為第一次剪枝的最優子樹T1,我們然后對子樹T1遞歸的按照上述思路剪枝下去,直到到達根節點位置,這樣我們就能找到一個最優子樹序列{T1,T2,......Tn}以及各個最優子樹對應的參數$\epsilon$.
但是這個序列中到底哪一個才是我們最終剪枝完成的所得到的最優子樹呢?這時我們需要用一批驗證數據來分別對這個最優子樹序列的每一個子樹進行驗證,找到相對於剪之前樹的整體損失函數減少的最小的那個Ti即為我們對這棵樹進行剪枝后得到的最優子樹了.(驗證方式對於大規模數據一般用trian_test_split,小規模數據用cross_validation).
上述過程就是CART樹的剪枝過程,這個是Breiman的經典版本,后續也有其他一些剪枝算法,有興趣可以參考相關論文.
總結
講到這里,決策樹的整個流程基本講的差不多了.從分類樹到回歸樹再到CART樹,從ID3到C4.5.基本常見的決策樹基礎算法,都有所涉及,這些都是我個人對於決策樹的理解,肯定也存在一些錯誤,望交流指正.接下來我還會繼續寫關於Random Forest,gbdt,xgboost的相關文章,喜歡的話可以關注一下!