3-7節(jié) 決策樹|判定魚類和非魚類項目匯總|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

文章原創(chuàng),最近更新:2018-08-20

本章節(jié)的主要內(nèi)容是:
重點介紹項目案例1:判定魚類和非魚類測試算法:測試和存儲分類器的代碼。

1.決策樹項目案例介紹:

項目案例1:

判定魚類和非魚類

項目概述:
  • 根據(jù)以下 2 個特征,將動物分成兩類:魚類和非魚類。
  • 特征: 1. 不浮出水面是否可以生存 2. 是否有腳蹼
開發(fā)流程:
  • 收集數(shù)據(jù):可以使用任何方法
  • 準(zhǔn)備數(shù)據(jù):樹構(gòu)造算法只適用于標(biāo)稱型數(shù)據(jù),因此數(shù)值型數(shù)據(jù)必須離散化
  • 分析數(shù)據(jù):可以使用任何方法,構(gòu)造樹完成之后,我們應(yīng)該檢查圖形是否符合預(yù)期
  • 訓(xùn)練算法:構(gòu)造樹的數(shù)據(jù)結(jié)構(gòu)
  • 測試算法:使用決策樹執(zhí)行分類
  • 使用算法:此步驟可以適用于任何監(jiān)督學(xué)習(xí)算法,而使用決策樹可以更好地理解數(shù)據(jù)的內(nèi)在含義
數(shù)據(jù)集介紹

2.代碼匯總

2.1測試數(shù)據(jù)集

首先創(chuàng)建一個名為trees.py的文件,createDataSet()函數(shù)錄入到trees.py文件.

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
2.2計算給定數(shù)據(jù)集的香農(nóng)熵的函數(shù)

這段代碼主要是計算給定數(shù)據(jù)集的熵,創(chuàng)建一個函數(shù)calcShannonEn()函數(shù)錄入到trees.py文件.

def calcShannonEnt(dataSet):
    # 獲取數(shù)據(jù)集dataSet列表的長度,表示計算參與訓(xùn)練的數(shù)據(jù)量
    numEntries=len(dataSet)
    # 新建一個空字典labelCounts,用以統(tǒng)計每個標(biāo)簽出現(xiàn)的次數(shù),進而計算概率
    labelCounts={}
    for featVec in dataSet:
        # featVec[-1]獲取了daatSet中每行最后一個數(shù)據(jù),作為字典中的key(標(biāo)簽)
        currentLabel = featVec[-1]
        # 以currentLabel作為key加入到字典labelCounts.
        # 如果當(dāng)前的鍵值不存在,則擴展字典并將當(dāng)前鍵值加入字典。每個鍵值都記錄了當(dāng)前類別出現(xiàn)的次數(shù)。
        # 鍵值存在則則對應(yīng)value+1,否則為0
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        
        labelCounts[currentLabel] += 1
    # 對于 label 標(biāo)簽的占比,求出 label 標(biāo)簽的香農(nóng)熵
    shannonEnt = 0.0 
    for key in labelCounts:
        # 計算分類概率prob=標(biāo)簽發(fā)生頻率,labelCounts[key]除以數(shù)據(jù)集長度numEntries
        prob = float(labelCounts[key])/numEntries
        # 計算香農(nóng)熵,以2為底求對數(shù)
        shannonEnt -=prob * log(prob,2)
    return shannonEnt

測試代碼及其結(jié)果如下:

import trees

a, b = trees.createDataSet()

trees.calcShannonEnt(a)
Out[90]: 0.9709505944546686

2.3劃分?jǐn)?shù)據(jù)集的函數(shù)代碼

這個函數(shù)的是作用是當(dāng)我們按某個特征劃分?jǐn)?shù)據(jù)集時,把劃分后剩下的元素抽取出來,形成一個新的子集,用于計算條件熵。

創(chuàng)建一個函數(shù)splitDataSet()函數(shù)錄入到trees.py文件.

具體相關(guān)知識點,可參見:3-2節(jié) 決策樹|劃分?jǐn)?shù)據(jù)集|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

