【ML_Algorithm 5 】決策樹(Decision Tree)——算法實際應用

本文對決策樹算法進行簡單的總結和梳理,並對著名的決策樹算法ID3(Iterative Dichotomiser 迭代二分器)進行實現,實現採用Python語言,一句老梗,“人生苦短,我用Python”,Python確實能夠省很多語言方面的事,從而可以讓我們專注於問題和解決問題的邏輯。

根據不同的數據,我實現了三個版本的ID3算法,複雜度逐步提升:

1.純標稱值無缺失數據集

2.連續值和標稱值混合且無缺失數據集

3.連續值和標稱值混合,有缺失數據集

第一個算法參考了《機器學習實戰》的大部分代碼,第二、三個算法基於前面的實現進行模塊的增加。

1. 引語:決策樹簡介

決策樹算法不用說大家應該都知道,是機器學習的一個著名算法,由澳大利亞著名計算機科學家Rose Quinlan發表。

決策樹是一種監督學習的分類算法,目的是學習出一顆決策樹,該樹中間節點是數據特徵,葉子節點是類別,實際分類時根據樹的結構,一步一步根據當前數據特徵取值選擇進入哪一顆子樹,直到走到葉子節點,葉子節點的類別就是此決策樹對此數據的學習結果。下圖就是一顆簡單的決策樹:

                                       

此決策樹用來判斷一個具有紋理,觸感,密度的西瓜是否是“好瓜”。

當有這樣一個西瓜,紋理清晰,密度爲0.333,觸感硬滑,那麼要你判斷是否是一個“好瓜”,這時如果通過決策樹來判斷,顯然可以一直順着紋理->清晰->密度<=0.382->否,即此瓜不是“好瓜”,一次決策就這樣完成了。正因爲決策樹決策很方便,並且準確率也較高,所以常常被用來做分類器,也是“機器學習十大算法”之一C4.5的基本思想。

學習出一顆決策樹首要考慮一個問題,即 根據數據集構建當前樹應該選擇哪種屬性作爲樹根,即劃分標準? 

考慮最好的情況,一開始選擇某個特徵,就把數據集劃分成功,即在該特徵上取某個值的全是一類。

考慮最壞的情況,不斷選擇特徵,劃分後的數據集總是雜亂無章,就二分類任務來說,總是有正類有負類,一直到特徵全部用完了,劃分的數據集合還是有正有負,這時只能用投票法,正類多就選正類作爲葉子,否則選負類。

所以得出了一般結論: 隨着劃分的進行,我們希望選擇一個特徵,使得子節點包含的樣本儘可能屬於同一類別,即“純度”越高越好。基於“純度”的標準不同,有三種算法:

1.ID3算法(Iterative Dichotomiser 迭代二分器),也是本文要實現的算法,基於信息增益即信息熵來度量純度

2.C4.5算法(Classifier 4.5),ID3 的後繼算法,也是昆蘭提出

3.CART算法(Classification And Regression Tree),基於基尼指數度量純度。

2. ID3算法簡介

信息熵是信息論中的一個重要概念,也叫“香農熵”,香農先生的事蹟相比很多人都聽過,一個人開創了一門理論,牛的不行。香農理論中一個很重要的特徵就是”熵“,即”信息內容的不確定性“,香農在進行信息的定量計算的時候,明確地把信息量定義爲隨機不定性程度的減少。這就表明了他對信息的理解:信息是用來減少隨機不定性的東西。或者表達爲香農逆定義:信息是確定性的增加。這也印證了決策樹以熵作爲劃分選擇的度量標準的正確性,即我們想更快速地從數據中獲得更多信息,我們就應該快速降低不確定性,即減少”熵“。

信息熵定義爲:

D表示數據集,類別總數爲|y|,Pk表示D中第k類樣本所佔的比例。根據其定義,Ent的值越小,信息純度越高。Ent的範圍是[0,log|y|]

