機器學習筆記(8)-樹迴歸

樹迴歸CART概述

線性迴歸創建的模型需要擬合所有的樣本點(局部加權線性迴歸除外)。當數據擁有衆多特徵並且特徵之間關係十分複雜時,構建全局模型的想法就顯得太難了,也略顯笨拙。而且,實際生活中很多問題都是非線性的,不可能使用全局線性模型來擬合任何數據。一種可行的方法是將數據集切分成很多份易建模的數據,然後利用我們的線性迴歸技術來建模。如果首次切分後仍然難以擬合線性模型就繼續切分。在這種切分方式下,樹迴歸和迴歸法就相當有用。
如何計算連續型數值的混亂度:計算連續型數值的混亂度是非常簡單的。首先計算所有數據的均值,然後計算每條數據的值到均值的差值。爲了對正負差值同等看待,一般使用絕對值或平方值來代替上述差值。這種做法有點類似於前面介紹過的統計學中常用的方差計算。唯一不同就是,方差是平方誤差的均值(均方差),而這裏需要的是平方誤差的總值(總方差)。總方差可以通過均方差乘以數據集中樣本點的個數來得到。
CART(Classification And Regression Trees, 分類迴歸樹) :可以用於分類還可以用於迴歸。樹構建算法ID3 的做法是每次選取當前最佳的特徵來分割數據,並按照該特徵的所有可能取值來切分。切分過於迅速外,它不能直接處理連續型特徵。CART 是十分著名且廣泛記載的樹構建算法,它使用二元切分來處理連續型變量,對 CART 稍作修改就可以處理迴歸問題。

CART算法

分類與迴歸樹(classification and regression tree ,CART)可以用於分類與迴歸。ID3 和C4.5 樹都是多叉結構,CART樹是二叉樹,內部節點特徵的取值爲“是”和“否”,左分支是取值爲“是”的分支,右分支是取值爲“否”的分支。
迴歸樹的生成:一個迴歸樹對應着輸入空間(特徵空間)的一個劃分以及在劃分的單元上的輸出值。假設已將輸入空間劃分爲M個單元R1,R2,⋯,RM,並在每個單元Rm上有一個固定的輸出值,於是迴歸樹模型可表示爲:
這裏寫圖片描述
當輸入空間的劃分確定時,可以用平方誤差來表示迴歸樹對於訓練數據的預測誤差:
這裏寫圖片描述
下面算法第一步中公式裏面的c1和c2是用求得的切分點j和s劃分的兩個數據集並求其平均值。
這裏寫圖片描述

分類樹的生成

基尼指數:分類問題中,假設有K個類,樣本點屬於第k類的概率爲pk,則概率分佈的基尼指數定義爲:
這裏寫圖片描述
對於給定的樣本集合D,其基尼指數爲:
這裏寫圖片描述
概率分佈的基尼指數與熵意義類似,值越大,其不確定性越大。
如果樣本集合D根據特徵A是否取某一可能值a被分割成D1和D2兩部分,則在特徵A的條件下,集合D的基尼指數定義爲:
這裏寫圖片描述
Gini(D)是計算樣本集的基尼指數,表示集合D的不確定性;
Gini(D,A)是表示經A=a分割後集合D的不確定性,選擇時要選擇最小的,和H(D|A)意義類似,都是取最小的。H(D|A)是對特徵做計算,選擇一個特徵,Gini(D,A)是對特徵及其一個取值做計算,選擇一個特徵及其分割點。
注意:ID3和C4.5建樹時是選取特徵,切分點自動根據特徵取值確定;CART分類樹建樹時是選取特徵及其切分點。
建樹的過程和ID3、C4.5類似,三個結束條件:1.無更多特徵(建樹時下層的節點仍然會有可能用到上層特徵,但不可能是同一切分點);2.節點中的樣本數小於預定的閾值;3.樣本集的基尼指數大於指定閾值。
數據處理
按指定某個值切分矩陣
這裏寫圖片描述

def binSplitDataSet(dataSet, feature, value):
    mat0 = dataSet[nonzero(dataSet[:,feature] > value)[0],:][0]
    mat1 = dataSet[nonzero(dataSet[:,feature] <= value)[0],:][0]
    return mat0,mat1
