《機器學習實戰》決策樹(ID3演算法)的分析與實現

Thinkgamer_gyt發表於2015-08-15
============================================================================================
《機器學習實戰》系列部落格是博主閱讀《機器學習實戰》這本書的筆記,包含對其中演算法的理解和演算法的Python程式碼實現

另外博主這裡有機器學習實戰這本書的所有演算法原始碼和演算法所用到的原始檔,有需要的留言
============================================================================================



KNN演算法請參考:http://blog.csdn.net/gamer_gyt/article/details/47418223


一、簡介

        決策樹是一個預測模型;他代表的是物件屬性與物件值之間的一種對映關係。樹中每個節點表示某個物件,而每個分叉路徑則代表的某個可能的屬性值,而每個葉結點則對應從根節點到該葉節點所經歷的路徑所表示的物件的值。決策樹僅有單一輸出,若欲有複數輸出,可以建立獨立的決策樹以處理不同輸出。 資料探勘中決策樹是一種經常要用到的技術,可以用於分析資料,同樣也可以用來作預測
二、基本思想
       1)樹以代表訓練樣本的單個結點開始。
       2)如果樣本都在同一個類.則該結點成為樹葉,並用該類標記。
       3)否則,演算法選擇最有分類能力的屬性作為決策樹的當前結點.
       4)根據當前決策結點屬性取值的不同,將訓練樣本資料集tlI分為若干子集,每個取值形成一個分枝,有幾個取值形成幾個分枝。勻針對上一步得到的一個子集,重複進行先前  步驟,遞4'I形成每個劃分樣本上的決策樹。一旦一個屬性出現在一個結點上,就不必在該結點的任何後代考慮它。
       5)遞迴劃分步驟僅當下列條件之一成立時停止:
       ①給定結點的所有樣本屬於同一類。
       ②沒有剩餘屬性可以用來進一步劃分樣本.在這種情況下.使用多數表決,將給定的結點轉換成樹葉,並以樣本中元組個數最多的類別作為類別標記,同時也可以存放該結點樣本的類別分佈,
       ③如果某一分枝tc,沒有滿足該分支中已有分類的樣本,則以樣本的多數類建立一個樹葉。
三、構造方法
       決策樹構造的輸入是一組帶有類別標記的例子,構造的結果是一棵二叉樹或多叉樹。二叉樹的內部節點(非葉子節點)一般表示為一個邏輯判斷,如形式為a=aj的邏輯判斷,其中a是屬性,aj是該屬性的所有取值:樹的邊是邏輯判斷的分支結果。多叉樹(ID3)的內部結點是屬性,邊是該屬性的所有取值,有幾個屬性值就有幾條邊。樹的葉子節點都是類別標記。
由於資料表示不當、有噪聲或者由於決策樹生成時產生重複的子樹等原因,都會造成產生的決策樹過大。因此,簡化決策樹是一個不可缺少的環節。尋找一棵最優決策樹,主要應解決以下3個最優化問題:①生成最少數目的葉子節點;②生成的每個葉子節點的深度最小;③生成的決策樹葉子節點最少且每個葉子節點的深度最小。
四、Python程式碼實現
呼叫:命令列進入該程式碼所在目錄執行:import trees       dataSet,labels = trees.createDataSet()    trees.myTree()
#coding=utf-8
from math import log
import operator

def createDataSet():
    dataSet =[[1,1,'yes'],
              [1,1,'yes'],
              [1,0,'no'],
              [0,1,'no'],
              [0,1,'no']]  #建立資料集
    labels = ['no surfacing','flippers'] #分類屬性
    return dataSet,labels
'''
測試結果
[[1, 'no'], [1, 'no']]
>>> dataSet
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> labels
['no surfacing', 'flippers']
'''
def calcShannonEnt(dataSet):   #計算給定資料集的夏農熵
    numEntries = len(dataSet)  #求長度
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1] #獲得標籤
        if currentLabel not in labelCounts.keys():  #如果標籤不在新定義的字典裡建立該標籤值
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1 #該類標籤下含有資料的個數
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries  #同類標籤出現的概率
        shannonEnt -=prob*log(prob,2)   #以2為底求對數
    return shannonEnt
'''
測試結果
>>> trees.calcShannonEnt(dataSet)
0.9709505944546686
'''
def splitDataSet(dataSet,axis,value):  #劃分資料集,三個引數為帶劃分的資料集,劃分資料集的特徵,特徵的返回值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] ==value:
            #將相同資料集特徵的抽取出來
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet #返回一個列表
'''
測試結果
>>> trees.splitDataSet(dataSet,0,1)
[[1, 'yes'], [1, 'yes'], [0, 'no']] 
>>> trees.splitDataSet(dataSet,0,0)
[[1, 'no'], [1, 'no']]
'''          
def chooseBestFeatureToSplit(dataSet):  #選擇最好的資料集劃分方式
    numFeature = len(dataSet[0])-1
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    beatFeature = -1
    for i in range(numFeature):
        featureList = [example[i] for example in dataSet] #獲取第i個特徵所有的可能取值
        uniqueVals = set(featureList)  #從列表中建立集合,得到不重複的所有可能取值
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet,i,value)  #以i為資料集特徵,value為返回值,劃分資料集
            prob = len(subDataSet)/float(len(dataSet))   #資料集特徵為i的所佔的比例
            newEntropy +=prob * calcShannonEnt(subDataSet)   #計算每種資料集的資訊熵
        infoGain = baseEntropy- newEntropy
        #計算最好的資訊增益,增益越大說明所佔決策權越大
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature
'''
測試結果
>>> trees.chooseBestFeatureToSplit(dataSet)
0
說明:第0個特徵是最好的用於劃分資料集的特徵
'''
def majorityCnt(classList):      #遞迴構建決策樹
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote]+=1
    sortedClassCount = sorted(classCount.iteritems(),key =operator.itemgetter(1),reverse=True)#排序,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 = chooseBestFeatureToSplit(dataSet)   #選擇最好的資料集劃分方式
    bestFeatLabel = labels[bestFeat]   #得到對應的標籤值
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])      #清空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  
'''
測試結果
>>> reload(trees)

>>> dataSet,labels = trees.createDataSet()
>>> myTree = trees.createTree(dataSet,labels)
>>> myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
'''


相關文章