1. 程式人生 > >【機器學習】決策樹(下)CART演算法分類樹、迴歸樹

【機器學習】決策樹(下)CART演算法分類樹、迴歸樹

CART同樣由特徵選擇、樹的生成、剪枝組成。既可以用於迴歸,又可以用於分類。
CART是在給定輸入隨機變數X條件下輸出隨機變數Y的條件概率分佈的學習方法。
CART假設決策樹是二叉樹,內部節點特徵的取值為“是“和“否“,左分支是取值為“是“的分支,右分支是取值為“否“的分支。這樣的決策樹等價於遞迴地二分每個特徵,將輸入空間即特徵空間劃分為有限個單元,並在這些單元上確定預測的概率分佈,也就是在輸入給定的條件下輸出的條件概率分佈。

建立分支的虛擬碼如下:


> if so return 類標籤; else
>       尋找劃分資料集的最好特徵
>       劃分資料集
> 建立分支結點 > for 每個分支結點 > 呼叫函式createBranch並增加返回結點到分支結點中//遞迴呼叫createBranch() > return 分支結點

CART演算法由以下兩步組成:

(1)決策樹生成:基於訓練資料集生成決策樹,生成的決策樹要儘量大;
(2)決策樹剪枝:用驗證資料集對已生成的樹進行剪枝並選擇最優子樹,這時用損失函式最小作為剪枝的標準。

一、分類樹
1、基尼指數:分類問題中,假設有K個類,樣本點屬於第k類的概率為 p

k ,則概率分佈的基尼指數定義為:

Gini(p)=k=1Kpk(1pk)=1k=1Kpk2
2、對於二類分類問題:若樣本點屬於第1個類的概率是p,則概率分佈的基尼指數為: Gini(p)=2p(1p)

3、對於給定的樣本集合D,其基尼指數為:

Gini(D)=1k=1K(|Ck||D|)2(1.1)

這裡, Ck 是D中屬於第 k 類的樣本子集, K 是類的個數。

計算基尼指數程式碼實現:

#一、計算基尼指數
def calcGini(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet: # 遍歷每個例項,統計標籤的頻數
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    Gini = 1.0
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries
        Gini -= prob * prob # 以2為底的對數
    return Gini

補充:

熵的程式碼實現過程一樣:
1、首先建立一個空字典,用來儲存標籤的頻次;
2、遍歷資料集得到各標籤對應的頻次,計算頻率,計算熵。

4、如果樣本集合 D 根據特徵 A 是否取某一可能值 a 被分割成 D1 D2 兩部分,即

D1={(x,y)D|A(x)=a},D2=DD1 在特徵A的條件下,集合D的基尼指數定義為:
Gini(D,A)=|D1||D|Gini(D1)+|D2||D|Gini(D2)(1.2)

在給定特徵條件下,集合的基尼指數程式碼實現:

#二、在給定特徵條件下,集合的基尼指數:
def calcGiniWithFeat(dataSet, feature, value): 
    D0 = []; D1 = []
    # 根據特徵劃分資料
    for featVec in dataSet:
        if featVec[feature] == value:
            D0.append(featVec)
        else:
            D1.append(featVec)
    Gini = len(D0) / len(dataSet) * calcGini(D0) + len(D1) / len(dataSet) * calcGini(D1)
    return Gini

補充:

1、分割資料集:給定特徵取值為value的那些資料儲存為D0,其餘部分儲存為D1;
2、利用公式(1.1)計算。

基尼指數 Gini(D) 表示集合 D 的不確定性;基尼指數 Gini(D,A) 表示經 A=a 分割後集合D的不確定性。
基尼指數越大,樣本集合的不確定性也就越大,這一點與熵相似。

演算法一、CART分類生成演算法:

輸入:訓練資料集D,停止計算的條件
**輸出:**CART決策樹
根據訓練資料集,從根結點開始,遞迴地對每個結點進行以下操作,構建二叉決策樹:
(1)設結點的訓練資料集為D,計算現有特徵對該資料集的基尼指數。此時,對每一個特徵A,對其可能取得每個值a根據樣本點對 A=a 的測試為“是“或“否“將D分割成D_1和D_2兩部分,利用式(1.1)計算 A=a 時的基尼指數。
(2)在所有可能的特徵A以及它們所有可能的切分點a中,選擇基尼指數最小的特徵以及其對應的切分點作為最優特徵與最優切分點。依最優特徵與最優切分點,從線結點生成兩個子節點,將訓練資料集依特徵分配到兩個子結點中去。
(3)對兩個子結點遞迴地呼叫(1),(2),直至滿足停止條件。
(4)生成CART決策樹。
演算法停止計算的條件是結點中的樣本個數小於預定閾值,或樣本集的基尼指數小於預定閾值(樣本基本屬於同一類),或者沒有更多特徵。

演算法二、CATRT分類決策樹生成程式碼如下:

#三、選擇最好的資料集劃分方式
def chooseBestSplit(dataSet):
    numFeatures=len(dataSet[0])-1
    bestFeat = 0
    bestGini=float("inf") #初始值設為無窮大
    newGini=0
    for i in range(numFeatures): #遍歷所有特徵,並得到對應特徵的所有不同取值
        featList=[example[i] for example in dataSet]
        uniqueVals=set(featList)
        for splitVal in uniqueVals: #遍歷要分割特徵的所有取值,得到指定特徵取值的基尼指數
            newGini=calcGiniWithFeat(dataSet, i, splitVal)
            if newGini<bestGini:
                bestFeat=i
                bestGini=newGini
    return bestFeat

#四、採用多數表決的方法決定葉結點的分類
def majorityCnt(classList):
    classCount={}
    for vote in classCount:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote] +=1
    sortedClassCount =sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #第一維度降序排列
    return sortedClassCount[0][0]


