Python 決策樹

1、決策樹算法

決策樹(decision tree)又叫判定樹,是基于樹結構對樣本屬性進行分類的分類算法。以二分類為例,當對某個問題進行決策時,會進行一系列的判斷或者“子決策”,將每個判定問題可以簡單理解為“開”或“關”,當達到一定條件的時候,就進行“開”或“關”的操作,操作就是對決策樹的這個問題進行的測試。所有的“子決策”都完成的時候,就構建出了一顆完整的決策樹。

一顆典型的決策樹包括根結點、若干個內部結點和若干個葉結點;葉結點對應著決策結果,其它每個結點對應于一個屬性測試;每個結點包含的樣本集合根據(jù)屬性測試的結果被劃分到子結點中;根結點包含樣本全集。

決策樹學習的目的在于產(chǎn)生一顆泛化能力強,即處理未見示例能力強的決策樹,基本流程遵循簡單直觀的“分而治之”(divide-and-conquer)策略。

一般而言,隨著劃分過程的不斷進行,我們希望決策樹的分支節(jié)點所包含的樣本盡可能屬于同一類別,即結點的“純度”(purity)越來越高。

  • 信息熵(information entropy)

    信息熵是度量樣本集合純度最常用的一種指標。在樣本集合D中,第k 類樣本所占比例為 ![](http://latex.codecogs.com/png.latex? \Large $p_k?$(k=1,2,...,|y|?))則D的信息熵為:![](http://latex.codecogs.com/png.latex? \Large $$ Ent(D)=-\sum_{k=1}^{|y|}p_klog_2p_k $$)

    Ent(D)的值越小,D的純度越高。

  • 信息增益(information gain)

    離散屬性a中有V個可能取值![](http://latex.codecogs.com/png.latex? \Large ${ a1,a2,...,a^V}$)使用a對樣本集D進行劃分,則產(chǎn)生V個分支結點,其中第v個分支結點包含了D中所有在屬性a上取值為av的樣本,記作DvDv信息熵即為Ent(Dv)

    考慮到不同分支結點包含樣本數(shù)不同,則給分支結點賦權![](http://latex.codecogs.com/png.latex? \Large $$ \frac{|D^v|}{|D|} $$)最后可計算出屬性a對樣本集D進行劃分所得到的信息增益:![](http://latex.codecogs.com/png.latex? \Large $$Gain(D,a)=Ent(D)-\sum_{v=1}{V}\frac{|Dv|}{|D|}Ent(D^v)$$)

    著名的ID3算法就是基于信息增益構建的。

    一般信息增益越大,則意味使用屬性a進行劃分所獲得的“純度提升”越大。

  • 信息增益率(gain ratio)

    信息增益準則對可數(shù)數(shù)目較多屬性有所偏好。這里考慮一個特殊情況,若分支數(shù)目就為樣本數(shù),則信息增益就為樣本集信息熵,此時信息增益亦越大,此時決策樹并不具有泛化能力,無法對新樣本進行有效預測。

    信息增益率就是為了解決上述問題而產(chǎn)生的。信息增益率的計算公式為:![](http://latex.codecogs.com/png.latex? \Large $$ Gain_ratio(D,a)=\frac{Gain(D,a)}{IV(a)} $$)其中![](http://latex.codecogs.com/png.latex? \Large $$ IV(a)=-\sum_{v=1}^V \frac{|Dv|}{|D|}log_2\frac{|Dv|}{|D|} $$)稱為屬性a的“固有值”(intrinsic value),屬性a的可能取值數(shù)目越多(即V越大),則IV(a)通常會越大。

    基于ID3算法改進的C4.5算法就是基于信息增益率構建的。

    需要注意的是,信息增益率對于可取值數(shù)目較少的屬性有所偏好。

  • 基尼指數(shù)(gini index)

    基尼指數(shù)是常用于CART決策樹的一種劃分準則,樣本集D的“純度”也可通過這個指數(shù)進行確定。基尼指數(shù):![](http://latex.codecogs.com/png.latex? \Large $$ Gini(D)=\sum_{k=1}{|y|}\sum_{k{'}\neq{k}}p_kp_{k{'}}=1-\sum_{k=1}{|y|}p_k^2 $$)

    它反映了從樣本集D中隨機抽取兩個樣本,其類別標記不一致的概率。

    因此Gini(D)越小,樣本集D純度越高。

    定義屬性a的基尼指數(shù)為:![](http://latex.codecogs.com/png.latex? \Large$$ Gini_index(D)=\sum_{v=1}{|V|}\frac{|Dv|}{|D|}Gini(D^v) $$)在候選屬性集合A中,選擇使得劃分后基尼指數(shù)最小的屬性做為最優(yōu)劃分屬性。

2、算法實現(xiàn)

本文采用以下樣本集(data.txt)實現(xiàn)決策樹

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

通過以下代碼導入樣本集數(shù)據(jù)

import pandas as pd#導入pandas庫
import numpy as np#導入numpy庫

data = pd.read_csv('data.txt', '\t', index_col='編號')
labels = list(data.columns)
dataSet = np.array(data).tolist()#處理讀入數(shù)據(jù)為list類型,方便后續(xù)計算

2.1、計算信息熵

導入樣本集,通過下面代碼實現(xiàn)根結點信息熵的計算

from math import log

def calcShannonEnt(dataSet):
    numEntries = len(dataSet)#計算樣本集的總樣本數(shù)量
    labelCounts = {}#設置一個空的dict類型變量
    for featVec in dataSet:#遍歷每行樣本集
        currentLabel = featVec[-1]#選取樣本集最后一列,設置為labelCounts變量的key值
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0#key對應value初始化
        labelCounts[currentLabel] += 1#累計value,即計算同類別樣本數(shù)
    shannonEnt = 0.0#初始化信息熵
    for key in labelCounts:
        prob = float(labelCounts[key]) / numEntries#計算頻率
        shannonEnt -= prob * log(prob, 2)#計算信息熵
    return shannonEnt
print(calcShannonEnt(dataSet))
0.9975025463691153

可以得到是否“好瓜”,即根結點的信息熵為0.9975025463691153

判斷是否“好瓜”的有{色澤,根蒂,敲聲,紋理,臍部,觸感}等6個子屬性,同時以“色澤”屬性為例,它有3個可能取值{青綠,烏黑,淺白},分別記為D1(色澤=青綠)、D2(色澤=烏黑)和D1(色澤=淺白)。
如果希望通過上面計算根結點的代碼計算不同子屬性信息熵,這里就需要對樣本集進行劃分,選擇相應的子屬性。這里通過下面的代碼實現(xiàn)樣本集的劃分。

def splitDataSet(dataSet, axis, value):
    #dataSet為樣本集
    #axis為子屬性下標,如0代表子屬性“色澤”
    #value為上述子屬性取值
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis + 1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet
newdataSet1=splitDataSet(dataSet, 0, '青綠')#將為“青綠”的樣本集合劃分出來
[['蜷縮', '濁響', '清晰', '凹陷', '硬滑', '是'], ['蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '是'], ['稍蜷', '濁響', '清晰', '稍凹', '軟粘', '是'], ['硬挺', '清脆', '清晰', '平坦', '軟粘', '否'], ['稍蜷', '濁響', '稍糊', '凹陷', '硬滑', '否'], ['蜷縮', '沉悶', '稍糊', '稍凹', '硬滑', '否']]
newdataSet2=splitDataSet(dataSet, 0, '烏黑')#將為“青綠”的樣本集合劃分出來
[['蜷縮', '沉悶', '清晰', '凹陷', '硬滑', '是'], ['蜷縮', '濁響', '清晰', '凹陷', '硬滑', '是'], ['稍蜷', '濁響', '稍糊', '稍凹', '軟粘', '是'], ['稍蜷', '濁響', '清晰', '稍凹', '硬滑', '是'], ['稍蜷', '沉悶', '稍糊', '稍凹', '硬滑', '否'], ['稍蜷', '濁響', '清晰', '稍凹', '軟粘', '否']]
newdataSet3=splitDataSet(dataSet, 0, '淺白')#將為“青綠”的樣本集合劃分出來
[['蜷縮', '濁響', '清晰', '凹陷', '硬滑', '是'], ['硬挺', '清脆', '模糊', '平坦', '硬滑', '否'], ['蜷縮', '濁響', '模糊', '平坦', '軟粘', '否'], ['稍蜷', '沉悶', '稍糊', '凹陷', '硬滑', '否'], ['蜷縮', '濁響', '模糊', '平坦', '硬滑', '否']]

由上面的劃分可以看出“色澤=青綠”的正類為3/6,反類為3/6,“色澤=烏黑”的正類為4/6,反類為2/6,“色澤=淺白”的正類為1/5,反類為4/5。
通過上文代碼計算每個取值的熵值:

print(calcShannonEnt(newdataSet1))
print(calcShannonEnt(newdataSet2))
print(calcShannonEnt(newdataSet3))
1.0
0.9182958340544896
0.7219280948873623

可以得到D1(色澤=青綠)的信息熵為:1.0,D2(色澤=烏黑)的信息熵:為0.9182958340544896,D1(色澤=淺白)的信息熵為:0.7219280948873623。

2.2、計算信息增益及最優(yōu)劃分屬性選取

由下面的代碼可以實現(xiàn)信息增益的計算

numFeatures = len(dataSet[0]) - 1#計算子屬性的數(shù)量
baseEntropy = calcShannonEnt(dataSet)#計算根結點信息熵
columns=['色澤','根蒂','敲聲','紋理','臍部','觸感']#子屬性
for i in range(numFeatures):
    featList = [example[i] for example in dataSet]
    uniqueVals = set(featList)
    newEntropy = 0.0
    for value in uniqueVals:
        #根據(jù)子屬性及其取值劃分樣本子集
        subDataSet = splitDataSet(dataSet, i, value)
        prob = len(subDataSet) / float(len(dataSet))#權值
        newEntropy += prob * calcShannonEnt(subDataSet)
        print(value,'的信息熵為:',calcShannonEnt(subDataSet))#不同取值的信息熵
    infoGain = baseEntropy - newEntropy#計算信息增益
    print(columns[i],'信息增益為:',infoGain)
    print('----------------------------------')
青綠 的信息熵為: 1.0
烏黑 的信息熵為: 0.9182958340544896
淺白 的信息熵為: 0.7219280948873623
色澤 信息增益為: 0.10812516526536531
----------------------------------
蜷縮 的信息熵為: 0.9544340029249649
硬挺 的信息熵為: 0.0
稍蜷 的信息熵為: 0.9852281360342516
根蒂 信息增益為: 0.14267495956679288
----------------------------------
清脆 的信息熵為: 0.0
濁響 的信息熵為: 0.9709505944546686
沉悶 的信息熵為: 0.9709505944546686
敲聲 信息增益為: 0.14078143361499584
----------------------------------
模糊 的信息熵為: 0.0
清晰 的信息熵為: 0.7642045065086203
稍糊 的信息熵為: 0.7219280948873623
紋理 信息增益為: 0.3805918973682686
----------------------------------
稍凹 的信息熵為: 1.0
平坦 的信息熵為: 0.0
凹陷 的信息熵為: 0.863120568566631
臍部 信息增益為: 0.28915878284167895
----------------------------------
硬滑 的信息熵為: 1.0
軟粘 的信息熵為: 0.9709505944546686
觸感 信息增益為: 0.006046489176565584
----------------------------------

一般某個子屬性的信息增益越大,意味著使用該屬性進行劃分的“純度提升”越大,下面通過改造上述代碼,選取最優(yōu)的劃分屬性。

# 基于信息增益選擇最優(yōu)劃分屬性
def chooseBestFeatureToSplit_Gain(dataSet):
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0#初始最優(yōu)信息增益
    bestFeature = -1#初始最優(yōu)子屬性
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)
        infoGain = baseEntropy - newEntropy
        if (infoGain > bestInfoGain):#選擇最優(yōu)子屬性
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature
chooseBestFeatureToSplit_Gain(dataSet)
3

上述結果表示在這個樣本集中,最優(yōu)的劃分子屬性是“紋理”,接下來根結點就通過該子屬性進行劃分。在該劃分之后,通過剩余的其它子屬性再進行信息增益的計算得到下個最優(yōu)劃分子屬性,迭代進行,直到全部子屬性變量完成。

2.3、基于信息增益率最優(yōu)劃分屬性選取

計算信息增益率代碼與上文計算信息增益的代碼類似,同時下文基于該原則選擇最優(yōu)子屬性的代碼中有體現(xiàn),這里不過多贅述。
同樣的,基于信息增益率原則也是選擇信息增益率最大的為最優(yōu)劃分結點,下面基于信息增益率劃分最優(yōu)子屬性。
由于本文采用的樣本集是離散型特征的樣本,這里的信息增益率和基尼指數(shù)選擇劃分屬性的實現(xiàn)并未針對連續(xù)型特征進行。后續(xù)會加以改進。

# 基于信息增益率選擇最優(yōu)劃分屬性
def chooseBestFeatureToSplit_GainRatio(dataSet):
    numFeatures = len(dataSet[0]) - 1
    baseEntropy = calcShannonEnt(dataSet)
    bestGainRatio = 0.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 0.0
        iv = 0.0#初始化“固有值”
        GainRatio = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            iv -= prob * log(prob, 2)#計算每個子屬性“固有值”
            newEntropy += prob * calcShannonEnt(subDataSet)
        infoGain = baseEntropy - newEntropy
        GainRatio = infoGain / iv#計算信息增益率
        if (GainRatio > bestGainRatio):#選擇最優(yōu)節(jié)點
            bestGainRatio = GainRatio
            bestFeature = i
    return bestFeature
chooseBestFeatureToSplit_GainRatio(dataSet)
3

上述結果表示在這個樣本集中,最優(yōu)的劃分子屬性同樣是“紋理”,接下來的劃分實現(xiàn)過程就由該結點開始。

2.4、計算基尼指數(shù)及最優(yōu)劃分屬性選取

以下代碼實現(xiàn)了基尼指數(shù)的計算。

# 計算基尼指數(shù)
def calcGini(dataSet):
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
        Gini = 1.0
        for key in labelCounts:
            prob = float(labelCounts[key]) / numEntries
            Gini -= prob * prob
    return Gini
calcGini(dataSet)#根結點基尼指數(shù)
0.49826989619377154

上述結果為根結點的基尼指數(shù)值。下面是通過基尼指數(shù)選擇根結點的子結點代碼實現(xiàn)。

# 基于基尼指數(shù)選擇最優(yōu)劃分屬性(只能對離散型特征進行處理)
def chooseBestFeatureToSplit_Gini(dataSet):
    numFeatures = len(dataSet[0]) - 1
    bestGini = 100000.0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newGiniIndex = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet) / float(len(dataSet))
            newGiniIndex += prob * calcGini(subDataSet)
        if (newGiniIndex < bestGini):
            bestGini = newGiniIndex
            bestFeature = i
    return bestFeature
chooseBestFeatureToSplit_Gini(dataSet)
3

上述結果同樣表示在這個樣本集中,最優(yōu)的劃分子屬性同樣是“紋理”,接下來的劃分實現(xiàn)過程也由該結點開始。

2.5、創(chuàng)建樹

決策樹的創(chuàng)建過程是迭代進行的,下面的代碼是根據(jù)子屬性的取值數(shù)目大小來進行每層根結點的選擇的實現(xiàn)過程。即簡單理解為選擇“下一個根結點”。

import operator

# 選擇下一個根結點
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0#初始化子屬性取值的計數(shù)
        classCount[vote] += 1#累計
    #根據(jù)第二個域,即dict的value降序排序
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse = True)
    return sortedClassCount[0][0]#返回子屬性取值

下面就是創(chuàng)建樹的過程。

# 創(chuàng)建樹
def createTree(dataSet, labels, chooseBestFeatureToSplit):
    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)#最優(yōu)劃分屬性選取
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel: {}}#初始化樹
    del (labels[bestFeat])#刪除已劃分屬性
    featValues = [example[bestFeat] for example in dataSet]#初始化下層根結點
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:]
        #遍歷實現(xiàn)樹的創(chuàng)建
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels, chooseBestFeatureToSplit)
    return myTree
chooseBestFeatureToSplit=chooseBestFeatureToSplit_Gain#根據(jù)信息增益創(chuàng)建樹
# chooseBestFeatureToSplit=chooseBestFeatureToSplit_GainRatio#根據(jù)信息增益率創(chuàng)建樹
# chooseBestFeatureToSplit=chooseBestFeatureToSplit_Gini#根據(jù)基尼指數(shù)創(chuàng)建樹
myTree = createTree(dataSet, labels, chooseBestFeatureToSplit)
myTree
{'紋理': {'模糊': '否',
  '清晰': {'根蒂': {'硬挺': '否',
    '稍蜷': {'色澤': {'烏黑': {'觸感': {'硬滑': '是', '軟粘': '否'}}, '青綠': '是'}},
    '蜷縮': '是'}},
  '稍糊': {'觸感': {'硬滑': '否', '軟粘': '是'}}}}

我們需要將劃分好的決策樹加載出來進行分類,這里定義一個分類的代碼如下。

#分類測試器
def classify(inputTree, featLabels, testVec):
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]#下一層樹
    featIndex = featLabels.index(firstStr)#將Labels標簽轉換為索引
    for key in secondDict.keys():
        if testVec[featIndex] == key:#判斷是否為與分支節(jié)點相同,即向下探索子樹
            if type(secondDict[key]).__name__ == 'dict':
                #遞歸實現(xiàn)
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel#返回判斷結果
classify(myTree, ['色澤','根蒂','敲聲','紋理','臍部','觸感'],['烏黑','稍蜷','沉悶','稍糊','稍凹','硬滑'])
'否'#得到分類結果為'否'