下面要選擇某個屬性進行劃分,要依次考慮每個屬性,假設當前考慮屬性a,a的取值有|V|種,那麼我們希望取a作爲劃分屬性,劃分到|V|個子節點後,所有子節點的信息熵之和即劃分後的信息熵能夠有很大的減小,減小的最多的那個屬性就是我們選擇的屬性。

劃分後的信息熵定義爲:

所以用屬性a對樣本集D進行劃分的信息增益就是原來的信息熵減去劃分後的信息熵:

ID3算法就是這樣每次選擇一個屬性對樣本集進行劃分,直到兩種情況使這個過程停止:

(1)某個子節點樣本全部屬於一類   (2)屬性都用完了,這時候如果子節點樣本還是不一致,那麼只好少數服從多數了

3. 算法實現

3.1 ID3算法實現(純標稱值)

如果樣本全部是標稱值即離散值的話,會比較簡單。

from math import log
from operator import itemgetter
def createDataSet():            #創建數據集
    dataSet = [[1,1,'yes'],
               [1,1,'yes'],
               [1,0,'no'],
               [0,1,'no'],
               [0,1,'no']]
    featname = ['no surfacing', 'flippers']
    return dataSet,featname
def filetoDataSet(filename):
    fr = open(filename,'r')
    all_lines = fr.readlines()
    featname = all_lines[0].strip().split(',')[1:-1]
    print(featname)
    dataSet = []
    for line in all_lines[1:]:
        line = line.strip()
        lis = line.split(',')[1:]
        dataSet.append(lis)
    fr.close()
    return dataSet,featname
def calcEnt(dataSet):           #計算香農熵
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        label = featVec[-1]
        if label not in labelCounts.keys():
            labelCounts[label] = 0
        labelCounts[label] += 1
    Ent = 0.0
    for key in labelCounts.keys():
        p_i = float(labelCounts[key]/numEntries)
        Ent -= p_i * log(p_i,2)
    return Ent
def splitDataSet(dataSet, axis, value):   #劃分數據集,找出第axis個屬性爲value的數據
    returnSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet
def chooseBestFeat(dataSet):
    numFeat = len(dataSet[0])-1
    Entropy = calcEnt(dataSet)
    DataSetlen = float(len(dataSet))
    bestGain = 0.0
    bestFeat = -1
    for i in range(numFeat):
        allvalue = [featVec[i] for featVec in dataSet]
        specvalue = set(allvalue)
        nowEntropy = 0.0
        for v in specvalue:
            Dv = splitDataSet(dataSet,i,v)
            p = len(Dv)/DataSetlen
            nowEntropy += p * calcEnt(Dv)
        if Entropy - nowEntropy > bestGain:
            bestGain = Entropy - nowEntropy
            bestFeat = i
    return bestFeat
def Vote(classList):
    classdic = {}
    for vote in classList:
        if vote not in classdic.keys():
            classdic[vote] = 0
        classdic[vote] += 1
    sortedclassDic = sorted(classdic.items(),key=itemgetter(1),reverse=True)
    return sortedclassDic[0][0]
def createDecisionTree(dataSet,featnames):
    featname = featnames[:]              ################
    classlist = [featvec[-1] for featvec in dataSet]  #此節點的分類情況
    if classlist.count(classlist[0]) == len(classlist):  #全部屬於一類
        return classlist[0]
    if len(dataSet[0]) == 1:         #分完了,沒有屬性了
        return Vote(classlist)       #少數服從多數
    # 選擇一個最優特徵進行劃分
    bestFeat = chooseBestFeat(dataSet)
    bestFeatname = featname[bestFeat]
    del(featname[bestFeat])     #防止下標不準
    DecisionTree = {bestFeatname:{}}
    # 創建分支,先找出所有屬性值,即分支數
    allvalue = [vec[bestFeat] for vec in dataSet]
    specvalue = sorted(list(set(allvalue)))  #使有一定順序
    for v in specvalue:
        copyfeatname = featname[:]
        DecisionTree[bestFeatname][v] = createDecisionTree(splitDataSet(dataSet,bestFeat,v),copyfeatname)
    return DecisionTree
