1. 程式人生 > >機器學習實戰——1.2決策樹(2)

機器學習實戰——1.2決策樹(2)

宣告:參考書目《機器學習實戰》作者: Peter Harrington 出版社: 人民郵電出版社 譯者: 李銳 / 李鵬 / 曲亞東 / 王斌 

宣告:參考書目《統計學習方法》作者: 李航  出版社: 清華大學出版社   ISBN: 9787302275954

宣告:參考書目《機器學習》作者: 周志華  出版社: 清華大學出版社  ISBN: 9787302423287

參考部落格 Jack-Cui作者個人網站:http://cuijiahua.com/

 

一遞迴建立決策樹

目前我們已經學習了從資料集構造決策樹演算法需要的 子功能模組,其工作原理如下:得到原始資料集,然後基於最好的資料特徵劃分資料集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的資料集劃分。第一次劃分之後,資料將被傳遞到樹的分支下個節點,在這個節點上,我們可以繼續劃分資料集。由此可見,我們將採用遞迴呼叫的方式來處理資料集。

遞迴結束的條件是:(1)程式遍歷完所有劃分資料集的特徵屬性;(2)每個分支下的所有例項都具有相同的分類。如果所有例項都具有相同的分類,則得到一個葉子結點或者終止節點。任何達到葉子結點的資料必然屬於葉子結點的分類。

第一個結束條件使得演算法可以終止,我們甚至可以設定演算法可以劃分的最大組數目。如果資料集已經處理了所有的特徵,但是類標籤仍不是唯一的,這時我們就需要使用舉手表決的方法來少數服從多數了。我們會選擇類數目最多的分類。


import operator
"""
classlist:給定的資料字典
function:如果決策樹仍然不能正確分類,那麼就採取舉手表達的措施選擇類數最大的類別
"""
def majorityCnt(classlist):
    classCount = {}                             #建立一個字典,用於存放最大的類別
    for vote in classlist:
        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]

該函式使用分類名稱的列表,然後建立鍵值為  classList  中唯一的資料字典,字典物件儲存了  classList  中每個類標籤出現的頻率,最後利用 operator  操作鍵值排序字典,並返回出現次數最多的分類名稱。

然後我們就可以建立決策樹了:


"""
dataSet:給定的資料集
lables:給定的標籤
return:返回的是構造好的決策樹
"""
def createTree(dataSet,lables):
    class_list = [example[-1]  for example in dataSet]         #獲取資料集的類別
    if class_list.count(class_list[0]) == len(class_list):      #如果資料集都是一種類別的話,那麼就返回這個類別
        return class_list[0]
    if len(dataSet[0]) == 1:                                    #如果遍歷完所有資料任有資料不能正確分類,那麼就採用舉手表決的方式,選擇資料類最大的一個類別
        return majorityCnt(class_list)
    bestFeat = chooseBestFeature(dataSet)                      #獲取最佳特徵的索引值
    bestFeatLabel = lables[bestFeat]                           #根據索引值在標籤列表裡面獲得標籤的名稱
    myTree = {bestFeatLabel:{}}                                #建立一個字典,這個字典用於存放決策樹的資料結構
    del(lables[bestFeat])                                      #獲得了我們需要的標籤之後,就將標籤從標籤列表裡面刪除,防止產生死迴圈
    featValue = [example[bestFeat] for example in dataSet]     #從資料集獲得對應標籤的所有資料,即最好的特徵資料的值
    uniqueValue = set(featValue)                               #利用集合將重複的特徵資料的值刪除。每個特徵的值只留下一個
    for value in uniqueValue:                                  #迴圈獲取特徵的值
        subLables = lables[:]                                  #將刪除最優特徵的標籤列表賦值給subLables
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value), subLables)    #遞迴呼叫數生成函式,遞迴生成樹的節點
    return myTree                                             #返回特徵的樹字典結構表示式

下面是建立決策樹的完整程式碼:

