決策樹--ID3

分類:IT技術 時間:2016-10-09

機器學習實戰第三章學習筆記


使用決策樹分類的一般步驟:準備數據à構建決策樹(該決策樹可以被存成文件,便於二次利用)à使用決策樹決策

【準備數據】

有n組數據,每一組有m個元素,前m-1個元素為分類標簽即特征,最後一個元素為決策結果。

【構建決策樹】

首先選擇最優的分類特征

輸入:數據集dataSet,假設該數據集有k個特征,1個決策結果。

方法:從第一個特征到第k個特征,每次使用一個特征去劃分數據集dataSet成dataSet1、dataSet2、dataSet3…。挨個計算每一個子數據集的熵值n1、n2、n3…,並求和(dataSet1/dataset)*n1+(dataSet2/dataset)*n2+(dataSet3/dataset)*n3+….。總的熵值最小的那個特征便是最優分類特征。

ps:所有代碼的依賴包

from math import log
import operator
import matplotlib.pyplot as plt
import numpy


[具體代碼如下]

########
##計算輸入數據集的熵值
########
def calcShannonEnt(dataSet):   #####計算數據集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)
    return shannonEnt


使用key-value格式的數據結構創建決策樹。

輸入:數據集dataSet,  標簽序列

方法:從數據集dataSet中選擇最優分類特征feature1。以該特征值為key創建空的鍵值對。通過在數據集中計算該feature1所在列有多少個取值,就可以知道以該feature1為父結點的子節點有多少個。假設該feature1有i個子節點值分別為temp1、temp2、temp3…tempi,則循環i次,每次從當前數據集dataSet中刪除feature1所在列值為tmpi行,並以余下的數據作為一個新的數據集創建子樹。【遞歸調用】

[具體代碼如下]

########
##從數據集dataSet中刪除第axis列,值為value的那行,並返回剩余的數據集
########
def splitDateSet(dataSet, axis, value):     ###從數據集合dataSet中選出第axis+1列內容等於value的數據行,且選出的數據不再包含第axis+1列的內容
    retDataSet = []
    for featVec in dataSet :
        if featVec[axis] == value:
            reduceFeatVec = featVec[:axis]   #featVec[:axis]選去featVec中從第0到第axis列的內容
            reduceFeatVec.extend(featVec[axis+1:])    #featVec[axis+1:]選去featVec中第axis+1列之後的內容
            retDataSet.append(reduceFeatVec)
    return retDataSet


########
##從輸入的數據集中挑選取分類效果最好的特征
########
def chooseBestFeatureToSplit(dataSet):  ##選擇最優的數據劃分方式
    numFeatures = len(dataSet[0]) - 1    ###計算每組數據中除去最後一個數據類型標簽標示外總共有多少個特征,這些特征描述該組數據
    baseEntropy = calcShannonEnt(dataSet)   #判斷數據集合dataSet之中是否有多種類型的數據,即判斷數據集的熵值是否為零
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):      #從第1列開始(即第一個特征,每一行各有一個特征值),每次選取一個特征去劃分數據集合
        featList =  [example[i] for example in dataSet]         ###i表示特征所在列的索引,featList存放該列的每一個具體的值
        uniqueVals = set(featList)                      ####刪除重復的值
        newEntorpy = 0.0
        for value in uniqueVals:
            subDataSet = splitDateSet(dataSet, i, value)    ####從dataSet中篩選出第i列值為value的那些行,並把這些行劃分為同一類
            prob = len(subDataSet)/float(len(dataSet))     ###計算當前劃分出的子數據集合所擁有的數據行數占總數據行的比值
            newEntorpy += prob * calcShannonEnt(subDataSet)    ####用上一步的比值乘以子數據集合的熵,newEntorpy中存儲每個子數據集合的熵的累加值
        infoGain = baseEntropy - newEntorpy            ##由於熵的值為負數,為了比較大小,取整
        if infoGain > bestInfoGain :           ####上面的步驟是從第i列下手,計算在此列中劃分的各子數據集熵值的累加和
            bestInfoGain = infoGain           ####使用冠軍法選取最小的那個熵值下的特征列
        bestFeature = i  ##返回最優分類特征的索引值
    return bestFeature



def majorityCnt(classList):     ###決定葉子節點的分類
    classCount={}       ###存儲classList中每個類標簽出現的頻率
    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)
    ##operator模塊提供itemgetter函數用於獲取對象的哪些維的數據或者哪些key對應的數據,參數就是索引號或key值.可以設置多個索引號或key值。要註意,operator.itemgetter函數獲取的不是值,而是定義了一個函數,通過該函數作用到對象上才能獲取值
    ##operator.itemgetter(1)獲取索引號為1的對象的內容

    return sortedClassCount