def splitDataSet(dataSet,axis,value):
    """
    splitDataSet(通過遍歷dataSet數(shù)據(jù)集,求出index對應(yīng)的column列的值為value的行)
    就是依據(jù)index列進行分類,如果index列的數(shù)據(jù)等于value的時候,就要index劃分到我們創(chuàng)建的新的數(shù)據(jù)集中
    Args:
      dataSet:數(shù)據(jù)集                待劃分的數(shù)據(jù)集
      axis:表示每一行的index列      特征的坐標(biāo),等于0,第0個特征為0或者1
      value:表示index列對應(yīng)的value值 需要返回的特征的值
    Returns:
        index列為value的數(shù)據(jù)集[該數(shù)據(jù)集需要排除axis列]
    """
    retDataSet = []
    # index列為value的數(shù)據(jù)集[該數(shù)據(jù)集需要排除index列]
    # 判斷index列的值是否等于value
    # 遍歷數(shù)據(jù)集,將axis上的數(shù)據(jù)和value值進行對比
    for featVec in dataSet:
        # 如果待檢測的特征axis和指定的特征value相等
        if featVec[axis] == value:
            # 從第0開始,一旦發(fā)現(xiàn)第axis符合要求,就將數(shù)據(jù)0-axis保存至reduceFeatVec
            reducedFeatVec =featVec[:axis]
            # 將指定的數(shù)據(jù)的axis+1位到末尾添加至reducedFeatVec,保持?jǐn)?shù)據(jù)完整性
            reducedFeatVec.extend(featVec[axis+1:])
            # 收集結(jié)果值除掉index列的reducedFeatVec收據(jù)集添加到retDataSet數(shù)據(jù)集
            retDataSet.append(reducedFeatVec)
    return retDataSet

測試代碼及其結(jié)果如下:

import trees
mydata,labels=trees.createDataSet()

mydata
Out[111]: [[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]

trees.splitDataSet(mydata,0,1)
Out[112]: [[1, 'maybe'], [1, 'yes'], [0, 'no']]
2.4選擇最好的數(shù)據(jù)集劃分方式的函數(shù)代碼

接下來我們將遍歷整個數(shù)據(jù)集,循環(huán)計算香農(nóng)熵和 splitDataSet()函數(shù),找到最好的特征劃分方式。熵計算將會告訴我們?nèi)绾蝿澐謹(jǐn)?shù)據(jù)集是最好的數(shù)據(jù)組織方式.

創(chuàng)建一個函數(shù)chooseBestFeatTopSplit()函數(shù)錄入到trees.py文件.

具體相關(guān)知識點,可參見:3-3節(jié) 決策樹|選擇最好的數(shù)據(jù)集劃分方式|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

def chooseBestFeatTopSplit(dataSet):
    """chooseBestFeatureToSplit(選擇最好的特征)

    Args:
        dataSet 數(shù)據(jù)集
    Returns:
        bestFeature 最優(yōu)的特征列
    """
    # 求第一行有多少列的 Feature, 減去1,是因為最后一列是label列
    numFeatures = len(dataSet[0])-1
    # 計算沒有經(jīng)過劃分的數(shù)據(jù)的香農(nóng)熵
    baseEntropy = calcShannonEnt(dataSet) 
    # 最優(yōu)的信息增益值
    bestInfoGain = 0.0
    #最優(yōu)的Featurn編號
    bestFeature = -1
    for i in range(numFeatures): 
        # 創(chuàng)建唯一的分類標(biāo)簽列表,獲取第i個的所有特征(信息元縱排列?。?        featList = [example[i] for example in dataSet]
        """
        print(featList)結(jié)果為
        [1, 1, 1, 0, 0]
        [1, 1, 0, 1, 1]
        """
        # 使用set集,排除featList中的重復(fù)標(biāo)簽,得到唯一分類的集合
        uniqueVals = set(featList)
        """
        print(uniqueVals)結(jié)果為
        {0, 1}
        {0, 1}
        """
        newEntropy = 0.0
         # 遍歷當(dāng)次uniqueVals中所有的標(biāo)簽value(這里是0,1)
        for value in uniqueVals: 
            # 對第i個數(shù)據(jù)劃分?jǐn)?shù)據(jù)集, 返回所有包含i的數(shù)據(jù)(已排除第i個特征)
            subDataSet = splitDataSet(dataSet, i, value)
            """
            print(subDataSet)結(jié)果為
            [[1, 'no'], [1, 'no']]
            [[1, 'yes'], [1, 'yes'], [0, 'no']]
            [[1, 'no']]
            [[1, 'yes'], [1, 'yes'], [0, 'no'], [0, 'no']]
            """        
            # 計算包含個i的數(shù)據(jù)占總數(shù)據(jù)的百分比
            prob = len(subDataSet)/float(len(dataSet))
            """
            print(prob)結(jié)果為
            0.4
            0.6
            0.2
            0.8
            """
            # 計算新的香農(nóng)熵,不斷進行迭代,這個計算過程僅在包含指定特征標(biāo)簽子集中進行
            newEntropy += prob * calcShannonEnt(subDataSet) 
            """
            print(calcShannonEnt(subDataSet))
            0.0
            0.9182958340544896
            0.0
            1.0
        
            print(newEntropy)結(jié)果為
            0.0
            0.5509775004326937
            0.0
            0.8
            """
            
            # 計算信息增益
            infoGain = baseEntropy - newEntropy
            # 如果信息增益大于最優(yōu)增益,即新增益newEntropy越小,信息增益越大,分類也就更優(yōu)(分類越簡單越好)
            """
            print(infoGain)結(jié)果為
            0.4199730940219749
            0.17095059445466854
            """
            
            if (infoGain > bestInfoGain): 
                # 更新信息增益 
                bestInfoGain = infoGain
                # 確定最優(yōu)增益的特征索引
                bestFeature = i 
                # 更新信息增益
        # 返回最優(yōu)增益的索引
        return bestFeature 