from math import log
import operator
def createDataSet():
    dataSet = [['青年', '否', '否', '一般',    'no'],
            [   '青年', '否', '否', '好',      'no'],
            [   '青年', '是', '否', '好',     'yes'],
            [   '青年', '是', '是', '一般',   'yes'],
            [   '青年', '否', '否', '一般',    'no'],
            [   '中年', '否', '否', '一般',    'no'],
            [   '中年', '否', '否', '好',      'no'],
            [   '中年', '是', '是', '好',     'yes'],
            [   '中年', '否', '是', '非常好', 'yes'],
            [   '中年', '否', '是', '非常好', 'yes'],
            [   '老年', '否', '是', '非常好', 'yes'],
            [   '老年', '否', '是', '好',     'yes'],
            [   '老年', '是', '否', '好',     'yes'],
            [   '老年', '是', '否', '非常好', 'yes'],
            [   '老年', '否', '否', '一般',     'no']]
    labels = ['年齡', '有工作', '有自己的房子', '信貸情況']
    return dataSet,labels


"""
DataSet:給定的資料集
Function:計算給定資料集的香濃熵
return:返回的是計算好的香濃熵
"""
def ShannonEnt(dataSet):
    len_dataSet = len(dataSet)     #樣本資料的個數
    label_class = {}               #用來記錄每個樣本類別的個數
    Ent = 0.0                      #用來儲存經驗熵
    for item in dataSet:          #迴圈讀入例項
        if item[-1] not in label_class.keys():       #如果儲存類別的的字典內沒有現在的類別,那麼就建立一個以當前類別為key值的元素
            label_class[item[-1]] = 0                 #並將其value值賦值為0
        label_class[item[-1]] += 1                    #如果字典內已經存在此類別,那麼將其value加 1,即當前類別的個數加一
    for lable in label_class:                         #從字典內迴圈獲取所有的類別
        P = float(label_class[lable]) / len_dataSet   #計算當前類別佔總樣本資料的比例,即當前類別出現的概率
        Ent -= P * log(P, 2)        #計算所有類別的香濃熵
    return Ent


"""
dataSet: 給定的資料集
axis: 給定的特徵的索引值
value: 對應索引的值
new_dataset: 根據給定的特徵劃分的新資料
Function:按照給定的特徵將資料集分類成新的資料集
"""
def splitDataSet(dataSet, axis, value):
    new_dataset = []
    for item in dataSet:                            #迴圈讀入資料集的每一行資料
        if item[axis] == value:                     #如果是我們需要的特徵的資料就將其存放到新的陣列中
            templet_set = item[:axis]               #中間變數,用於存放獲取的特徵變數所在行的其他資料
            templet_set.extend(item[axis+1:])       #a=[1,2,3], b=[4,5,6]   a.extend(b)=[1, 2, 3, 4, 5, 6]
            new_dataset.append(templet_set)         #a=[1,2,3], b=[4,5,6]   a.append(b)=[1, 2, 3, [4,5,6] ]
    return new_dataset


"""
dataSet: 輸入的資料集
Function: 選擇資訊增益最大的特徵
return: 返回的是資訊增益最大的特徵在DataSet中的列索引值
"""
def chooseBestFeature(dataSet):
    len_lables = len(dataSet[0]) - 1     #獲取資料集的特徵總數。減去的是資料集的類別
    base_Ent = ShannonEnt(dataSet)  #獲得總資料集的夏農熵
    base_InfoGain = 0.0             #用於記錄當前最佳資訊增益
    best_lables = -1                #用於記錄獲得最佳資訊增益的特徵的索引值
    for i in range(len_lables):    #獲取每個特徵相應的夏農熵
        lable_list = [items[i] for items in dataSet]  #利用列表生成式獲取相應特徵下的分類,item表示為dataSet的單行資料,item[i]表示對應資料的具體數值
        unique_value = set(lable_list)  #利用集合獲得唯一的資料特徵,set跟數學中的集合概念類似,裡面沒有重複的元素
        new_Ent = 0.0                 #用於存放當前子類資料集的經驗條件熵
        for value in unique_value:   #獲取單個特徵值下的對應值例如:青年,中年, 老年
            sub_dataset = splitDataSet(dataSet, i, value)  #按照當前的特徵值將資料集進行劃分
            prob = len(sub_dataset) / float(len(dataSet))  #獲得當前特徵的資料佔總資料的比例,即概率
            new_Ent += prob * ShannonEnt(sub_dataset)      #獲得當前類別的經驗條件熵
        info_Gain = base_Ent - new_Ent          #獲得當前的資訊增益
        #print("第",i,"個特徵的資訊增益為 ",info_Gain)
        if(info_Gain > base_InfoGain):
            base_InfoGain = info_Gain           #如果遇見更好的資訊增益,就將更好的資訊增益賦值給best_InfoGain,並且記錄下當前資訊增益的特徵值的索引值
            best_lables = i
    #print("最好的特徵索引值為:",best_lables)
    return best_lables