if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\西瓜2.0.txt"
    DataSet,featname = filetoDataSet(filename)
    #print(DataSet)
    #print(featname)
    Tree = createDecisionTree(DataSet,featname)
    print(Tree)

解釋一下幾個函數:

filetoDataSet(filename)  將文件中的數據整理成數據集

calcEnt(dataSet)     計算香農熵

splitDataSet(dataSet, axis, value)     劃分數據集,選擇出第axis個屬性的取值爲value的所有數據集,即D^v,並去掉第axis個屬性,因爲不需要了

chooseBestFeat(dataSet)      根據信息增益,選擇一個最好的屬性

Vote(classList)        如果屬性用完,類別仍不一致,投票決定

createDecisionTree(dataSet,featnames)     遞歸創建決策樹


用西瓜數據集2.0對算法進行測試,西瓜數據集見 西瓜數據集2.0,輸出如下:

['色澤''根蒂''敲聲''紋理''臍部''觸感']

{'紋理': {'清晰': {'根蒂': {'蜷縮''是''硬挺''否''稍蜷': {'色澤': {'青綠''是''烏黑': {'觸感': {'硬滑''是''軟粘''否'}}}}}}, '稍糊': {'觸感': {'硬滑''否''軟粘''是'}}, '模糊''否'}}

爲了能夠體現決策樹的優越性即決策方便,這裏基於matplotlib模塊編寫可視化函數treePlot,對生成的決策樹進行可視化,可視化結果如下:

由於數據太少,沒有設置測試數據以驗證其準確度,但是我後面會根據乳腺癌的例子進行準確度的測試的,下面進入下一部分:

3.2 有連續值的情況

有連續值的情況如 西瓜數據集3.0 

一個屬性有很多種取值,我們肯定不能每個取值都做一個分支,這時候需要對連續屬性進行離散化,有幾種方法供選擇,其中兩種是:

1.對每一類別的數據集的連續值取平均值,再取各類的平均值的平均值作爲劃分點,將連續屬性化爲兩類變成離散屬性

2.C4.5採用的二分法,排序離散屬性,取每兩個的中點作爲劃分點的候選點,計算以每個劃分點劃分數據集的信息增益,取最大的那個劃分點將連續屬性化爲兩類變成離散屬性,用該屬性進行劃分的信息增益就是剛剛計算的最大信息增益。公式如下:

這裏採用第二種,並在學習前對連續屬性進行離散化。增加處理的代碼如下:

def splitDataSet_for_dec(dataSet, axis, value, small):
    returnSet = []
    for featVec in dataSet:
        if (small and featVec[axis] <= value) or ((not small) and featVec[axis] > value):
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet
def DataSetPredo(filename,decreteindex):
    dataSet,featname = filetoDataSet(filename)
    Entropy = calcEnt(dataSet)
    DataSetlen = len(dataSet)
    for index in decreteindex:     #對每一個是連續值的屬性下標
        for i in range(DataSetlen):
            dataSet[i][index] = float(dataSet[i][index])
        allvalue = [vec[index] for vec in dataSet]
        sortedallvalue = sorted(allvalue)
        T = []
        for i in range(len(allvalue)-1):        #劃分點集合
            T.append(float(sortedallvalue[i]+sortedallvalue[i+1])/2.0)
        bestGain = 0.0
        bestpt = -1.0
        for pt in T:          #對每個劃分點
            nowent = 0.0
            for small in range(2):   #化爲正類負類
                Dt = splitDataSet_for_dec(dataSet, index, pt, small)
                p = len(Dt) / float(DataSetlen)
                nowent += p * calcEnt(Dt)
            if Entropy - nowent > bestGain:
                bestGain = Entropy-nowent
                bestpt = pt
        featname[index] = str(featname[index]+"<="+"%.3f"%bestpt)
        for i in range(DataSetlen):
            dataSet[i][index] = "是" if dataSet[i][index] <= bestpt else "否"
    return dataSet,featname