測試代碼及其 結(jié)果如下:

import trees
myDat,labels=trees.createDataSet()

myDat
Out[182]: [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]

trees.chooseBestFeatTopSplit(myDat)
Out[183]: 0
2.5 遞歸構(gòu)建決策樹

創(chuàng)建分別函數(shù)majorityCnt()以及createTree()錄入到trees.py文件.

具體相關(guān)知識點,可參見:3-4節(jié) 決策樹|遞歸構(gòu)建決策樹|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

2.5.1篩選出現(xiàn)次數(shù)最多的分類標(biāo)簽名稱
如果數(shù)據(jù)集已經(jīng)處理了所有的屬性,但是類標(biāo)簽依然不是唯一的,此時我們需要決定如何定義該葉子節(jié)點,在這種情況下,我們通常會采用多數(shù)表決的方法決定該葉子節(jié)點的分類.

#篩選出現(xiàn)次數(shù)最多的分類標(biāo)簽名稱
def majorityCnt(classList):
    """
    majorityCnt(篩選出現(xiàn)次數(shù)最多的分類標(biāo)簽名稱)

    Args:
        classList 類別標(biāo)簽的列表
    Returns:
        sortedClassCount[0][0] 出現(xiàn)次數(shù)最多的分類標(biāo)簽名稱
        
    假設(shè)classList=['yes', 'yes', 'no', 'no', 'no']    
    """
    classCount={}
    for vote in classList:
        if vote not in classCount.keys():classCount[vote]= 0
        classCount[vote] += 1
        """
        print(classCount[vote])的結(jié)果為:
        {'yes': 1}
        {'yes': 2}
        {'yes': 2, 'no': 1}
        {'yes': 2, 'no': 2}
        {'yes': 2, 'no': 3}
        """
    sortedClassCount =sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    """
    print(sortedClassCount)的結(jié)果為:
    [('no', 3), ('yes', 2)]
    """
    return sortedClassCount[0][0]

測試代碼及其結(jié)果如下:

import trees
classList=['yes', 'yes', 'no', 'no', 'no']

majorityCnt(classList)
Out[45]: 'no'

2.5.2遞歸構(gòu)建決策樹
決策樹是一個遞歸算法,偽代碼如下:

def createBranch():
    檢測數(shù)據(jù)集中的所有數(shù)據(jù)的分類標(biāo)簽是否相同:
        If so return 類標(biāo)簽
        Else:
            尋找劃分?jǐn)?shù)據(jù)集的最好特征(劃分之后信息熵最小,也就是信息增益最大的特征)
            劃分?jǐn)?shù)據(jù)集
            創(chuàng)建分支節(jié)點
                for 每個劃分的子集
                    調(diào)用函數(shù) createBranch (創(chuàng)建分支的函數(shù))并增加返回結(jié)果到分支節(jié)點中
            return 分支節(jié)點