"""
classlist:給定的資料字典
function:如果決策樹仍然不能正確分類,那麼就採取舉手表達的措施選擇類數最大的類別
"""
def majorityCnt(classlist):
    classCount = {}                             #建立一個字典,用於存放最大的類別
    for vote in classlist:
        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]

"""
dataSet:給定的資料集
lables:給定的標籤
return:返回的是構造好的決策樹
"""
def createTree(dataSet,lables):
    class_list = [example[-1]  for example in dataSet]         #獲取資料集的類別
    if class_list.count(class_list[0]) == len(class_list):      #如果資料集都是一種類別的話,那麼就返回這個類別
        return class_list[0]
    if len(dataSet[0]) == 1:                                    #如果遍歷完所有資料任有資料不能正確分類,那麼就採用舉手表決的方式,選擇資料類最大的一個類別
        return majorityCnt(class_list)
    bestFeat = chooseBestFeature(dataSet)                      #獲取最佳特徵的索引值
    bestFeatLabel = lables[bestFeat]                           #根據索引值在標籤列表裡面獲得標籤的名稱
    myTree = {bestFeatLabel:{}}                                #建立一個字典,這個字典用於存放決策樹的資料結構
    del(lables[bestFeat])                                      #獲得了我們需要的標籤之後,就將標籤從標籤列表裡面刪除,防止產生死迴圈
    featValue = [example[bestFeat] for example in dataSet]     #從資料集獲得對應標籤的所有資料,即最好的特徵資料的值
    uniqueValue = set(featValue)                               #利用集合將重複的特徵資料的值刪除。每個特徵的值只留下一個
    for value in uniqueValue:                                  #迴圈獲取特徵的值
        subLables = lables[:]                                  #將刪除最優特徵的標籤列表賦值給subLables
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value), subLables)    #遞迴呼叫數生成函式,遞迴生成樹的節點
    return myTree                                             #返回特徵的樹字典結構表示式


if __name__ == "__main__":
    dataSet,label = createDataSet()
    myTree = createTree(dataSet,label)
    print("決策樹的字典表示式:")
    print(myTree)

下面是創建出來的決策樹

決策樹的字典表示式:
{'有自己的房子': {'否': {'有工作': {'否': 'no', '是': 'yes'}}, '是': 'yes'}}

Process finished with exit code 0

二使用決策樹進行分類工作

依靠訓練資料結構建立了決策樹之後,我們可以將它應用於實際資料的分類。在執行資料分類時,需要使用決策樹以及用於構建決策樹的標籤向量。然後程式比較測試資料與決策樹上的數值,遞迴執行該過程直到進入葉子結點;最後將測試資料定義為葉子結點所屬的型別。

為了驗證演算法的實際效果,需要編寫一段測試程式:


"""
inputTree:輸入我們構建好的樹
featLabels:資料集的標籤
testVec:列表,包含樹的節點
return:返回的是葉子結點
function:用於測試決策樹是否合格
"""
def classify(inputTree, featLabels, testVec):
    firstStr = next(iter(inputTree))                                                        #獲取決策樹結點
    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

然後我們需要將結構樹進行持久化操作,將結構樹以二進位制方式寫入文字,下次使用時,可以直接從文字讀取結構樹,而不必重新執行所有程式來求取結構樹。我們使用  pickle 來進行決策樹的持久化以及讀取操作:

import pickle
"""
function:將決策樹持久化
"""
def storeTree(inputTree, filename):
    with open(filename, 'wb') as f:     #以二進位制方式寫入資料
        pickle.dump(inputTree,f)

