1. 程式人生 > >決策樹之CART演算法原理及python實現

決策樹之CART演算法原理及python實現

1 CART演算法

CART 是在給定輸入X條件下輸出隨機變數Y的條件概率分佈的學習方法。CART二分每個特徵(包括標籤特徵以及連續特徵),經過最優二分特徵及其最優二分特徵值的選擇、切分,二叉樹生成,剪枝來實現CART演算法。對於迴歸CART樹選擇誤差平方和準則、對於分類CART樹選擇基尼係數準則進行特徵選擇,並遞迴呼叫構建二叉樹過程生成CART樹。
決策樹的經典演算法包括ID3、C4.5、CART演算法,其應用領域及所使用的準則,如下圖所示。
決策樹

2 CART生成演算法

  1. 最小二乘迴歸樹生成演算法
    之所以稱為最小二乘迴歸樹,是因為,迴歸樹以誤差平方和為準則選擇最優二分切點,該生成演算法在訓練資料集上所在的輸入空間中,遞迴的將每個區域劃分為兩個子區域並決定每個子區域的輸出值,在這裡分為兩種情況,一是輸出值為子區域輸出值的均值該種情況下為迴歸樹,二是輸出值為子區域輸入與輸出的線性迴歸,輸出值為迴歸係數,該種情況下為模型樹。
    演算法實現步驟:
    1)選擇最優切分特徵J與切分點s,按照如下原則:
    m
    inj,s[minc1(yic1)+minc2(yic2)]

    c1,c2分別為左右子區域輸出的均值(模型樹時是輸出變數的迴歸值),可通過遍歷每個變數的每個可能取值來切分資料集找出最優切分點。
    2)用切分點劃分區域並決定相應的輸出值
    3)遞迴呼叫1)2)直到滿足停止條件
    4)利用字典,遞迴呼叫建立二叉樹,生成決策樹
  2. CART生成演算法(分類樹)
    在這裡需要提一下基尼係數:
    在分類問題中,假設有K類,樣本點屬於第k類的概率為pk,則概率分佈的基尼指數定義為:
    Gini(p)=Kk=1pk(1pk)=(p1+p2+...+pK)Kk=1p2k=1Kk=1p2k
    對於分類問題:設C
    k
    D中屬於第k類的樣本子集,則基尼指數為:
    Gini(D)=1Kk=1(|Ck||D|)2
    設條件A將樣本D切分為D1D2兩個資料子集,則在條件A下的樣本D的基尼指數為:
    Gini(D,A)=|D1|DGini(D1)+|D2|DGini(D2)
    注意:基尼指數也表示樣本的不確定性,基尼指數值越大,樣本集合的不確定性越大。
    演算法實現步驟:
    1)計算現有樣本D的基尼指數,之後利用樣本中每一個特徵A,及A的每一個可能取值a,根據A>=aA<a將樣本分為兩部分,並計算Gini(D,A)
    2)找出對應基尼指數最小Gini(D,A)的最優切分特徵及取值,並判斷是否切分停止條件,否,則輸出最優切分點
    3)遞迴呼叫1)2)
    4)生成CART決策樹
  3. 最小二乘迴歸樹的python實現流程圖
    CART迴歸樹生成流程圖
  4. 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):