########
##創建決策樹
########
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])  ##將已經使用的特征從特征序列中刪除
    featValues = [example[bestFeat] for example in dataSet]  ##獲取該特征下的各種取值,當該特征作為父節點時,其不同的取值就是不同的子節點
    uniqueVals = set(featValues)  ##去除重復的值,將不同的值存在一個臨時的list中,該list的長度即子節點的數目
    for value in uniqueVals:
        subLabels = labels[:]  ##將去除已用特征後剩余的特征作為一個子標簽序列
        print bestFeat
        print value
        #splitDateSet(dataSet, bestFeat, value)從當前所用的數據集中刪除第bestFeat列,值為value的那行數據,並返回余下的數據集
        myTree[bestFeatLabel][value] = createTree(splitDateSet(dataSet, bestFeat, value), subLabels)  ##以當前所用特征為key,並將子樹作為其value值
    return myTree


 

 

數據集的香農熵:利用計算公式計算,熵的值越大數據集中數據類型越多,熵值為零說明該數據集中就只有一個一類數據。【利用熵值來判斷當前數據集中的數據類型是否單一,如果不單一則繼續劃分】

 

決策樹的特點:標簽都是非葉子結點且都有兩個子結點,在python中以該標簽為key的value值都是一個字典(dict);決策結果都是葉子結點。

 

決策樹的實際存儲模型:



【利用決策樹判斷】

假如使用的決策樹為:


決策樹中的標簽為:no surfacing,  flippers

待判斷的輸入為:[1, 0]    【說明:輸入[1,0]表示nosurfacing的value值為1,flippers的value值為0 ;其它輸入依次類推斷】

在實際寫代碼使用決策樹進行決策時一般會有三個輸入:“樹模型”、“用到的標簽”、“標簽符合情況”。其中標簽符合情況中的元素個數與用到的標簽的個數相同,且從前到後依次匹配。例如:用到的標簽[a, b, c, d, e, f ...];標簽符合情況[1, 0 , 0 , 0 , 1, 0 …]就表示與a:1;b:0;c:0;d:0;e:1;f:0。

代碼實現的過程中我們會從決策樹的根結點開始,根據決策樹上的實際標簽值到“用到的標簽”中找出該標簽,之後再到“標簽符合情況”中找到具體的符合情況。

[具體代碼如下]

########
##使用決策樹進行分類
########
def classify(inputTree,featLabels,testVec):  #inputTree:決策樹; featLabels:進行決策所用到的標簽(該標簽與決策樹中的非葉子節點中的key相同); testVec:標簽實際符合情況
    firstStr = inputTree.keys()[0]  #獲得根結點處的標簽(key)
    secondDict = inputTree[firstStr]  #獲得根結點處的標簽符合情況(value)
    featIndex = featLabels.index(firstStr)  #查找當前標簽(key)在實際輸入標簽中的位置
    print featIndex
    key = testVec[featIndex]  #根據實際標簽,查找當前標簽的實際符合情況
    valueOfFeat = secondDict[key]  #根據符合情況獲得子節點的內容,可能是決策結果也可能是一個以標簽為key的字典(dict)
    if isinstance(valueOfFeat, dict):  #判斷子節點是葉子節點還是非葉子節點
        classLabel = classify(valueOfFeat, featLabels, testVec)  #是一個dict繼續決策
    else: classLabel = valueOfFeat  #是個決策結果,返回該結果
    return classLabel



-----------------------------------------------------------------------------------------------【畫出具體的樹型圖】-----------------------------------------------------------------------------------

[假設輸入數據為]文件名:lenses.txt
young<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
young<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>soft
young<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
young<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>hard
young<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
young<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>soft
young<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
young<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>hard
pre<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
pre<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>soft
pre<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
pre<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>hard
pre<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
pre<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>soft
pre<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
pre<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>myope<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>hard
presbyopic<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>no<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>soft
presbyopic<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>reduced<span style="white-space:pre">	</span>no lenses
presbyopic<span style="white-space:pre">	</span>hyper<span style="white-space:pre">	</span>yes<span style="white-space:pre">	</span>normal<span style="white-space:pre">	</span>no lenses
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle='<-')

def plotNode(nodeTxt, centerPt, parentPt, nodeType):         ###執行實際的畫圖工作
    createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',
    xytext = centerPt, textcoords= 'axes fraction',
    va = "center", ha = "center", bbox = nodeType, arrowprops = arrow_args)