"""
function:讀取持久化的決策樹
"""
def grabTree(filename):
    with open(filename,'rb') as r:       #以二進位制方式讀取資料
        return pickle.load(r)

然後我們可以來檢驗一下是否能夠正常使用我們的決策樹,我們將會利用決策樹來進行眼鏡案例的分類,首先我們需要驗證我們是否正常將決策樹持久化了:

from math import log
import operator
import pickle


def createDataSet():
    dataSet = [['青年', '否', '否', '一般',    'no'],
            [   '青年', '否', '否', '好',      'no'],
            [   '青年', '是', '否', '好',     'yes'],
            [   '青年', '是', '是', '一般',   'yes'],
            [   '青年', '否', '否', '一般',    'no'],
            [   '中年', '否', '否', '一般',    'no'],
            [   '中年', '否', '否', '好',      'no'],
            [   '中年', '是', '是', '好',     'yes'],
            [   '中年', '否', '是', '非常好', 'yes'],
            [   '中年', '否', '是', '非常好', 'yes'],
            [   '老年', '否', '是', '非常好', 'yes'],
            [   '老年', '否', '是', '好',     'yes'],
            [   '老年', '是', '否', '好',     'yes'],
            [   '老年', '是', '否', '非常好', 'yes'],
            [   '老年', '否', '否', '一般',     'no']]
    labels = ['年齡', '有工作', '有自己的房子', '信貸情況']
    return dataSet,labels


"""
DataSet:給定的資料集
Function:計算給定資料集的香濃熵
return:返回的是計算好的香濃熵
"""
def ShannonEnt(dataSet):
    len_dataSet = len(dataSet)     #樣本資料的個數
    label_class = {}               #用來記錄每個樣本類別的個數
    Ent = 0.0                      #用來儲存經驗熵
    for item in dataSet:          #迴圈讀入例項
        if item[-1] not in label_class.keys():       #如果儲存類別的的字典內沒有現在的類別,那麼就建立一個以當前類別為key值的元素
            label_class[item[-1]] = 0                 #並將其value值賦值為0
        label_class[item[-1]] += 1                    #如果字典內已經存在此類別,那麼將其value加 1,即當前類別的個數加一
    for lable in label_class:                         #從字典內迴圈獲取所有的類別
        P = float(label_class[lable]) / len_dataSet   #計算當前類別佔總樣本資料的比例,即當前類別出現的概率
        Ent -= P * log(P, 2)        #計算所有類別的香濃熵
    return Ent


"""
dataSet: 給定的資料集
axis: 給定的特徵的索引值
value: 對應索引的值
new_dataset: 根據給定的特徵劃分的新資料
Function:按照給定的特徵將資料集分類成新的資料集
"""
def splitDataSet(dataSet, axis, value):
    new_dataset = []
    for item in dataSet:                            #迴圈讀入資料集的每一行資料
        if item[axis] == value:                     #如果是我們需要的特徵的資料就將其存放到新的陣列中
            templet_set = item[:axis]               #中間變數,用於存放獲取的特徵變數所在行的其他資料
            templet_set.extend(item[axis+1:])       #a=[1,2,3], b=[4,5,6]   a.extend(b)=[1, 2, 3, 4, 5, 6]
            new_dataset.append(templet_set)         #a=[1,2,3], b=[4,5,6]   a.append(b)=[1, 2, 3, [4,5,6] ]
    return new_dataset


