CART決策樹演算法總結
CART決策樹演算法,顧名思義可以建立分類樹(classification)和迴歸樹(regression)。
1.分類樹。
當CART決策樹演算法用於建立分類樹時,和ID3和C4.5有很多相似之處,但是CART採用基尼指數作為選擇劃分屬性的依據,資料集的純度用基尼值來度量,具體公式為
直觀的來看,基尼值反應了從資料集中隨機抽取兩個樣本,其類別標記不一樣的概率,也就是說基尼指數越大,當前資料集合越“混亂”。
對於一個屬性a,定義基尼指數(Gini index)的計算公式為
於是,在劃分屬性時,選擇使得劃分後基尼指數最小的屬性作為最優屬性。
2.迴歸樹
當CART決策樹演算法用於迴歸樹時,整個樹是一棵二叉樹,也就是說對於每一個非葉節點,都有一個劃分屬性和一個劃分的值,根據這個值將當前資料集合劃分成兩類,要注意的是,在迴歸樹的時候CART並不刪除當前屬性值,這是因為一個屬性可能要劃分多次。
迴歸樹其實根據葉子節點的型別又可以分為“迴歸樹”(這裡的叫法保持不變)和“模型樹”。
具體的來說,迴歸樹的葉子節點是一個常數,通常取訓練集合中劃分到這個葉子的資料的平均值;而模型樹相當於對於每個節點都建立了一個分段直線,也就是說用一段直線來擬合訓練資料。
下面是用圖片來表示二者的區別
(左側是迴歸樹,右側是模型樹)
當CART演算法用在迴歸樹時,屬性的選擇就不能用基尼指數了,而是用真實值和預測值的平方差的和來作為度量依據。
3.剪枝操作
在決策樹學習演算法中,為了儘可能分類訓練樣本節點劃分過程將不斷重複,構建出來的數會傾向於過擬合,一棵過擬合樹常常十分複雜,這時候就需要剪枝來簡化樹的結構並提高決策樹的泛化能力。
剪枝操作首先將資料集分為訓練集和測試集,用訓練集來生成決策樹,然後用測試機來評價這棵樹看是否進行剪枝操作。
剪枝操作分為預剪枝和後剪枝。預剪枝是指在決策數的生成過程中進行的,如果當前決策樹不能帶來決策樹泛化效能的提高,那麼停止劃分並將當前結點作為葉子節點。後剪枝操作首先生成整棵樹,然後自底向上的對非葉子結點進行考察,如果將當前結點替換為葉子節點能帶來泛化效能的提高,那麼就進行剪枝。
下面是用Python實現的迴歸樹和模型樹的程式碼
from numpy import *
#CART決策樹演算法
def loadDataSet(fileName):
dataMat = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
# 將原資料對映成浮點數
fltLine = list(map(float, curLine))
dataMat.append(fltLine)
return dataMat
def binSplitDataSet(dataSet, feature, value):
mat0 = dataSet[nonzero(dataSet[:, feature] > value)[0], :]
mat1 = dataSet[nonzero(dataSet[:, feature] <= value)[0], :]
return mat0, mat1
# 模型樹的函式
def linearSolve(dataSet):
m, n = shape(dataSet)
x = mat(ones((m, n)))
y = mat(ones((m, 1)))
x[:, 1:n] = dataSet[:, 0:n-1]
y = dataSet[:, -1]
xTx = x.T * x
if linalg.det(xTx) == 0:
raise NameError('This matrix is singular')
ws = xTx.I * x.T * y
return ws, x, y
# 迴歸樹的葉節點,是一個常數
def regLeaf(dataSet):
return mean(dataSet[:, -1])
# 模型樹的葉節點,是一個函式
def modelLeaf(dataSet):
ws, x, y = linearSolve(dataSet)
return ws
# 迴歸樹的誤差計算公式,也就是所有點與葉節點方差之和
def regErr(dataSet):
return var(dataSet[:, -1])*shape(dataSet)[0]
# 模型樹的誤差計算公式,所有點與預測值的方差之和
def modelErr(dataSet):
ws, x, y = linearSolve(dataSet)
yHat = x * ws
return sum(power(yHat-y, 2))
# ops兩個值用來控制劃分的結束,相當於預剪枝
# 第一個只是容許的誤差下降值
# 第二個是切分的最少樣本數量
def chooseBestFeat(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)):
tolS = ops[0]
tolN = ops[1]
# 如果當前集合取值相同,那麼不再繼續劃分
if len(set(dataSet[:, -1].T.tolist()[0])) == 1:
return None, leafType(dataSet)
m, n = shape(dataSet)
currentS = errType(dataSet)
bestFeat = -1
bestS = inf
bestValue = 0
for feat in range(n-1):
valSet = set(dataSet[:, feat].T.tolist()[0])
for val in valSet:
mat0, mat1 = binSplitDataSet(dataSet, feat, val)
# 如果劃分的某一個集合太小,則不進行這次劃分
if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
continue
s0 = errType(mat0)
s1 = errType(mat1)
if s0+s1 < bestS:
bestS = s0+s1
bestFeat = feat
bestValue = val
# 如果最優劃分對於誤差的減少不大,那麼不進行這次劃分
if currentS-bestS < tolS:
return None, leafType(dataSet)
return bestFeat, bestValue
def isTree(obj):
return (type(obj).__name__ == 'dict')
def getMean(node):
if isTree(node['left']):
node['left'] = getMean(node['left'])
if isTree(node['right']):
node['right'] = getMean(node['right'])
return (node['left']+node['right'])/2
# 後剪枝
def prune(node, testData):
# 如果當前測試集為空,認為此時發生了過擬合,進行剪枝
if shape(testData)[0] == 0:
return getMean(node)
# 就對測試集合進行劃分
lSet, rSet = binSplitDataSet(testData, node['spInd'], node['spVal'])
# 對左右子樹進行剪枝
if isTree(node['left']):
node['left'] = prune(node['left'], lSet)
if isTree(node['right']):
node['right'] = prune(node['right'], rSet)
# 如果左右子樹都是葉子節點,那麼對當前結點進行剪枝
if (not isTree(node['left'])) and (not isTree(node['right'])):
errNoMerge = sum(power(lSet[:, -1]-node['left'], 2)) + sum(power(rSet[:, -1]-node['right'], 2))
nodeMean = getMean(node)
errMerge = sum(power(testData[:, -1]-nodeMean, 2))
if errNoMerge < errMerge:
return nodeMean
return node
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1, 4)):
feat, val = chooseBestFeat(dataSet, leafType, errType, ops)
if feat == None:
return val
retTree = {}
retTree['spInd'] = feat
retTree['spVal'] = val
lSet, rSet = binSplitDataSet(dataSet, feat, val)
retTree['left'] = createTree(lSet, leafType, errType, ops)
retTree['right'] = createTree(rSet, leafType, errType, ops)
return retTree
# 運用樹迴歸進行預測
# 樹的結點值
def regVal(node, inData):
return float(node)
def modelVal(node, inData):
n = shape(inData)[1]
x = mat(ones((1, n+1)))
x[:, 1:n+1] = inData
return float(x*node)
# 對單組資料進行預測
def treeForeCast(node, inData, leafVal=regVal):
while isTree(node):
if inData[node['spInd']] > node['spVal']:
node = node['left']
else:
node = node['right']
return leafVal(node, inData)
# 對於輸入資料集進行預測
def createForeCast(node, testData, leafVal=regVal):
m = len(testData)
yHat = mat(zeros((m, 1)))
for i in range(m):
yHat[i, 0] = treeForeCast(node, mat(testData[i]), modelVal)
return yHat
#dataSet = mat(loadDataSet('ex2.txt'))
#print(dataSet)
#root = createTree(dataSet, ops=(0, 1))
#testData = mat(loadDataSet('ex2test.txt'))
#print(root)
#root = prune(root, testData)
#print(root['spVal'])
trainMat = mat(loadDataSet('bikeSpeedVsIq_train.txt'))
testMat = mat(loadDataSet('bikeSpeedVsIq_test.txt'))
root = createTree(trainMat, modelLeaf, modelErr, (1, 20))
yHat = createForeCast(root, testMat[:, 0], modelVal)
rSquare = corrcoef(yHat, testMat[:, 1], rowvar=0)[0, 1]
print(rSquare)