主要是預處理函數DataSetPredo,對數據集提前離散化,然後再進行學習,學習代碼類似。輸出的決策樹如下:

3.3 有缺失值的情況

數據有缺失值是常見的情況,我們不好直接拋棄這些數據,因爲這樣會損失大量數據,不划算,但是缺失值我們也無法判斷它的取值。怎麼辦呢,辦法還是有的。

考慮兩個問題: 

1.有缺失值時如何進行劃分選擇

2.已選擇劃分屬性,有缺失值的樣本劃不劃分,如何劃分?

問題1:有缺失值時如何進行劃分選擇

基本思想是進行最優屬性選擇時,先只考慮無缺失值樣本,然後再乘以相應比例,得到在整個樣本集上的大致情況。連帶考慮到第二個問題的話,考慮給每一個樣本一個權重,此時每個樣本不再總是被看成一個獨立樣本,這樣有利於第二個問題的解決:即若樣本在屬性a上的值缺失,那麼將其看成是所有值都取,只不過取每個值的權重不一樣,每個值的權重參考該值在無缺失值樣本中的比例,簡單地說,比如在無缺失值樣本集中,屬性a取去兩個值1和2,並且取1的權重和佔整個權重和1/3,而取2的權重和佔2/3,那麼依據該屬性對樣本集進行劃分時,遇到該屬性上有缺失值的樣本,那麼我們認爲該樣本取值2的可能性更大,於是將該樣本的權重乘以2/3歸到取值爲2的樣本集中繼續進行劃分構造決策樹,而乘1/3劃到取值爲1的樣本集中繼續構造。不知道我說清楚沒有。

公式如下:

其中,D~表示數據集D在屬性a上無缺失值的樣本,根據它來判斷a屬性的優劣,rho(即‘lou')表示屬性a的無缺失值樣本佔所有樣本的比例,p~_k表示無缺失值樣本中第k類所佔的比例,r~_v表示無缺失值樣本在屬性a上取值爲v的樣本所佔的比例。

在劃分樣本時,如果有缺失值,則將樣本劃分到所有子節點,在屬性a取值v的子節點上的權重爲r~_v * 原來的權重。

更詳細的解讀參考《機器學習》P86-87。

根據權重法修改後的ID3算法實現如下:

from math import log
from operator import itemgetter

def filetoDataSet(filename):
    fr = open(filename,'r')
    all_lines = fr.readlines()
    featname = all_lines[0].strip().split(',')[1:-1]
    dataSet = []
    for line in all_lines[1:]:
        line = line.strip()
        lis = line.split(',')[1:]
        if lis[-1] == '2':
            lis[-1] = '良'
        else:
            lis[-1] = '惡'
        dataSet.append(lis)
    fr.close()
    return dataSet,featname

def calcEnt(dataSet, weight):           #計算權重香農熵
    labelCounts = {}
    i = 0
    for featVec in dataSet:
        label = featVec[-1]
        if label not in labelCounts.keys():
            labelCounts[label] = 0
        labelCounts[label] += weight[i]
        i += 1
    Ent = 0.0
    for key in labelCounts.keys():
        p_i = float(labelCounts[key]/sum(weight))
        Ent -= p_i * log(p_i,2)
    return Ent

def splitDataSet(dataSet, weight, axis, value, countmissvalue):   #劃分數據集,找出第axis個屬性爲value的數據
    returnSet = []
    returnweight = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?' and (not countmissvalue):
            continue
        if countmissvalue and featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        if featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i])
        i += 1
    return returnSet,returnweight