"""
dataSet: 輸入的資料集
Function: 選擇資訊增益最大的特徵
return: 返回的是資訊增益最大的特徵在DataSet中的列索引值
"""
def chooseBestFeature(dataSet):
    len_lables = len(dataSet[0]) - 1     #獲取資料集的特徵總數。減去的是資料集的類別
    base_Ent = ShannonEnt(dataSet)  #獲得總資料集的夏農熵
    base_InfoGain = 0.0             #用於記錄當前最佳資訊增益
    best_lables = -1                #用於記錄獲得最佳資訊增益的特徵的索引值
    for i in range(len_lables):    #獲取每個特徵相應的夏農熵
        lable_list = [items[i] for items in dataSet]  #利用列表生成式獲取相應特徵下的分類,item表示為dataSet的單行資料,item[i]表示對應資料的具體數值
        unique_value = set(lable_list)  #利用集合獲得唯一的資料特徵,set跟數學中的集合概念類似,裡面沒有重複的元素
        new_Ent = 0.0                 #用於存放當前子類資料集的經驗條件熵
        for value in unique_value:   #獲取單個特徵值下的對應值例如:青年,中年, 老年
            sub_dataset = splitDataSet(dataSet, i, value)  #按照當前的特徵值將資料集進行劃分
            prob = len(sub_dataset) / float(len(dataSet))  #獲得當前特徵的資料佔總資料的比例,即概率
            new_Ent += prob * ShannonEnt(sub_dataset)      #獲得當前類別的經驗條件熵
        info_Gain = base_Ent - new_Ent          #獲得當前的資訊增益
        #print("第",i,"個特徵的資訊增益為 ",info_Gain)
        if(info_Gain > base_InfoGain):
            base_InfoGain = info_Gain           #如果遇見更好的資訊增益,就將更好的資訊增益賦值給best_InfoGain,並且記錄下當前資訊增益的特徵值的索引值
            best_lables = i
    #print("最好的特徵索引值為:",best_lables)
    return best_lables

"""
classlist:給定的資料字典
function:如果決策樹仍然不能正確分類,那麼就採取舉手表達的措施選擇類數最大的類別
"""
def majorityCnt(classlist):
    classCount = {}                             #建立一個字典,用於存放最大的類別
    for vote in classlist:
        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]

"""
dataSet:給定的資料集
lables:給定的標籤
return:返回的是構造好的決策樹
"""
def createTree(dataSet,lables):
    class_list = [example[-1]  for example in dataSet]         #獲取資料集的類別
    if class_list.count(class_list[0]) == len(class_list):      #如果資料集都是一種類別的話,那麼就返回這個類別
        return class_list[0]
    if len(dataSet[0]) == 1:                                    #如果遍歷完所有資料任有資料不能正確分類,那麼就採用舉手表決的方式,選擇資料類最大的一個類別
        return majorityCnt(class_list)
    bestFeat = chooseBestFeature(dataSet)                      #獲取最佳特徵的索引值
    bestFeatLabel = lables[bestFeat]                           #根據索引值在標籤列表裡面獲得標籤的名稱
    myTree = {bestFeatLabel:{}}                                #建立一個字典,這個字典用於存放決策樹的資料結構
    del(lables[bestFeat])                                      #獲得了我們需要的標籤之後,就將標籤從標籤列表裡面刪除,防止產生死迴圈
    featValue = [example[bestFeat] for example in dataSet]     #從資料集獲得對應標籤的所有資料,即最好的特徵資料的值
    uniqueValue = set(featValue)                               #利用集合將重複的特徵資料的值刪除。每個特徵的值只留下一個
    for value in uniqueValue:                                  #迴圈獲取特徵的值
        subLables = lables[:]                                  #將刪除最優特徵的標籤列表賦值給subLables
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value), subLables)    #遞迴呼叫數生成函式,遞迴生成樹的節點
    return myTree                                             #返回特徵的樹字典結構表示式


"""
inputTree:輸入我們構建好的樹
featLabels:資料集的標籤
testVec:列表,包含樹的節點
return:返回的是葉子結點
function:用於測試決策樹是否合格
"""
def classify(inputTree, featLabels, testVec):
    firstStr = next(iter(inputTree))                                                        #獲取決策樹結點
    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

"""
function:將決策樹持久化
"""
def storeTree(inputTree, filename):
    with open(filename, 'wb') as f:     #以二進位制方式寫入資料
        pickle.dump(inputTree,f)

"""
function:讀取持久化的決策樹
"""
def grabTree(filename):
    with open(filename,'rb') as r:       #以二進位制方式讀取資料
        return pickle.load(r)


if __name__ == "__main__":
    dataSet, label = createDataSet()
    myTree = createTree(dataSet,label)
    filename = 'C:/Users/lpp/Desktop/Tree.txt'
    storeTree(myTree, filename)
    load_tree = grabTree(filename)
    print(load_tree)

