決策樹原理及Python代碼實現

2020.4.5添加:

哈哈,遲來的源碼,我把它放到GitHub上了:包含詳細註釋的樹模型源碼;包括決策樹和隨機森林,歡迎取用,歡迎討論,歡迎star;

我才發現CSDN的資源下載自動要求這麼多積分,我之前上傳的時候是限定0積分的。。

###################正文分割線######################

決策樹其實就是按節點分類數據集的一種方法。在本文中,我將討論數學上如何使用信息論劃分數據集,並編寫代碼構建決策樹(本文使用ID3算法構建決策樹,ID3算法可以用來劃分標稱型數據集)。

創建決策樹進行分類的流程如下:

(1)    創建數據集

(2)    計算數據集的信息熵

(3)    遍歷所有特徵,選擇信息熵最小的特徵,即爲最好的分類特徵

(4)    根據上一步得到的分類特徵分割數據集,並將該特徵從列表中移除

(5)    執行遞歸函數,返回第三步,不斷分割數據集,直到分類結束

(6)    使用決策樹執行分類,返回分類結果

 

首先,給出一個簡單數據集:

 

 數據解讀:

在該數據集中包含五個海洋動物,有兩個特徵:(1)不浮出水面是否可以生存;(2)是否有腳蹼;這些動物被分成兩類:魚類和非魚類。在我們構建決策樹的過程中,對某個動物,只有兩個特徵都爲“是”時,纔將其判定爲魚類。

   在構建決策樹時,我們需要解決的第一個問題是:當前數據集哪個特徵在劃分數據分類時起決定性作用,即我們要如何找出最優的分類特徵。爲了找到決定性的特徵,劃分出最好的結果,我們必須評估每個特徵。完成數據劃分後,原始數據集就被劃分爲幾個數據子集,這些數據子集會分佈在第一個決策點的所有分支上。如果某個分支下的數據屬於同一類型,即數據已正確分類,無需進一步分割。如果數據子集內的數據不屬於同個類型,則需要重複劃分數據子集的過程。劃分數據子集的算法和劃分原始數據集的方法相同(因此可用遞歸函數繼續劃分子集),直到所有具有相同類型的數據都在一個數據子集內。

 

  構建決策樹的僞代碼函數createTree()如下所示:

檢測數據集中的每個子集是否屬於同一分類:

    If so return 類標籤

    Else:

        尋找劃分數據集的最好特徵

        劃分數據集

        創建分支節點

            For 每個劃分的子集:

                調用函數createTree()並增加返回結果到分支節點中

        Return 分支節點

 

 

1)劃分數據集的大原則是:將無序的數據變得更加有序。這裏引入信息熵的概念。如果待分類的事務可能劃分在多個分類之中,則符號xi的信息定義爲:

 

其中p(xi)是選擇該分類的概率。

爲了計算熵,我們需要計算所有類別所有可能值包含的信息期望值,通過下面的公式得到:

 

直觀的理解:如果x屬於某個分類的值越大(即數據越有序),H的值越小;極端情況下,p(xi)=1時,H=0,此時分類最準確。所以我們要使H的值儘可能小。

計算給定數據集的信息熵的代碼如下:

'''計算數據集的信息熵 (信息熵即指類別標籤的混亂程度,值越小越好)'''
def calcshan(dataSet):   
    lenDataSet=len(dataSet)
    p={}
    H=0.0
    for data in dataSet:
        currentLabel=data[-1]  #獲取類別標籤
        if currentLabel not in p.keys():  #若字典中不存在該類別標籤,即創建
            p[currentLabel]=0
        p[currentLabel]+=1    #遞增類別標籤的值
    for key in p:
        px=float(p[key])/float(lenDataSet)  #計算某個標籤的概率
        H-=px*log(px,2)  #計算信息熵
    return H

結果如下:

 

(2)計算完信息熵後,我們便可以得到數據集的無序程度。我們將對每個特徵劃分數據集的結果計算一次信息熵,然後判斷哪個特徵劃分數據集是最好的劃分方式(根據信息熵判斷,信息熵越小,說明劃分效果越好)。

按照給定特徵劃分數據集的代碼如下:

 

'''根據某一特徵分類數據集'''
def spiltData(dataSet,axis,value):    #dataSet爲要劃分的數據集,axis爲給定的特徵,value爲給定特徵的具體值
    subDataSet=[]
    for data in dataSet:
        subData=[]
        if data[axis]==value:
            subData=data[:axis]  #取出data中第0到axis-1個數進subData;
            subData.extend(data[axis+1:])  #取出data中第axis+1到最後一個數進subData;這兩行代碼相當於把第axis個數從數據集中剔除掉
            subDataSet.append(subData) #此處要注意expend和append的區別
    return subDataSet

 

結果如下:

 

(3)選擇最好的數據集劃分方式,代碼如下:

 