def splitDataSet_for_dec(dataSet, axis, value, small, countmissvalue):
    returnSet = []
    for featVec in dataSet:
        if featVec[axis] == '?' and (not countmissvalue):
            continue
        if countmissvalue and featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        if (small and featVec[axis] <= value) or ((not small) and featVec[axis] > value):
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
    return returnSet
            
def DataSetPredo(filename,decreteindex):     #首先運行,權重不變爲1
    dataSet,featname = filetoDataSet(filename)
    DataSetlen = len(dataSet)
    Entropy = calcEnt(dataSet,[1 for i in range(DataSetlen)])
    for index in decreteindex:     #對每一個是連續值的屬性下標
        UnmissDatalen = 0
        for i in range(DataSetlen):      #字符串轉浮點數
            if dataSet[i][index] != '?':
                UnmissDatalen += 1
                dataSet[i][index] = int(dataSet[i][index])
        allvalue = [vec[index] for vec in dataSet if vec[index] != '?']
        sortedallvalue = sorted(allvalue)
        T = []
        for i in range(len(allvalue)-1):        #劃分點集合
            T.append(int(sortedallvalue[i]+sortedallvalue[i+1])/2.0)
        bestGain = 0.0
        bestpt = -1.0
        for pt in T:          #對每個劃分點
            nowent = 0.0
            for small in range(2):   #化爲正類(1)負類(0)
                Dt = splitDataSet_for_dec(dataSet, index, pt, small, False)
                p = len(Dt) / float(UnmissDatalen)
                nowent += p * calcEnt(Dt,[1.0 for i in range(len(Dt))])
            if Entropy - nowent > bestGain:
                bestGain = Entropy-nowent
                bestpt = pt
        featname[index] = str(featname[index]+"<="+"%d"%bestpt)
        for i in range(DataSetlen):
            if dataSet[i][index] != '?':
                dataSet[i][index] = "是" if dataSet[i][index] <= bestpt else "否"
    return dataSet,featname

def getUnmissDataSet(dataSet, weight, axis):
    returnSet = []
    returnweight = []
    tag = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?':
            tag.append(i)
        else:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
        i += 1
    for i in range(len(weight)):
        if i not in tag:
            returnweight.append(weight[i])
    return returnSet,returnweight

def printlis(lis):
    for li in lis:
        print(li)
        
def chooseBestFeat(dataSet,weight,featname):
    numFeat = len(dataSet[0])-1
    DataSetWeight = sum(weight)
    bestGain = 0.0
    bestFeat = -1
    for i in range(numFeat):
        UnmissDataSet,Unmissweight = getUnmissDataSet(dataSet, weight, i)   #無缺失值數據集及其權重
        Entropy = calcEnt(UnmissDataSet,Unmissweight)      #Ent(D~)
        allvalue = [featVec[i] for featVec in dataSet if featVec[i] != '?']
        UnmissSumWeight = sum(Unmissweight)
        lou = UnmissSumWeight / DataSetWeight        #lou
        specvalue = set(allvalue)
        nowEntropy = 0.0
        for v in specvalue:      #該屬性的幾種取值
            Dv,weightVec_v = splitDataSet(dataSet,Unmissweight,i,v,False)   #返回 此屬性爲v的所有樣本 以及 每個樣本的權重
            p = sum(weightVec_v) / UnmissSumWeight          #r~_v = D~_v / D~
            nowEntropy += p * calcEnt(Dv,weightVec_v)
        if lou*(Entropy - nowEntropy) > bestGain:
            bestGain = Entropy - nowEntropy
            bestFeat = i
    return bestFeat

def Vote(classList,weight):
    classdic = {}
    i = 0
    for vote in classList:
        if vote not in classdic.keys():
            classdic[vote] = 0
        classdic[vote] += weight[i]
        i += 1
    sortedclassDic = sorted(classdic.items(),key=itemgetter(1),reverse=True)
    return sortedclassDic[0][0]