然後輸出我們的結果:

{'有自己的房子': {'否': {'有工作': {'否': 'no', '是': 'yes'}}, '是': 'yes'}}

Process finished with exit code 0

這說明我們可以正常使用我們構建的決策樹了,然後我們可以看一下我們儲存的決策樹的具體內容:一堆亂碼,因為我們使用的是二進位制儲存。

三使用決策樹預測隱形眼鏡的型別

通過這兩節內容,我們已經構建了一個可以正常使用的決策樹模型了,那麼我們現在就利用已經構建好的決策樹模型來預測隱形眼鏡的分類,資料集以及python程式碼連線

  https://github.com/Jack-honor/Machine-Learning/tree/master/%E5%86%B3%E7%AD%96%E6%A0%91


from math import log
import operator
import pickle

"""
DataSet:給定的資料集
Function:計算給定資料集的香濃熵
return:返回的是計算好的香濃熵
"""
def ShannonEnt(dataSet):
    len_dataSet = len(dataSet)     #樣本資料的個數
    label_class = {}               #用來記錄每個樣本類別的個數
    Ent = 0.0                      #用來儲存經驗熵
    for item in dataSet:          #迴圈讀入例項
        if item[-1] not in label_class.keys():       #如果儲存類別的的字典內沒有現在的類別,那麼就建立一個以當前類別為key值的元素
            label_class[item[-1]] = 0                 #並將其value值賦值為0
        label_class[item[-1]] += 1                    #如果字典內已經存在此類別,那麼將其value加 1,即當前類別的個數加一
    for lable in label_class:                         #從字典內迴圈獲取所有的類別
        P = float(label_class[lable]) / len_dataSet   #計算當前類別佔總樣本資料的比例,即當前類別出現的概率
        Ent -= P * log(P, 2)        #計算所有類別的香濃熵
    return Ent


"""
dataSet: 給定的資料集
axis: 給定的特徵的索引值
value: 對應索引的值
new_dataset: 根據給定的特徵劃分的新資料
Function:按照給定的特徵將資料集分類成新的資料集
"""
def splitDataSet(dataSet, axis, value):
    new_dataset = []
    for item in dataSet:                            #迴圈讀入資料集的每一行資料
        if item[axis] == value:                     #如果是我們需要的特徵的資料就將其存放到新的陣列中
            templet_set = item[:axis]               #中間變數,用於存放獲取的特徵變數所在行的其他資料
            templet_set.extend(item[axis+1:])       #a=[1,2,3], b=[4,5,6]   a.extend(b)=[1, 2, 3, 4, 5, 6]
            new_dataset.append(templet_set)         #a=[1,2,3], b=[4,5,6]   a.append(b)=[1, 2, 3, [4,5,6] ]
    return new_dataset


"""
dataSet: 輸入的資料集
Function: 選擇資訊增益最大的特徵
return: 返回的是資訊增益最大的特徵在DataSet中的列索引值
"""
def chooseBestFeature(dataSet):
    len_lables = len(dataSet[0]) - 1     #獲取資料集的特徵總數。減去的是資料集的類別
    base_Ent = ShannonEnt(dataSet)  #獲得總資料集的夏農熵
    base_InfoGain = 0.0             #用於記錄當前最佳資訊增益
    best_lables = -1                #用於記錄獲得最佳資訊增益的特徵的索引值
    for i in range(len_lables):    #獲取每個特徵相應的夏農熵
        lable_list = [items[i] for items in dataSet]  #利用列表生成式獲取相應特徵下的分類,item表示為dataSet的單行資料,item[i]表示對應資料的具體數值
        unique_value = set(lable_list)  #利用集合獲得唯一的資料特徵,set跟數學中的集合概念類似,裡面沒有重複的元素
        new_Ent = 0.0                 #用於存放當前子類資料集的經驗條件熵
        for value in unique_value:   #獲取單個特徵值下的對應值例如:青年,中年, 老年
            sub_dataset = splitDataSet(dataSet, i, value)  #按照當前的特徵值將資料集進行劃分
            prob = len(sub_dataset) / float(len(dataSet))  #獲得當前特徵的資料佔總資料的比例,即概率
            new_Ent += prob * ShannonEnt(sub_dataset)      #獲得當前類別的經驗條件熵
        info_Gain = base_Ent - new_Ent          #獲得當前的資訊增益
        #print("第",i,"個特徵的資訊增益為 ",info_Gain)
        if(info_Gain > base_InfoGain):
            base_InfoGain = info_Gain           #如果遇見更好的資訊增益,就將更好的資訊增益賦值給best_InfoGain,並且記錄下當前資訊增益的特徵值的索引值
            best_lables = i
    #print("最好的特徵索引值為:",best_lables)
    return best_lables