由于決策樹的訓練是一件耗時的工作,當碰到較大的樣本集的時候,為了下次使用的方便,這里將決策樹保存起來。
這里通過pickle模塊存儲和加載決策樹。

#保存樹
def storeTree(inputTree,filename):
    import pickle
    fw = open(filename,'wb+')
    pickle.dump(inputTree,fw)
    fw.close()
 
#加載樹
def grabTree(filename):
    import pickle
    fr = open(filename,'rb')
    return pickle.load(fr)
storeTree(myTree,'myTree.txt')#保存到mytree.txt文件中
grabTree('myTree.txt')#加載保存的決策樹
{'紋理': {'模糊': '否',
  '清晰': {'根蒂': {'硬挺': '否',
    '稍蜷': {'色澤': {'烏黑': {'觸感': {'硬滑': '是', '軟粘': '否'}}, '青綠': '是'}},
    '蜷縮': '是'}},
  '稍糊': {'觸感': {'硬滑': '否', '軟粘': '是'}}}}

2.6、繪制樹

上文已經(jīng)將樹創(chuàng)建出來了,下面可以通過這段代碼進行決策樹的繪制

import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif']=['SimHei']#支持圖像顯示中文

#boxstyle為文本框的類型,sawtooth是鋸齒形,fc是邊框線粗細
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
#定義箭頭的屬性
arrow_args = dict(arrowstyle="<-")