#
# def createPlot():                     ###獲得畫圖的地方
#     fig = plt.figure(1, facecolor='white')  ###matplotlib.pyplot.figure    Creates a new figure.  num:an id for figure; facecolor:the background color
#     fig.clf()  ###fig的類型是matplotlib.figure.Figure    fig.clf():Clear the figure
#     createPlot.ax1 = plt.subplot(111, frameon=False)  ##subplot(m,n,p)==subplot(mnp) if m,n,p<10,作用是將多個figure放到一個平面中。其中m,n表示將一個figure切割成m行n列。p表示序號即第幾塊
#     print type(createPlot.ax1)
#     ### print type(createPlot.ax1)   打印的內容是<class 'matplotlib.axes.AxesSubplot'>
#     plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)  ###執行實際的繪圖工作
#     plotNode('a left node', (0.8, 0.1), (0.3, 0.8), leafNode)
#     plt.show()


########
##獲得決策樹的葉子節點個數
########
def getNumleafs(myTree):              ###獲得樹的葉子節點個數
    numLeafs = 0
    firstStr = myTree.keys()[0]     ###myTree.keys()的內容雖然是一個字符串,但是類型卻是list;myTree.keys()[0]獲取list中第一個索引代表的內容
    secondDict = myTree[firstStr]    ###secondDict是一個字典類型,secondDict.key()提取字典secondDict中所有的key
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ =='dict':      ###一個字典類型對象的__name__屬性的值為dict;一個list類型對象的__name__屬性的值為list;該處用於判斷一個secondDict中key所代表的value值是一個子字典還是字符串,如果是字符串則是葉子節點
            numLeafs += getNumleafs(secondDict[key])
        else:
            numLeafs += 1
    return numLeafs


########
##獲得決策樹的層數
########
def getTreeDepth(myTree):              ###獲得樹的層數
    maxDepth = 0
    thisDepth = 0
    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict':      ###一個字典類型對象的__name__屬性的值為dict;一個list類型對象的__name__屬性的值為list;該處用於判斷當前節點是否是葉節點
            thisDepth = 1+ getNumleafs(secondDict[key])
        else:
            thisDepth += 1
        if thisDepth > maxDepth:     ###使用冠軍法獲得最大層數
            maxDepth = thisDepth
    return maxDepth


def retrieveTree(i):              ###定義樹的結構
    listOfTree = [ {'no surfacing': {0:'no', 1:{ 'flippers': {0:'no', 1:"yes"} } } },
                   {'no surfacing': {0:'no', 1:{ 'flippers': {0: {'head': {0:'no', 1:'yes'}}, 1:'no'}}}}
                  ]
    return listOfTree[i]


def plotMidText(cntrPt, parentPt, txtString):  #parentPt:父母節點的坐標; cntrPt:子節點的坐標; txtString:在父節點和子節點的連線上填充的內容
    xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]  #xMid:要填充內容在x軸上的坐標
    yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]  #yMid:要填充內容在y軸上的坐標
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)

def plotTree(myTree, parentPt, nodeTxt):  ###myTree:要畫出圖形的樹; parentPt:根結點的坐標   
    numLeafs = getNumleafs(myTree)  #獲得樹的葉節點數
    depth = getTreeDepth(myTree)  #獲得樹的層數
    firstStr = myTree.keys()[0]     #獲得樹的根節點
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
    print cntrPt
    plotMidText(cntrPt, parentPt, nodeTxt)
    plotNode(firstStr, cntrPt, parentPt, decisionNode)
    secondDict = myTree[firstStr]
    plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
            plotTree(secondDict[key],cntrPt,str(key))        #recursion
        else:   #it's a leaf node print the leaf node
            plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
#if you do get a dictonary you know it's a tree, and the first element will be another dict

def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')
    fig.clf()
    axprops = dict(xticks=[], yticks=[])          ###class dict in module __builtin__ ; dict是一個字典類型的構造函數
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)    #no ticks
    #createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
    plotTree.totalW = float(getNumleafs(inTree))         ###定義一個變量totalW,該變量屬於plotTree,每次訪問該變量時需要寫成plotTree.totalW
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
    plotTree(inTree, (0.5,1.0), 'tree')
    plt.show()

if __name__ == "__main__":
    import treePlotter
    fr = open('lenses.txt')
    lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
    lenses = [inst.strip().split('\t') for inst in fr.readlines()]
    lensesTree = createTree(lenses, lensesLabels)
    createPlot(lensesTree)







Tags:

文章來源:


ads
ads

相關文章
ads

相關文章

ad