'''遍歷所有特徵,選擇信息熵最小的特徵,即爲最好的分類特徵'''      
def chooseBestFeature(dataSet):  
    lenFeature=len(dataSet[0])-1    #計算特徵維度時要把類別標籤那一列去掉
    shanInit=calcshan(dataSet)      #計算原始數據集的信息熵
    feature=[]
    inValue=0.0
    bestFeature=0
    for i in range(lenFeature):
        shanCarry=0.0
        feature=[example[i] for example in dataSet]  #提取第i個特徵的所有數據
        feature=set(feature)  #得到第i個特徵所有的分類值,如'0'和'1'
        for feat in feature:  
            subData=spiltData(dataSet,i,feat)  #先對數據集按照分類值分類
            prob=float(len(subData))/float(len(dataSet))
            shanCarry+=prob*calcshan(subData)  #計算第i個特徵的信息熵
        outValue=shanInit-shanCarry  #原始數據信息熵與循環中的信息熵的差
        if (outValue>inValue):
            inValue=outValue  #將信息熵與原始熵相減後的值賦給inValue,方便下一個循環的信息熵差值與其比較
            bestFeature=i
    return bestFeature

 

結果如下:

 

(4)選擇好最好的劃分特徵後,接下來,可以開始創建決策樹了。其工作原理如下:得到原始數據集,然後基於最好的屬性值劃分數據集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的數據集劃分。第一次劃分後,數據將被向下傳遞到樹分支的下一個節點,在這個節點上,我們可以再次劃分數據。因此我們可以使用遞歸的原則處理數據集。遞歸結束的條件是:程序遍歷完所有劃分數據集的屬性,或者每個分支下的所有實例都具有相同的分類。

具體實現代碼如下:

 

'''創建我們所要分類的決策樹'''
def createTree(dataSet,label):    
    classList=[example[-1] for example in dataSet]   #classList是指當前數據集的類別標籤
    if classList.count(classList[0])==len(classList): #計算classList中某個類別標籤的數量,若只有一類,則數量與它的數據長度相等
        return classList[0]
    if len(dataSet[0])==1:   #當處理完所有特徵而類別標籤還不唯一時起作用
        return majorityCnt(classList)
    featBest=chooseBestFeature(dataSet)  #選擇最好的分類特徵
    feature=[example[featBest] for example in dataSet]  #接下來使用該分類特徵進行分類
    featValue=set(feature)  #得到該特徵所有的分類值,如'0'和'1'
    newLabel=label[featBest]
    del(label[featBest])
    Tree={newLabel:{}}  #創建一個多重字典,存儲決策樹分類結果
    for value in featValue:
        subLabel=label[:]
        Tree[newLabel][value]=createTree(spiltData(dataSet,featBest,value),subLabel) #遞歸函數使得Tree不斷創建分支,直到分類結束
    return Tree

 

結果如下:

 

 

(5)依靠訓練數據構造了決策樹之後,我們可以將它用於實際數據的分類。在執行數據分類時,需要使用決策樹以及用於構造樹的標籤向量。然後,程序比較測試數據與決策樹上的數值,遞歸執行該過程直到進入葉子節點;最後將測試數據定義爲葉子節點所屬的類型。

具體實現代碼如下:

 

'''使用決策樹執行分類,返回分類結果'''
def classify(tree,label,testVec):       #tree爲createTree()函數返回的決策樹;label爲特徵的標籤值;testVec爲測試數據,即所有特徵的具體值構成的向量
    firstFeat=tree.keys()[0]            #取出tree的第一個鍵
    secondDict=tree[firstFeat]          #取出tree第一個鍵的值,即tree的第二個字典(包含關係)
    labelIndex=label.index(firstFeat)   #得到第一個特徵firstFeat在標籤label中的索引
    for key in secondDict.keys():       #遍歷第二個字典的鍵
        if testVec[labelIndex]==key:    #如果第一個特徵的測試值與第二個字典的鍵相等時
            if type(secondDict[key]).__name__=='dict':  #如果第二個字典的值還是一個字典,說明分類還沒結束,遞歸執行classify函數
                classLabel=classify(secondDict[key],label,testVec)  #遞歸函數中只有輸入的第一個參數不同,不斷向字典內層滲入
            else:
                classLabel=secondDict[key]  #最後將得到的分類值賦給classLabel輸出
    return classLabel

 

結果如下:

 

我們可以看到,只有測試數據的兩個特徵都爲1時,纔會輸出‘yes’,判定爲魚類,結果符合我們的實際要求。

 

現在我們已經創建了使用決策樹的分類器,但是每次使用分類器時,必須重新構造決策樹,而且構造決策樹是很耗時的任務。因此,爲了節省計算時間,最好能夠在每次執行分類時調用已經構造好的決策樹。這裏我們使用Python的pickle模塊序列化對象。序列化對象可以在磁盤上保存對象,並在需要的時候讀取出來。

使用pickle模塊存儲決策樹代碼如下:

 

'''使用pickle模塊存儲決策樹'''
def storeTree(tree,filename):  
    import pickle
    fw=open(filename,'w')
    pickle.dump(tree,fw)
    fw.close()

'''打開文件取出決策樹'''
def loadTree(filename):         
    import pickle
    fr=open(filename,'r')
    return pickle.load(fr)

 

結果如下:

 

執行完storeTree()函數,我們的代碼路徑裏就會多出一個dataTree.txt的文件,保存決策樹內容,以後要使用決策樹進行分類時,使用loadTree()函數直接調用即可。

參考書籍:

《機器學習實戰》 Peter Harrington著  李銳,李鵬,曲亞東,王斌譯

 

 

 

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