#繪制節(jié)點
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)

#獲取葉節(jié)點的數(shù)量
def getNumLeafs(myTree):
    #定義葉子節(jié)點數(shù)目
    numLeafs = 0
    #獲取myTree的第一個鍵
    firstStr = list(myTree.keys())[0]
    #劃分第一個鍵對應的值,即下層樹
    secondDict = myTree[firstStr]
    #遍歷secondDict
    for key in secondDict.keys():
        #如果secondDict[key]為字典,則遞歸計算其葉子節(jié)點數(shù)量
        if type(secondDict[key]).__name__ == 'dict':
            numLeafs += getNumLeafs(secondDict[key])
        else:
            #是葉子節(jié)點累計
            numLeafs +=1
    return numLeafs

#獲取樹的層數(shù)
def getTreeDepth(myTree):
    #定義樹的層數(shù)
    maxDepth = 0
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        #如果secondDict[key]為字典,則遞歸計算其層數(shù)
        if type(secondDict[key]).__name__ == 'dict':
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:
            thisDepth = 1
        #當前層數(shù)大于最大層數(shù)
        if thisDepth > maxDepth:
            maxDepth = thisDepth
    return maxDepth

#繪制節(jié)點文本
def plotMidText(cntrPt, parentPt, txtString):
    #求中間點橫坐標
    xMid = (parentPt[0] - cntrPt[0])/2.0 + cntrPt[0]
    #求中間點縱坐標
    yMid = (parentPt[1] - cntrPt[1])/2.0 + cntrPt[1]
    #繪制結點
    createPlot.ax1.text(xMid, yMid, txtString)