def splitDataSet_adjustWeight(dataSet,weight,axis,value,r_v):
    returnSet = []
    returnweight = []
    i = 0
    for featVec in dataSet:
        if featVec[axis] == '?':
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i] * r_v)
        elif featVec[axis] == value:
            retVec = featVec[:axis]
            retVec.extend(featVec[axis+1:])
            returnSet.append(retVec)
            returnweight.append(weight[i])
        i += 1
    return returnSet,returnweight
    
def createDecisionTree(dataSet,weight,featnames):
    featname = featnames[:]              ################
    classlist = [featvec[-1] for featvec in dataSet]  #此節點的分類情況
    if classlist.count(classlist[0]) == len(classlist):  #全部屬於一類
        return classlist[0]
    if len(dataSet[0]) == 1:         #分完了,沒有屬性了
        return Vote(classlist,weight)       #少數服從多數
    # 選擇一個最優特徵進行劃分
    bestFeat = chooseBestFeat(dataSet,weight,featname)
    bestFeatname = featname[bestFeat]
    del(featname[bestFeat])     #防止下標不準
    DecisionTree = {bestFeatname:{}}
    # 創建分支,先找出所有屬性值,即分支數
    allvalue = [vec[bestFeat] for vec in dataSet if vec[bestFeat] != '?']
    specvalue = sorted(list(set(allvalue)))  #使有一定順序
    UnmissDataSet,Unmissweight = getUnmissDataSet(dataSet, weight, bestFeat)   #無缺失值數據集及其權重
    UnmissSumWeight = sum(Unmissweight)      # D~
    for v in specvalue:
        copyfeatname = featname[:]
        Dv,weightVec_v = splitDataSet(dataSet,Unmissweight,bestFeat,v,False)   #返回 此屬性爲v的所有樣本 以及 每個樣本的權重
        r_v = sum(weightVec_v) / UnmissSumWeight          #r~_v = D~_v / D~
        sondataSet,sonweight = splitDataSet_adjustWeight(dataSet,weight,bestFeat,v,r_v)
        DecisionTree[bestFeatname][v] = createDecisionTree(sondataSet,sonweight,copyfeatname)
    return DecisionTree

if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\breastcancer.txt"
    DataSet,featname = DataSetPredo(filename,[0,1,2,3,4,5,6,7,8])
    Tree = createDecisionTree(DataSet,[1.0 for i in range(len(DataSet))],featname)
    print(Tree)

有缺失值的情況如 西瓜數據集2.0alpha

實驗結果:

3.4 在乳腺癌數據集上的測試與表現

有了算法,我們當然想做一定的測試看一看算法的表現。這裏我選擇了威斯康辛女性乳腺癌的數據。

數據總共有9列,每一列分別代表,以逗號分割

1 Sample code number (病人ID)
2 Clump Thickness 腫塊厚度 
3 Uniformity of Cell Size 細胞大小的均勻性 
4 Uniformity of Cell Shape 細胞形狀的均勻性 
5 Marginal Adhesion 邊緣粘 
6 Single Epithelial Cell Size 單上皮細胞的大小 
7 Bare Nuclei 裸核 
8 Bland Chromatin 乏味染色體 
9 Normal Nucleoli 正常核 
10 Mitoses 有絲分裂
11 Class: 2 for benign, 4 formalignant(惡性或良性分類)

[from Toby]

總共700條左右的數據,選取最後80條作爲測試集,前面作爲訓練集,進行學習。

使用分類器的代碼如下:

import treesID3 as id3
import treePlot as tpl
import pickle
 
def classify(Tree, featnames, X):
    classLabel = "未知"
    root = list(Tree.keys())[0]
    firstGen = Tree[root]
    featindex = featnames.index(root)  #根節點的屬性下標
    for key in firstGen.keys():   #根屬性的取值,取哪個就走往哪顆子樹
        if X[featindex] == key:
            if type(firstGen[key]) == type({}):
                classLabel = classify(firstGen[key],featnames,X)
            else:
                classLabel = firstGen[key]
    return classLabel
 