決策樹一般使用遞歸的方法生成。

  • 編寫遞歸函數(shù)有一個好習(xí)慣,就是先考慮結(jié)束條件。生成決策樹結(jié)束的條件有兩個:其一是劃分的數(shù)據(jù)都屬于一個類,其二是所有的特征都已經(jīng)使用了。在第二種結(jié)束情況中,劃分的數(shù)據(jù)有可能不全屬于一個類,這個時候需要根據(jù)多數(shù)表決準(zhǔn)則確定這個子數(shù)據(jù)集的分類。

  • 在非結(jié)束的條件下,首先選擇出信息增益最大的特征,然后根據(jù)其分類。分類開始時,記錄分類的特征到?jīng)Q策樹中,然后在特征標(biāo)簽集中刪除該特征,表示已經(jīng)使用過該特征。根據(jù)選中的特征將數(shù)據(jù)集分為若干個子數(shù)據(jù)集,然后將子數(shù)據(jù)集作為參數(shù)遞歸創(chuàng)建決策樹,最終生成一棵完整的決策樹

 # 創(chuàng)建樹的函數(shù)代碼       
def createTree(dataSet, labels):
    """
    createTree(創(chuàng)建樹)

    Args:
        dataSet 數(shù)據(jù)集
        labels  標(biāo)簽列表:標(biāo)簽列表包含了數(shù)據(jù)集中所有特征的標(biāo)簽。最后代碼遍歷當(dāng)前選擇
    Returns:
        myTree 標(biāo)簽樹:特征包含的所有屬性值,在每個數(shù)據(jù)集劃分上遞歸待用函數(shù)createTree(),
        得到的返回值將被插入到字典變量myTree中,因此函數(shù)終止執(zhí)行時,字典中將會嵌套很多代
        表葉子節(jié)點信息的字典數(shù)據(jù)。
    """
    #取得dataSet的最后一列數(shù)據(jù)保存在列表classList中
    classList = [example[-1] for example in dataSet]
    #如果classList中的第一個值在classList中的總數(shù)等于長度,也就是說classList中所有的值都一樣
    #也就等價于當(dāng)所有的類別只有一個時停止
    if classList.count(classList[0])==len(classList):
        return classList[0]
    #當(dāng)數(shù)據(jù)集中沒有特征可分時也停止
    if len(dataSet[0])==1:
        #通過majorityCnt()函數(shù)返回列表中最多的分類
        return majorityCnt(classList)
    #通過chooseBestFeatTopSplit()函數(shù)選出劃分?jǐn)?shù)據(jù)集最佳的特癥
    bestFeat = chooseBestFeatTopSplit(dataSet) 
    #最佳特征名 = 特征名列表中下標(biāo)為bestFeat的元素
    bestFeatLabel=labels[bestFeat]
    # 構(gòu)造樹的根節(jié)點,多級字典的形式展現(xiàn)樹,類似多層json結(jié)構(gòu)
    myTree={bestFeatLabel:{}}
    # 刪除del列表labels中的最佳特征(就在labels變量上操作)
    del(labels[bestFeat])
    #取出所有訓(xùn)練樣本最佳特征的值形成一個list
    featValues = [example[bestFeat] for example in dataSet]
    # 通過set函數(shù)將featValues列表變成集合,去掉重復(fù)的值
    uniqueVals = set(featValues)
    for value in uniqueVals:
        #復(fù)制類標(biāo)簽并將其存儲在新列表subLabels中
        subLabels = labels[:] 
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree

測試代碼及其結(jié)果如下:

import trees
myDat,labels=createDataSet()
myTree =createTree(myDat,labels)