#繪制決策樹
def plotTree(myTree, parentPt, nodeTxt):
    numLeafs = getNumLeafs(myTree)#葉子節(jié)點數(shù)
#    depth = getTreeDepth(myTree)#層數(shù)
    firstStr = list(myTree.keys())[0]#第一層鍵
    #計算坐標,
    cntrPt=(plotTree.xOff + (1.0+float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
    #繪制中間節(jié)點
    plotMidText(cntrPt, parentPt, nodeTxt)
    #繪制決策樹節(jié)點
    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':
            #遞歸繪制決策樹
            plotTree(secondDict[key], cntrPt, str(key))
        else:
            #葉子節(jié)點橫坐標
            plotTree.xOff = plotTree.xOff+1.0/plotTree.totalW
            #繪制葉子節(jié)點
            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

def createPlot(inTree):
    #定義圖像界面
    fig = plt.figure(1, facecolor='white', figsize = (10,8))
    #初始化
    fig.clf()
    #定義橫縱坐標軸
    axprops = dict(xticks = [],yticks = [])
    #初始化圖像
    createPlot.ax1 = plt.subplot(111, frameon = False,**axprops)
    #寬度
    plotTree.totalW = float(getNumLeafs(inTree))
    #高度
    plotTree.totalD = float(getTreeDepth(inTree))
    #起始橫坐標
    plotTree.xOff = -0.5/plotTree.totalW
    #起始縱坐標
    plotTree.yOff = 1.0
    #繪制決策樹
    plotTree(inTree,(0.5,1.0), '')
    #顯示圖像
    plt.show()
createPlot(myTree)

2.7、使用Scikit - Learn庫實現(xiàn)決策樹

Scikit-Learn庫是Python一個第三方的機器學習模塊,它基于NumPy,Scipy,Matplotlib的基礎構建的,是一個高效、簡便的數(shù)據(jù)挖掘、數(shù)據(jù)分析的模塊。在后續(xù)的學習過程中,我也通過其進行機器學習的實例實現(xiàn)。
下面是基于信息熵簡單得到該決策樹的代碼實現(xiàn)。

import numpy as np
import pandas as pd
from sklearn import tree

'''由于在此庫中需要使用數(shù)值進行運算,這里需要對樣本集進行處理'''
data = pd.read_csv('data1.txt', '\t', index_col='Idx')
labels = np.array(data['label'])
dataSet = np.array(data[data.columns[:-1]])

clf = tree.DecisionTreeClassifier(criterion = 'entropy')#參數(shù)criterion = 'entropy'為基于信息熵,‘gini’為基于基尼指數(shù)
clf.fit(dataSet, labels)#訓練模型

with open("tree.dot", 'w') as f:#將構建好的決策樹保存到tree.dot文件中
    f = tree.export_graphviz(clf,feature_names = np.array(data.columns[:-1]), out_file = f)

這里我們需要通過Graphviz將保存好的tree.dot文件繪制出來,在這之前,需要安裝Graphviz這個軟件。
安裝步驟如下:

  • 打開http://www.graphviz.org/Download_windows.php ,下載此文件;

  • 打開下載好的文件graphviz-2.38,一直next安裝完成;


  • 打開系統(tǒng)屬性,編輯環(huán)境變量,添加graphviz-2.38的安裝目錄下的\bin文件夾路徑到新變量中,確定即可;



  • 打開命令提示符,輸入:dot -version查看環(huán)境變量是否設置成功;


  • 命令提示符轉到tree.dot文件所在位置,鍵入以下命令:dot tree.dot -Tpng -o tree.png,將決策樹文件轉化為圖像文件。


    經(jīng)過上述過程,我們得到了決策樹圖像。


上述代碼只是基于Scikit-Learn庫簡單實現(xiàn)決策樹,不能體現(xiàn)這個機器學習庫的強大之處,后續(xù)會介紹如何使用這個庫訓練模型、測試模型等。

2.8、不足及改進

本文研究了基于信息增益、信息增益率和基尼指數(shù)三種劃分準則進行決策樹的構造,通過“西瓜”樣本集實現(xiàn)了其決策樹的構造。但是決策樹的構造過程中還存在過擬合,即生成的樹需要剪枝的情況,以及對于連續(xù)變量的處理問題,在下一篇文章中,將針對這兩個問題改進相應的代碼,從而使構建出的決策樹更為嚴謹準確。

3、參考資料

[1] 周志華. 機器學習.清華大學出版社,2016
[2] Peter Harrington. 機器學習實戰(zhàn). 人民郵電出版社,2013
[3] http://m.blog.csdn.net/article/details?id=51057311
[4] http://www.tuicool.com/articles/uUry2uq

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 決策樹理論在決策樹理論中,有這樣一句話,“用較少的東西,照樣可以做很好的事情。越是小的決策樹,越優(yōu)于大的決策樹”。...
    制杖灶灶閱讀 5,928評論 0 25
  • (圖片來源網(wǎng)絡) 1. 章節(jié)主要內容 決策樹是機器學習的一類常見算法,其核心思想是通過構建一個樹狀模型來對新樣本進...
    閃電隨筆閱讀 5,285評論 3 14
  • 積跬步以致千里,積怠惰以致深淵 注:本篇文章在整理時主要參考了 周志華 的《機器學習》。 主要內容 決策樹是機器學...
    指尖上的魔術師閱讀 1,429評論 0 5
  • 這里開始機器學習的筆記記錄。今天的這篇是一個分類方法--決策樹。 決策樹優(yōu)點:計算復雜度不高,輸出結果易于理解,對...
    七號蘿卜閱讀 6,483評論 0 18
  • 那天還是悶熱的八月底,我坐在首都機場的登機口,聽著母親的囑咐。我估摸著這應該是疲憊又狀態(tài)百出的一天,我放棄了夏天最...
    可歸閱讀 396評論 2 2