'''
>>> testMat = mat(eye(4))
>>> testMat[:,1] > 0.5
matrix([[False],[ True],[False],[False]], dtype=bool)
>>> nonzero(testMat[:,1] > 0.5)
(array([1], dtype=int64), array([0], dtype=int64))
>>> nonzero(testMat[:,1] > 0.5)[0]
array([1], dtype=int64)
'''
def regLeaf(dataSet):
    # 負責生成葉節點,當不在對數據進行切分時,調用該函數生成葉節點模型,在迴歸樹中,該模型就是目標變量的均值
    return mean(dataSet[:,-1])
def regErr(dataSet):
    return var(dataSet[:,-1]) * shape(dataSet)[0]
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    '''用最佳方式切分數據集 和 生成相應的葉節點
    對每個特徵:
        對每個特徵值:
            將數據集切分成兩份(小於該特徵值的數據樣本放在左子樹,否則放在右子樹)
            計算切分的誤差
            如果當前誤差小於當前最小誤差,那麼將當前切分設定爲最佳切分並更新最小誤差
    返回最佳切分的特徵和閾值
    :param dataSet:加載的原始數據集
    :param leafType:建立葉子點的函數
    :param errType:誤差計算函數(求總方差)
    :param ops:[容許誤差下降值,切分的最少樣本數]。
    :return:
        bestIndex:bestIndex feature的index座標
        bestValue:bestValue 切分的最優值
    '''
    tolS,tolN = ops[0],ops[1]
    if len(set(dataSet[:,-1].T.tolist()[0])) == 1:  # matrix([[ 0.],[ 1.],[ 0.],[ 0.]]).T.tolist() --> [[0.0, 1.0, 0.0, 0.0]]
        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.tolist()[0]):  # [0]表示這一列的[所有行],不要[0]就是一個array[[所有行]]
            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 (S - bestS) < tolS:     # 如果誤差減少不大,則退出
        return None, leafType(dataSet)
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
        return None, leafType(dataSet)
    return bestIndex,bestValue
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(1,4)):
    '''獲取迴歸樹
    :param dataSet:加載的原始數據集
    :param leafType:建立葉子點的函數
    :param errType:誤差計算函數
    :param ops:容許誤差下降值,切分的最少樣本數
    :return:
        retTree:決策樹最後的結果
    '''
    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  

樹減枝

一棵樹如果節點過多,表明該模型可能對數據進行了 “過擬合”。通過降低決策樹的複雜度來避免過擬合的過程稱爲 剪枝(pruning)。在函數 chooseBestSplit() 中提前終止條件,實際上是在進行一種所謂的 預剪枝(prepruning)操作。另一個形式的剪枝需要使用測試集和訓練集,稱作 後剪枝(postpruning)預剪枝就是及早的停止樹增長,在構造決策樹的同時進行剪枝。所有決策樹的構建方法,都是在無法進一步降低熵的情況下才會停止創建分支的過程,爲了避免過擬合,可以設定一個閾值,熵減小的數量小於這個閾值,即使還可以繼續降低熵,也停止繼續創建分支。但是這種方法實際中的效果並不好。後剪枝就是決策樹構造完成後進行剪枝。剪枝的過程是對擁有同樣父節點的一組節點進行檢查,判斷如果將其合併,熵的增加量是否小於某一閾值。如果確實小,則這一組節點可以合併一個節點,其中包含了所有可能的結果。合併也被稱作 塌陷處理 ,在迴歸樹中一般採用取需要合併的所有子樹的平均值。後剪枝是目前最普遍的做法。

基於已有的樹切分測試數據:
    如果存在任一子集是一棵樹,則在該子集遞歸剪枝過程
    計算將當前兩個葉節點合併後的誤差
    計算不合並的誤差
    如果合併會降低誤差的話,就將葉節點合併

後剪枝

def isTree(obj):  # 測試輸入變量是否是一棵樹,即是否是一個字典
    return (type(obj).__name__=='dict')

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 prune(tree, testData):
    '''
    :param tree:待剪枝的樹
    :param testData:剪枝所需要的測試數據 testData
    :return:
        tree:剪枝完成的樹
    '''
    if shape(testData)[0] == 0: return getMean(tree)  # 判斷是否測試數據集沒有數據,如果沒有,就直接返回tree本身的均值(塌陷處理)
    if (isTree(tree['right']) or isTree(tree['left'])):  # 判斷分枝是否是dict字典,如果是就將測試數據集進行切分
        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:    # 注意返回的結果: 如果可以合併,原來的dict就變爲了 數值
            print "merging"
            return treeMean
        else: return tree
    else: return tree

後剪枝可能不如預剪紙有效,一般爲求最大模型可以同時使用兩種剪枝技術

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章