"""
classlist:給定的資料字典
function:如果決策樹仍然不能正確分類,那麼就採取舉手表達的措施選擇類數最大的類別
"""
def majorityCnt(classlist):
    classCount = {}                             #建立一個字典,用於存放最大的類別
    for vote in classlist:
        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]

"""
dataSet:給定的資料集
lables:給定的標籤
return:返回的是構造好的決策樹
"""
def createTree(dataSet,lables):
    class_list = [example[-1]  for example in dataSet]         #獲取資料集的類別
    if class_list.count(class_list[0]) == len(class_list):      #如果資料集都是一種類別的話,那麼就返回這個類別
        return class_list[0]
    if len(dataSet[0]) == 1:                                    #如果遍歷完所有資料任有資料不能正確分類,那麼就採用舉手表決的方式,選擇資料類最大的一個類別
        return majorityCnt(class_list)
    bestFeat = chooseBestFeature(dataSet)                      #獲取最佳特徵的索引值
    bestFeatLabel = lables[bestFeat]                           #根據索引值在標籤列表裡面獲得標籤的名稱
    myTree = {bestFeatLabel:{}}                                #建立一個字典,這個字典用於存放決策樹的資料結構
    del(lables[bestFeat])                                      #獲得了我們需要的標籤之後,就將標籤從標籤列表裡面刪除,防止產生死迴圈
    featValue = [example[bestFeat] for example in dataSet]     #從資料集獲得對應標籤的所有資料,即最好的特徵資料的值
    uniqueValue = set(featValue)                               #利用集合將重複的特徵資料的值刪除。每個特徵的值只留下一個
    for value in uniqueValue:                                  #迴圈獲取特徵的值
        subLables = lables[:]                                  #將刪除最優特徵的標籤列表賦值給subLables
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value), subLables)    #遞迴呼叫數生成函式,遞迴生成樹的節點
    return myTree                                             #返回特徵的樹字典結構表示式


"""
inputTree:輸入我們構建好的樹
featLabels:資料集的標籤
testVec:列表,包含樹的節點
return:返回的是葉子結點
function:用於測試決策樹是否合格
"""
def classify(inputTree, featLabels, testVec):
    firstStr = next(iter(inputTree))                                                        #獲取決策樹結點
    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

"""
function:將決策樹持久化
"""
def storeTree(inputTree, filename):
    with open(filename, 'wb') as f:     #以二進位制方式寫入資料
        pickle.dump(inputTree,f)

"""
function:讀取持久化的決策樹
"""
def grabTree(filename):
    with open(filename,'rb') as r:       #以二進位制方式讀取資料
        return pickle.load(r)


if __name__ == "__main__":
    filename = 'C:/Users/lpp/Desktop/lenses.txt'
    with open(filename) as f:
        lenses = [inst.strip().split('\t') for inst in f.readlines()]
        lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
        lensesTree = createTree(lenses, lensesLabels)
    print(lensesTree)

程式碼跟上面幾乎沒有什麼不同,唯一不同的地方就是更該了主程式,將資料的讀取換成從檔案讀取

結果如下:

{'tearRate': {'normal': {'astigmatic': {'yes': {'prescript': {'myope': 'hard', 'hyper': {'age': {'presbyopic': 'no lenses', 'young': 'hard', 'pre': 'no lenses'}}}}, 'no': {'age': {'presbyopic': {'prescript': {'myope': 'no lenses', 'hyper': 'soft'}}, 'young': 'soft', 'pre': 'soft'}}}}, 'reduced': 'no lenses'}}

Process finished with exit code 0