CART分類迴歸樹分析與python實現
引言
前面我們分享過一篇決策樹演算法叫ID3:ID3決策樹原理分析及python實現。首先我們來回顧下ID3演算法。ID3每次選取最佳特徵來分割資料,這個最佳特徵的判斷原則是通過資訊增益來實現的。這種按某種特徵切分完資料集後,當前特徵在下次切分資料集時就不再起作用,因此會存在切分方式過於迅速地問題。ID3演算法還存在另一個問題就是它不能直接處理連續型特徵,因此演算法需要改進。於是有人提出了二元切分法很好的解決了連續性變數問題及切分迅速的問題。其中代表性演算法就是CART。
CART分類迴歸樹
CART是Classification And Regression Trees的縮寫叫做“分類迴歸樹”。它既能做分類任務又能做迴歸任務。
- 分類樹:目標變數是類別資料,樹被用來識別目標變數可能屬於哪個類
- 迴歸樹:目標變數是連續資料,樹被用來預測目標變數的值是多少
CART樹的典型代表就是二叉樹,如下圖所示:
CART樹構建演算法
因為ID3決策樹分享時,我們已經分享過樹構建演算法的流程,這裡我們就直接來實現CART樹構建的過程。
樹構建框架
在樹的構建過程中,與ID3類似採用字典來儲存樹的資料結構,該字典包含以下4種元素:
- 待切分的特徵
- 待切分的特徵值
- 右子樹。當不再需要切分的時候,也可以是單個值
- 左子樹。與右子樹類似
函式createTree()虛擬碼如下:
可以很明顯的發現這是個典型的遞迴演算法。
這個演算法與ID3建立分支的演算法createBranch()十分類似:
劃分資料集的程式碼如下:
def binSplitDataSet(dataSet, feature, value):
mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:]
mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:]
return mat0,mat1
通過陣列過濾方式將資料集合切分得到兩個子集並返回。
劃分資料點
建立二進位制決策樹本質上就是遞迴劃分輸入空間的過程。有一種貪心的演算法用來劃分這個空間被稱為遞迴二進位制劃分。所有輸入變數和所有可能的分割點都以貪婪的方式進行評估和選擇(例如,每次選擇最佳的分割點)。
對分類問題來說,基尼指數(Gini index)被用來選擇劃分的屬性,資料集的純度可用基尼值來衡量:
Gini(D)=1−∑k=1|y|p2k
直觀地來說,Gini(D)反映了從資料集D中隨機抽取兩個樣本,其類別標記不一致的概率。因此Gini(D)越小,則資料集D的純度越高。對於二分類問題,這就可以寫成:
Gini(D)=1−p21−p22
那麼屬性a的基尼指數定義為
Gini_index(D,a)=∑v=1V|Dv||D|Gini(Dv)
於是,我們在候選屬性集合A中,選擇那個使得劃分後基尼指數最小的屬性作為最優劃分屬性。
對迴歸問題來說,用最小代價函式來劃分資料點,度量方法就是平方誤差的總值(總方差)
構建樹
在我們構建樹的虛擬碼中有一個“find the best feature to split data”函式,前面我們已經分析了構建迴歸樹時的誤差計算方法,根據這個方法就能找到資料集上最佳的二元切分方式。因此這個函式將會完成兩部分內容:1.用最佳方式切分資料集 2.生成相應的葉節點
我們實現“find the best feature to split data”函式chooserBestSplit(),它的虛擬碼如下:
上述虛擬碼的目標是找到資料集切分的最佳位置。它遍歷所有的特徵及其可能的取值來確定使誤差最小化的切分閾值。
程式碼如下:
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
tolS = ops[0]; tolN = ops[1]
#if all the target variables are the same value: quit and return value
if len(set(dataSet[:,-1].T.tolist()[0])) == 1: #exit cond 1
return None, leafType(dataSet)
m,n = shape(dataSet)
#the choice of the best feature is driven by Reduction in RSS error from mean
S = errType(dataSet)
bestS = inf; bestIndex = 0; bestValue = 0
for featIndex in range(n-1):
for splitVal in set(dataSet[:,featIndex]):
mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
newS = errType(mat0) + errType(mat1)
if newS < bestS:
bestIndex = featIndex
bestValue = splitVal
bestS = newS
#if the decrease (S-bestS) is less than a threshold don't do the split
if (S - bestS) < tolS:
return None, leafType(dataSet) #exit cond 2
mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): #exit cond 3
return None, leafType(dataSet)
return bestIndex,bestValue#returns the best feature to split on
#and the value used for that split
上述程式碼有出返回即函式退出條件:1.剩餘特徵數目為1;直接返回 2.切分資料集後效果提升不大,不需進行切分操作而直接建立葉節點;3提前終止條件均不滿足,返回切分特徵和特徵值。
剪枝
在決策樹的學習中,有時會造成決策樹分支過多,這時就需要去掉一些分支從而降低過擬合的風險。通過降低決策樹複雜度來避免過擬合的過程稱為剪枝。一種是預剪枝,一種是後剪枝。
預剪枝
前面我們的chooseBestSplit()程式碼裡已經有了預減枝的處理就是提前終止條件。但是樹構建演算法對這些提前終止條件很敏感,通過不斷地修改停止條件來得到合理地結果並不是很好的辦法。於是就有人提出了利用測試集對樹進行減枝就是我們接下來要分析的後剪枝。
後剪枝
後剪枝需要從訓練集生成一棵完整的決策樹,然後自底向上對非葉子結點進行考察,利用測試集判斷若將該結點對應的子樹替換成葉結點,能否帶來決策樹泛化效能的提升?將上述思路轉換成虛擬碼如下:
總結
本次博文主要分享了CART樹的構建過程,然後稍稍介紹了下剪枝的方法