def StoreTree(Tree,filename):
    fw = open(filename,'wb')
    pickle.dump(Tree,fw)
    fw.close()
 
def ReadTree(filename):
    fr = open(filename,'rb')
    return pickle.load(fr)
 
if __name__ == '__main__':
    filename = "D:\\MLinAction\\Data\\breastcancer.txt"
    dataSet,featnames = id3.DataSetPredo(filename,[0,1,2,3,4,5,6,7,8])
    Tree = id3.createDecisionTree(dataSet[:620],[1.0 for i in range(len(dataSet))],featnames)
    tpl.createPlot(Tree)
    storetree = "D:\\MLinAction\\Data\\decTree.dect"
    StoreTree(Tree,storetree)
    #Tree = ReadTree(storetree)
    i = 1
    cnt = 0
    for lis in dataSet[620:]:
        judge = classify(Tree,featnames,lis[:-1])
        shouldbe = lis[-1]
        if judge == shouldbe:
            cnt += 1
        print("Test %d was classified %s, it's class is %s %s" %(i,judge,shouldbe,"=====" if judge==shouldbe else ""))
        i += 1
    print("The Tree's Accuracy is %.3f" % (cnt / float(i)))

最終的正確率可以看到:

正確率約爲96%左右,算是不差的分類器了。

乳腺癌數據見:http://7xt9qk.com2.z0.glb.clouddn.com/breastcancer.txt【發現此鏈接已失效,請自行網絡搜尋

至此,決策樹算法ID3的實現完畢,下面考慮基於基尼指數和信息增益率進行劃分選擇,以及考慮實現剪枝過程,因爲我們可以看到上面訓練出的決策樹還存在着很多冗餘分支,是因爲實現過程中,由於數據量太大,每個分支都不完全純淨,所以會創建往下的分支,但是分支投票的結果又是一致的,而且數據量再大,特徵數再多的話,決策樹會非常大非常複雜,所以剪枝一般是必做的一步。剪枝分爲先剪枝和後剪枝,如果細說的話可以寫很多了。

 

4. 附錄:

1.西瓜數據集2.0

編號,色澤,根蒂,敲聲,紋理,臍部,觸感,好瓜
1,青綠,蜷縮,濁響,清晰,凹陷,硬滑,是
2,烏黑,蜷縮,沉悶,清晰,凹陷,硬滑,是
3,烏黑,蜷縮,濁響,清晰,凹陷,硬滑,是
4,青綠,蜷縮,沉悶,清晰,凹陷,硬滑,是
5,淺白,蜷縮,濁響,清晰,凹陷,硬滑,是
6,青綠,稍蜷,濁響,清晰,稍凹,軟粘,是
7,烏黑,稍蜷,濁響,稍糊,稍凹,軟粘,是
8,烏黑,稍蜷,濁響,清晰,稍凹,硬滑,是
9,烏黑,稍蜷,沉悶,稍糊,稍凹,硬滑,否
10,青綠,硬挺,清脆,清晰,平坦,軟粘,否
11,淺白,硬挺,清脆,模糊,平坦,硬滑,否
12,淺白,蜷縮,濁響,模糊,平坦,軟粘,否
13,青綠,稍蜷,濁響,稍糊,凹陷,硬滑,否
14,淺白,稍蜷,沉悶,稍糊,凹陷,硬滑,否
15,烏黑,稍蜷,濁響,清晰,稍凹,軟粘,否
16,淺白,蜷縮,濁響,模糊,平坦,硬滑,否
17,青綠,蜷縮,沉悶,稍糊,稍凹,硬滑,否

2.西瓜數據集2.0α

