1. 程式人生 > >CART決策樹演算法總結

CART決策樹演算法總結

CART決策樹演算法,顧名思義可以建立分類樹(classification)和迴歸樹(regression)。

1.分類樹。

當CART決策樹演算法用於建立分類樹時,和ID3和C4.5有很多相似之處,但是CART採用基尼指數作為選擇劃分屬性的依據,資料集的純度用基尼值來度量,具體公式為
Gini(D)=1Ck=1pk,其中pk是第K類樣本的概率,C為屬性的數量。
直觀的來看,基尼值反應了從資料集中隨機抽取兩個樣本,其類別標記不一樣的概率,也就是說基尼指數越大,當前資料集合越“混亂”。
對於一個屬性a,定義基尼指數(Gini index)的計算公式為
Giniindex(D,

a)=Vv=1|Dv|/|D|Gini(Dv)
於是,在劃分屬性時,選擇使得劃分後基尼指數最小的屬性作為最優屬性。

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)