決策樹之CART演算法原理及python實現
阿新 • • 發佈:2019-01-09
1 CART演算法
CART 是在給定輸入X條件下輸出隨機變數Y的條件概率分佈的學習方法。CART二分每個特徵(包括標籤特徵以及連續特徵),經過最優二分特徵及其最優二分特徵值的選擇、切分,二叉樹生成,剪枝來實現CART演算法。對於迴歸CART樹選擇誤差平方和準則、對於分類CART樹選擇基尼係數準則進行特徵選擇,並遞迴呼叫構建二叉樹過程生成CART樹。
決策樹的經典演算法包括ID3、C4.5、CART演算法,其應用領域及所使用的準則,如下圖所示。
2 CART生成演算法
- 最小二乘迴歸樹生成演算法
之所以稱為最小二乘迴歸樹,是因為,迴歸樹以誤差平方和為準則選擇最優二分切點,該生成演算法在訓練資料集上所在的輸入空間中,遞迴的將每個區域劃分為兩個子區域並決定每個子區域的輸出值,在這裡分為兩種情況,一是輸出值為子區域輸出值的均值該種情況下為迴歸樹,二是輸出值為子區域輸入與輸出的線性迴歸,輸出值為迴歸係數,該種情況下為模型樹。
演算法實現步驟:
1)選擇最優切分特徵J與切分點s,按照如下原則:
m
c1,c2 分別為左右子區域輸出的均值(模型樹時是輸出變數的迴歸值),可通過遍歷每個變數的每個可能取值來切分資料集找出最優切分點。
2)用切分點劃分區域並決定相應的輸出值
3)遞迴呼叫1)2)直到滿足停止條件
4)利用字典,遞迴呼叫建立二叉樹,生成決策樹 - CART生成演算法(分類樹)
在這裡需要提一下基尼係數:
在分類問題中,假設有K 類,樣本點屬於第k 類的概率為pk ,則概率分佈的基尼指數定義為:
Gini(p)=∑Kk=1pk(1−pk)=(p1+p2+...+pK)−∑Kk=1p2k=1−∑Kk=1p2k
對於分類問題:設C 為D 中屬於第k 類的樣本子集,則基尼指數為:
Gini(D)=1−∑Kk=1(|Ck||D|)2
設條件A 將樣本D 切分為D1 和D2 兩個資料子集,則在條件A 下的樣本D 的基尼指數為:
Gini(D,A)=|D1|DGini(D1)+|D2|DGini(D2)
注意:基尼指數也表示樣本的不確定性,基尼指數值越大,樣本集合的不確定性越大。
演算法實現步驟:
1)計算現有樣本D 的基尼指數,之後利用樣本中每一個特徵A ,及A 的每一個可能取值a ,根據A>=a 與A<a 將樣本分為兩部分,並計算Gini(D,A) 值
2)找出對應基尼指數最小Gini(D,A) 的最優切分特徵及取值,並判斷是否切分停止條件,否,則輸出最優切分點
3)遞迴呼叫1)2)
4)生成CART決策樹 - 最小二乘迴歸樹的python實現流程圖
- python程式
# -*- coding: utf-8 -*-
"""
Created on Wed May 24 16:58:05 2017
CART
@author: Administrator
"""
import numpy as np
import pickle
import treePlotter
def loadDataSet(filename):
'''
輸入:檔案的全路徑
功能:將輸入資料儲存在datamat
輸出:datamat
'''
fr = open(filename)
datamat = []
for line in fr.readlines():
cutLine = line.strip().split('\t')
floatLine = map(float,cutLine)
datamat.append(floatLine)
return datamat
def binarySplitDataSet(dataset,feature,value):
'''
輸入:資料集,資料集中某一特徵列,該特徵列中的某個取值
功能:將資料集按特徵列的某一取值換分為左右兩個子資料集
輸出:左右子資料集
'''
matLeft = dataset[np.nonzero(dataset[:,feature] <= value)[0],:]
matRight = dataset[np.nonzero(dataset[:,feature] > value)[0],:]
return matLeft,matRight
#--------------迴歸樹所需子函式---------------#
def regressLeaf(dataset):
'''
輸入:資料集
功能:求資料集輸出列的均值
輸出:對應資料集的葉節點
'''
return np.mean(dataset[:,-1])
def regressErr(dataset):
'''
輸入:資料集(numpy.mat型別)
功能:求資料集劃分左右子資料集的誤差平方和之和
輸出: 資料集劃分後的誤差平方和
'''
#由於迴歸樹中用輸出的均值作為葉節點,所以在這裡求誤差平方和實質上就是方差
return np.var(dataset[:,-1]) * np.shape(dataset)[0]
def regressData(filename):
fr = open(filename)
return pickle.load(fr)
#--------------迴歸樹子函式 END --------------#
def chooseBestSplit(dataset,leafType=regressLeaf,errType=regressErr,threshold=(1,4)):#函式做為引數,挺有意思
thresholdErr = threshold[0];thresholdSamples = threshold[1]
#當資料中輸出值都相等時,feature = None,value = 輸出值的均值(葉節點)
if len(set(dataset[:,-1].T.tolist()[0])) == 1:
return None,leafType(dataset)
m,n = np.shape(dataset)
Err = errType(dataset)
bestErr = np.inf; bestFeatureIndex = 0; bestFeatureValue = 0
for featureindex in range(n-1):
for featurevalue in dataset[:,featureindex]:
matLeft,matRight = binarySplitDataSet(dataset,featureindex,featurevalue)
if (np.shape(matLeft)[0] < thresholdSamples) or (np.shape(matRight)[0] < thresholdSamples):
continue
temErr = errType(matLeft) + errType(matRight)
if temErr < bestErr:
bestErr = temErr
bestFeatureIndex = featureindex
bestFeatureValue = featurevalue
#檢驗在所選出的最優劃分特徵及其取值下,誤差平方和與未劃分時的差是否小於閾值,若是,則不適合劃分
if (Err - bestErr) < thresholdErr:
return None,leafType(dataset)
matLeft,matRight = binarySplitDataSet(dataset,bestFeatureIndex,bestFeatureValue)
#檢驗在所選出的最優劃分特徵及其取值下,劃分的左右資料集的樣本數是否小於閾值,若是,則不適合劃分
if (np.shape(matLeft)[0] < thresholdSamples) or (np.shape(matRight)[0] < thresholdSamples):
return None,leafType(dataset)
return bestFeatureIndex,bestFeatureValue
def createCARTtree(dataset,leafType=regressLeaf,errType=regressErr,threshold=(1,4)):
'''
輸入:資料集dataset,葉子節點形式leafType:regressLeaf(迴歸樹)、modelLeaf(模型樹)
損失函式errType:誤差平方和也分為regressLeaf和modelLeaf、使用者自定義閾值引數:
誤差減少的閾值和子樣本集應包含的最少樣本個數
功能:建立迴歸樹或模型樹
輸出:以字典巢狀資料形式返回子迴歸樹或子模型樹或葉結點
'''
feature,value = chooseBestSplit(dataset,leafType,errType,threshold)
#當不滿足閾值或某一子資料集下輸出全相等時,返回葉節點
if feature == None: return value
returnTree = {}
returnTree['bestSplitFeature'] = feature
returnTree['bestSplitFeatValue'] = value
leftSet,rightSet = binarySplitDataSet(dataset,feature,value)
returnTree['left'] = createCARTtree(leftSet,leafType,errType,threshold)
returnTree['right'] = createCARTtree(rightSet,leafType,errType,threshold)
return returnTree
#----------迴歸樹剪枝函式----------#
def isTree(obj):#主要是為了判斷當前節點是否是葉節點
return (type(obj).__name__ == 'dict')
def getMean(tree):#樹就是巢狀字典
if isTree(tree['left']): tree['left'] = getMean(tree['left'])
if isTree(tree['right']): tree['right'] = getMean(tree['right'])
return (tree['left'] + tree['right'])/2.0
def prune(tree, testData):
if np.shape(testData)[0] == 0: return getMean(tree)#存在測試集中沒有訓練集中資料的情況
if isTree(tree['left']) or isTree(tree['right']):
leftTestData, rightTestData = binarySplitDataSet(testData,tree['bestSplitFeature'],tree['bestSplitFeatValue'])
#遞迴呼叫prune函式對左右子樹,注意與左右子樹對應的左右子測試資料集
if isTree(tree['left']): tree['left'] = prune(tree['left'],leftTestData)
if isTree(tree['right']): tree['right'] = prune(tree['right'],rightTestData)
#當遞迴搜尋到左右子樹均為葉節點時,計算測試資料集的誤差平方和
if not isTree(tree['left']) and not isTree(tree['right']):
leftTestData, rightTestData = binarySplitDataSet(testData,tree['bestSplitFeature'],tree['bestSplitFeatValue'])
errorNOmerge = sum(np.power(leftTestData[:,-1] - tree['left'],2)) +sum(np.power(rightTestData[:,-1] - tree['right'],2))
errorMerge = sum(np.power(testData[:,1] - getMean(tree),2))
if errorMerge < errorNOmerge:
print 'Merging'
return getMean(tree)
else: return tree
else: return tree
#---------迴歸樹剪枝END-----------#
#-----------模型樹子函式-----------#
def linearSolve(dataset):
m,n = np.shape(dataset)
X = np.mat(np.ones((m,n)));Y = np.mat(np.ones((m,1)))
X[:,1:n] = dataset[:,0:(n-1)]
Y = dataset[:,-1]
xTx = X.T * X
if np.linalg.det(xTx) == 0:
raise NameError('This matrix is singular, cannot do inverse,\n\
try increasing the second value of threshold')
ws = xTx.I * (X.T * Y)
return ws, X,Y
def modelLeaf(dataset):
ws,X,Y = linearSolve(dataset)
return ws
def modelErr(dataset):
ws,X,Y = linearSolve(dataset)
yHat = X * ws
return sum(np.power(Y - yHat,2))
#------------模型樹子函式END-------#
#------------CART預測子函式------------#
def regressEvaluation(tree, inputData):