myTree
Out[55]: {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
2.6使用文本注解繪制樹節(jié)點的函數(shù)代碼

將以下代碼錄入到treePlotter.py文件.

具體相關(guān)知識點,可參見:3-5節(jié) 決策樹|使用文本注解繪制樹節(jié)點|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

《機器學(xué)習(xí)實戰(zhàn)》書中,該部分的代碼有些混亂。重新構(gòu)造了代碼,創(chuàng)建一個類。其中,繪制最基本的樹節(jié)點是如下代碼:

#導(dǎo)入matplotlib的pyplot繪圖模塊并命名為plt
import matplotlib.pyplot as plt

# boxstyle是文本框類型,fc是邊框粗細(xì),sawtooth是鋸齒形
decisionNode = dict(boxstyle="sawtooth",fc="0.8")
leafNode = dict(boxstyle="round4",fc="0.8")

# arrowprops: 通過arrowstyle表明箭頭的風(fēng)格或種類。
arrow_args=dict(arrowstyle="<-")

# annotate 注釋的意思
#plotNode()函數(shù)繪制帶箭頭的注解,sub_ax:使用figure命令來產(chǎn)生子圖, node_text:節(jié)點的文字標(biāo)注,start_pt:箭頭起點位置(上一節(jié)點位置),end_pt:箭頭結(jié)束位置, node_type:節(jié)點屬性   
def plot_node(sub_ax, node_text, start_pt, end_pt, node_type):
    sub_ax.annotate(node_text,
        xy = end_pt, xycoords='axes fraction', 
        xytext = start_pt, textcoords='axes fraction',
        va='center', ha='center', bbox=node_type, arrowprops=arrow_args)

if __name__ == '__main__':
    fig = plt.figure(1, facecolor='white')
    #清空繪圖區(qū)
    fig.clf()
    axprops = dict(xticks=[], yticks=[]) #去掉坐標(biāo)軸
    sub_ax = plt.subplot(111, frameon=False, **axprops)
    #繪制節(jié)點
    plot_node(sub_ax, 'a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)
    plot_node(sub_ax, 'a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode)
    plt.show()

輸出結(jié)果如下:


2.7測試算法:使用決策樹執(zhí)行分類代碼

依靠訓(xùn)練數(shù)據(jù)構(gòu)造了決策樹之后,我們可以將它用于實際數(shù)據(jù)的分類。在執(zhí)行數(shù)據(jù)分類時,需要決策樹以及用于決策樹的標(biāo)簽向量。然后,程序比較測試數(shù)據(jù)與決策樹上的數(shù)值,遞歸執(zhí)行該過程直到進入葉子結(jié)點;最后將測試數(shù)據(jù)定義為葉子結(jié)點所屬的類型。

創(chuàng)建一個函數(shù)classify()錄入到trees.py文件.

具體相關(guān)知識點,可參見:3-6節(jié) 決策樹|測試和存儲分類器|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

def classify(inputTree, featLabels, testVec):
    # 因為并不知道按特征分類的先后順序,所以要寫一個分類器
    """classify(給輸入的節(jié)點,進行分類)

    Args:
        inputTree  是輸入的決策樹對象
        featLabels Feature是我們要預(yù)測的特征值的label,如:['throat','mustache']
        testVec    是要預(yù)測的特征值向量,如[0,0]
    Returns:
        classLabel 分類的結(jié)果值,需要映射label才能知道名稱
    """
    # 存儲決策樹第一個節(jié)點
    firstStr=list(inputTree.keys())[0]
    """
    myTree={'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
    labels=['no surfacing', 'flippers']
    
    print(firstStr)的結(jié)果為:
    'no surfacing'
    """
    # 將第一個節(jié)點的值存到secondDict字典中
    secondDict = inputTree[firstStr]
    """
    print(secondDict)的結(jié)果為:
    {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}
    """
    # 判斷根節(jié)點名稱獲取根節(jié)點在label中的先后順序,這樣就知道輸入的testVec怎么開始對照樹來做分類
    featIndex = featLabels.index(firstStr)
    """
    print(featIndex)的結(jié)果為:
    0
    """
    for key in secondDict.keys():
        """
        print(secondDict.keys())的結(jié)果為:
        dict_keys([0, 1])
        """
        if testVec[featIndex]==key:
            # 判斷分枝是否結(jié)束:判斷secondDict[key]是否是dict類型,如果是就遞歸,不是就輸出當(dāng)前鍵值為結(jié)果
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

測試代碼以及結(jié)果如下:

import trees
myDat, labels = trees.createDataSet()
myTree = trees.createTree(myDat, labels[:])

Out[35]:  trees.classify(myTree, labels, [1, 0])
'no'
Out[36]:  trees.classify(myTree, labels, [1, 1])
'yes'
2.8使用算法:決策樹的存儲

可以使用Python模塊pickle序列化對象,參見下面的程序。序列化對象可以在磁盤上保存對象,并在需要的時候讀取出來。

創(chuàng)建分別函數(shù)storeTree()/grabTree()錄入到trees.py文件.

具體相關(guān)知識點,可參見:3-6節(jié) 決策樹|測試和存儲分類器|機器學(xué)習(xí)實戰(zhàn)-學(xué)習(xí)筆記

def storeTree(inputTree,filename):
    import pickle
    # wb二進制寫模式
    fw = open(filename,"wb")
    pickle.dump(inputTree,fw)
    fw.close()
    
def grabTree(filename):
    import pickle
    # rb二進制文件讀取
    fr=open(filename,"rb")
    return pickle.load(fr)

測試代碼以及結(jié)果如下:

import trees
myDat, labels = trees.createDataSet()
myTree = trees.createTree(myDat, labels[:])

storeTree(myTree,'classifierStorage.txt')
grabTree('classifierStorage.txt')
Out[51]: {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容