1. 程式人生 > >CART分類迴歸樹-(python3)

CART分類迴歸樹-(python3)

一、樹迴歸

1、簡介

假設X與Y分別是輸入和輸出向量,並且Y是連續變數,給定訓練資料集 考慮如何生成迴歸樹。 一個迴歸樹對應著輸入空間(即特徵空間)的一個劃分以及在劃分的但單元上的輸出值。假設已將輸入空間劃分為M個單元    ,並且在每個單元    上有一個固定的輸出值    ,於是迴歸樹模型可表示為(簡單來說就是把資料集劃分為多份資料,且每份資料集裡面的輸出一致) 對固定輸入變數    可以找到最優切分點  (找到最小的平方誤差的特徵量) 遍歷所有輸入變數,找到最優切分變數    ,構成一個對    ,依次將輸入空間劃分為兩個區域。接著,每個對每個區域重複上述劃分過程,直到滿足停止條件為止。這樣就生成一棵迴歸樹,這樣的迴歸樹通常被稱為最小二乘樹。[1]

樹迴歸的大致過程

(1)載入資料

#coding:utf-8
from numpy import *
def loadDataset(filename):
    dataMat = []
    fr = open(filename)
    for line in fr.readlines():
        curline = line.strip().split('\t')
        fltline = map(float, curline)
        # print(list(fltline))
        dataMat.append(list(fltline))
    return dataMat
(2)binSplitDataset函式切分資料集
def binSplitDataset(dataSet, feature, value):#以這一列的每個值為界限,大於它和小於它的值,返回的是以這個特徵值為界限分割的資料集
    mat0 = dataSet[nonzero(dataSet[:, feature] > value)[0], :]#返回索引,切割資料集
    mat1 = dataSet[nonzero(dataSet[:, feature] <= value)[0], :]
    return mat0, mat1
(3)函式計算平方誤差,均值
def regLeaf(dataSet):           #建立葉節點函式,value為所有y的均值
    return mean(dataSet[:,-1])
def regErr(dataset):
    return var(dataset[:, -1]) * shape(dataset)[0]#y的方差×y的數量=平方誤差

(4)選擇最好的切割方式
def chooseBestsplit(dataset, leafType=regLeaf, errtype = regErr,ops=(1, 4)):#找到最好的分割葉子節點
    tolS = ops[0]##允許的誤差下降值
    tolN = ops[1] #切分的最小樣本數
    #判斷是否可以分開二叉樹
    # print(len(set(dataset[:, -1].T.tolist()[0])))#不是一下子分開,然後就是先分割整個資料集,然後分割左邊,然後右邊
    if len(set(dataset[:, -1].T.tolist()[0])) == 1:  # #如果剩餘特徵值的數量等於1,不需要再切分直接返回,(退出條件1)
        return None, leafType(dataset)
    m, n = shape(dataset)#行列數
    S = errtype(dataset)#計算平方差
    bestS = inf
    bestIndex = 0
    bestValue = 0
    for featIndex in range(n - 1):#特徵索引
        for splitVal in set((dataset[:, featIndex].T.A.tolist())[0]):  #每一列的每個值
            mat0, mat1 = binSplitDataset(dataset, featIndex, splitVal)#整個資料集,第幾列,那一列的每個值
            if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue#樣本數最小限制
            # print(errtype(mat0))
            newS = errtype(mat0) + errtype(mat1)#計算平方誤差
            if newS < bestS:
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS

    if (S - bestS) < tolS:#如果切分後誤差效果下降不大,則取消切分,直接建立葉結點
        return None, leafType(dataset)
    mat0, mat1 = binSplitDataset(dataset, bestIndex, bestValue)  # 按照儲存的最佳分割來劃分集合
    # #判斷切分後子集大小,小於最小允許樣本數停止切分3
    if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
        return None, leafType(dataset)
    # 返回最佳二元切割的bestIndex和bestValue
    return bestIndex, bestValue#返回特徵編號和用於切分的特徵值
(5)構造迴歸樹
def isTree(obj):
    return (type(obj).__name__=='dict') #判斷為字典型別返回true
#返回樹的所有分支的和的平均值
def getMean(tree):
    if isTree(tree['right']):#找到
        tree['right'] = getMean(tree['right'])#得到的是有右子樹的平均值
    if isTree(tree['left']):
        tree['left'] = getMean(tree['left'])#左子樹的平均值
    return (tree['left']+tree['right'])/2.0#返回的就是整個數對於輸入的特徵的所判斷的均值
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):#assume dataSet is NumPy Mat so we can array filtering
    feat, val = chooseBestsplit(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 prune(tree, testData):#待剪枝的樹和剪枝所需的測試資料
    if shape(testData)[0] == 0:# 確認資料集非空
        return getMean(tree)
    #假設發生過擬合,採用測試資料對樹進行剪枝
    if (isTree(tree['right']) or isTree(tree['left'])): #左右子樹非空
        lSet, rSet = binSplitDataset(testData, tree['spInd'], tree['spVal'])#按照索引,和值分割資料集
    if isTree(tree['left']):
        tree['left'] = prune(tree['left'], lSet)
    if isTree(tree['right']):
        tree['right'] = prune(tree['right'], rSet)
    #剪枝後判斷是否還是有子樹
    if not isTree(tree['left']) and not isTree(tree['right']):#只要有一個空
        lSet, rSet = binSplitDataset(testData, tree['spInd'], tree['spVal'])
        #判斷是否融合
        errorNoMerge = sum(power(lSet[:, -1] - tree['left'], 2)) + \
                       sum(power(rSet[:, -1] - tree['right'], 2))#未熔合的方差
        treeMean = (tree['left'] + tree['right']) / 2.0#平均值
        errorMerge = sum(power(testData[:, -1] - treeMean, 2))#檢視融合後的方差
        #如果合併後誤差變小,融合,將兩個葉子的均值作為節點
        if errorMerge < errorNoMerge:
            print("merging")
            return treeMean
        else:
            return tree
    else:
        return tree

模型樹
def linearSolve(dataSet):   #將資料集格式化為X Y
    m,n = shape(dataSet)
    X = mat(ones((m, n)))
    Y = mat(ones((m, 1)))
    X[:, 1:n] = dataSet[:, 0:n-1]#把x矩陣第一列全設定為1
    Y = dataSet[:, -1]
    xTx = X.T*X
    # print(xTx)
    if linalg.det(xTx) == 0.0: #X Y用於簡單線性迴歸,需要判斷矩陣可逆
        raise NameError('This matrix is singular, cannot do inverse,\n\
        try increasing the second value of ops')
    ws = xTx.I * (X.T * Y)#正規方程
    # print(ws)
    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
    # print(yHat)
    return sum(power(Y - yHat, 2))