編號,色澤,根蒂,敲聲,紋理,臍部,觸感,好瓜
1,?,蜷縮,濁響,清晰,凹陷,硬滑,是
2,烏黑,蜷縮,沉悶,清晰,凹陷,?,是
3,烏黑,蜷縮,?,清晰,凹陷,硬滑,是
4,青綠,蜷縮,沉悶,清晰,凹陷,硬滑,是
5,?,蜷縮,濁響,清晰,凹陷,硬滑,是
6,青綠,稍蜷,濁響,清晰,?,軟粘,是
7,烏黑,稍蜷,濁響,稍糊,稍凹,軟粘,是
8,烏黑,稍蜷,濁響,?,稍凹,硬滑,是
9,烏黑,?,沉悶,稍糊,稍凹,硬滑,否
10,青綠,硬挺,清脆,?,平坦,軟粘,否
11,淺白,硬挺,清脆,模糊,平坦,?,否
12,淺白,蜷縮,?,模糊,平坦,軟粘,否
13,?,稍蜷,濁響,稍糊,凹陷,硬滑,否
14,淺白,稍蜷,沉悶,稍糊,凹陷,硬滑,否
15,烏黑,稍蜷,濁響,清晰,?,軟粘,否
16,淺白,蜷縮,濁響,模糊,平坦,硬滑,否
17,青綠,?,沉悶,稍糊,稍凹,硬滑,否

3.西瓜數據集3.0

編號,色澤,根蒂,敲聲,紋理,臍部,觸感,密度,含糖率,好瓜
1,青綠,蜷縮,濁響,清晰,凹陷,硬滑,0.697,0.460,是
2,烏黑,蜷縮,沉悶,清晰,凹陷,硬滑,0.774,0.376,是
3,烏黑,蜷縮,濁響,清晰,凹陷,硬滑,0.634,0.264,是
4,青綠,蜷縮,沉悶,清晰,凹陷,硬滑,0.608,0.318,是
5,淺白,蜷縮,濁響,清晰,凹陷,硬滑,0.556,0.215,是
6,青綠,稍蜷,濁響,清晰,稍凹,軟粘,0.403,0.237,是
7,烏黑,稍蜷,濁響,稍糊,稍凹,軟粘,0.481,0.149,是
8,烏黑,稍蜷,濁響,清晰,稍凹,硬滑,0.437,0.211,是
9,烏黑,稍蜷,沉悶,稍糊,稍凹,硬滑,0.666,0.091,否
10,青綠,硬挺,清脆,清晰,平坦,軟粘,0.243,0.267,否
11,淺白,硬挺,清脆,模糊,平坦,硬滑,0.245,0.057,否
12,淺白,蜷縮,濁響,模糊,平坦,軟粘,0.343,0.099,否
13,青綠,稍蜷,濁響,稍糊,凹陷,硬滑,0.639,0.161,否
14,淺白,稍蜷,沉悶,稍糊,凹陷,硬滑,0.657,0.198,否
15,烏黑,稍蜷,濁響,清晰,稍凹,軟粘,0.360,0.370,否
16,淺白,蜷縮,濁響,模糊,平坦,硬滑,0.593,0.042,否
17,青綠,蜷縮,沉悶,稍糊,稍凹,硬滑,0.719,0.103,否

4.西瓜數據集3.0α

密度 含糖率 好瓜
0.697 0.460 1
0.774 0.376 1
0.634 0.264 1
0.608 0.318 1
0.556 0.215 1
0.403 0.237 1
0.481 0.149 1
0.437 0.211 1
0.666 0.091 0
0.243 0.267 0
0.245 0.057 0
0.343 0.099 0
0.639 0.161 0
0.657 0.198 0
0.360 0.370 0
0.593 0.042 0
0.719 0.103 0

聲明:本文引自【https://www.cnblogs.com/whatbeg/p/5424890.html】,在原文基礎上略加改動。

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