#五、建立決策樹
def createTree(dataSet, labels):
    classList=[example[-1] for example in dataSet]

    if classList.count(classList[0])==len(classList):
        return classList[0] # 第一個遞迴結束條件:所有的類標籤完全相同
    if len(dataSet[0])==1:
        return majorityCnt(classList) # 第二個遞迴結束條件:用完了所有特徵
    bestFeat=chooseBestSplit(dataSet) #劃分最優特徵
    bestFeatLabel=labels[bestFeat]

    myTree={bestFeatLabel:{}}
    del(labels[bestFeat])

    featValues=[example[bestFeat] for example in dataSet]
    uniqueVals=set(featValues) #得到最優特徵對應的不同取值
    for value in uniqueVals:
        subLabels=labels[:]  # 複製所有類標籤,保證每次遞迴呼叫時不改變原始列表的內容
        myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree

#六、使用決策樹的分類演算法
'''
引數是:決策樹、特徵標籤列表、測試向量
1、利用inputTree.keys()[0]獲取第一個判斷特徵firsrStr,將其對應的值記為第二個字典;
2、在輸入的標籤列表中利用featLabels.index(firstStr)獲取第一個判斷特徵的索引;
3、第二個字典的健實際上是第一個特徵的取值,即遍歷第一個特徵的取值,找到測試向量相應特徵的取值;
4、若測試向量相應特徵的取值(第二個字典的健)對應的值的型別是一個字典,則遞迴這個分類演算法;引數分別為:key對應的健值,特徵標籤列表,測試向量;
                                         ...對應值的型別是類標籤,則測試資料的型別就是這個類標籤。
'''
def classify(inputTree, featLabels, testVec): #輸入決策樹、特徵標籤列表、測試向量
    firstStr=inputTree.keys()[0] #獲取第一個判斷特徵
    secondDict=inputTree[firstStr]
    featIndex=featLabels.index(firstStr) #將第一個特徵標籤字串轉換為索引
    for key in secondDict.keys(): #第一個特徵對應的取值
        if testVec[featIndex]==key:
            if type(secondDict[key])._name_=='dict':
                classLabel=classify(secondDict[key], featLabels, testVec)
            else:
                classLabel=secondDict[key]
    return classLabel


#七、計算測試誤差
def calcTestErr(myTree, testData, labels):
    errorCount =0.0
    for i in range(len(testData)):
        if classify(myTree, labels, testData[i]!=testData[i][-1]):
            errorCount+=1
    return float(errorCount)

演算法三、CART剪枝演算法:

CART剪枝演算法從“完全生長“的決策樹的底端減去一些子樹,使決策樹變小(模型變簡單),從而能夠對未知資料有更準確的預測。
剪枝的過程就是一個動態規劃的過程:從葉結點開始,自底向上地對內部結點計算預測誤差以及剪枝後的預測誤差,如果兩者的預測誤差是相等或者剪枝後預測誤差更小,當然是剪掉的好。但是如果剪枝後的預測誤差更大,那就不要剪了。剪枝後,原內部結點會變成新的葉結點,其決策類別由多數表決法決定。不斷重複這個過程往上剪枝,直到預測誤差最小為止。

CART剪枝演算法由兩步組成:

第一步、 首先從生成演算法產生的決策樹 T0 底端開始不斷剪枝,直到 T0 的根結點,形成一個子樹序列 {T0,T1,...,Tn}
第二步、然後通過交叉驗證法在獨立的驗證資料集上對子序列進行測試,從中選擇最優子樹。

第一步:剪枝,形成一個子樹序列
補充:

import copy
a = [1, 2, 3, 4, ['a', 'b']]  #原始物件

b = a  #賦值,傳物件的引用
c = copy.copy(a)  #物件拷貝,淺拷貝
d = copy.deepcopy(a)  #物件拷貝,深拷貝

a.append(5)  #修改物件a
a[4].append('c')  #修改物件a中的['a', 'b']陣列物件

print 'a = ', a
print 'b = ', b
print 'c = ', c
print 'd = ', d

輸出結果:
a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
c =  [1, 2, 3, 4, ['a', 'b', 'c']]
d =  [1, 2, 3, 4, ['a', 'b']]

1、copy.copy 淺拷貝: 只拷貝父物件,不會拷貝物件的內部的子物件,故內部的子物件會隨著原來的改變。
2、copy.deepcopy深拷貝: 複製了物件所有的值。所以不論原物件怎麼變化,deepcopy的結果都沒有改變。
如果複製物件沒有子物件,複製操作傳遞的是值,copy和deepcopy沒啥區別。
如果複製物件中有子物件,為了達到複製數值目的,還是使用deepcopy比較好。

CART迴歸演算法:
這裡寫圖片描述


參照博文