數據挖掘之決策樹

一、引言                                                                     node


    決策樹是創建在信息論基礎之上,對數據進行分類挖掘的一種方法。其思想是,經過一批已知的訓練數據創建一棵決策樹,而後利用建好的決策樹,對數據進行預測。決策樹的創建過程能夠當作是數據規則的生成過程。因爲基於決策樹的分類方法結構簡單,自己就是人們可以理解的規則。其次,決策樹方法計算複雜度不大,分類效率高,可以處理大數據量的訓練集;最後,決策樹方法的分類精度較高,對噪聲數據有較好的健壯性,符合通常系統的要求。說了這麼多,可能還不是太瞭解決策樹,用一個例子來講明吧。算法

套用俗語,決策樹分類的思想相似於找對象。現想象一個女孩的母親要給這個女孩介紹男友,因而有了下面的對話:app

      女兒:多大年紀了?
      母親:26。
      女兒:長的帥不帥?
      母親:挺帥的。
      女兒:收入高不?
      母親:不算很高,中等狀況。
      女兒:是公務員不?
      母親:是,在稅務局上班呢。
      女兒:那好,我去見見。
函數

      這個女孩的決策過程就是典型的分類樹決策。至關於經過年齡、長相、收入和是否公務員對將男人分爲兩個類別:見和不見。假設這個女孩對男人的要求是:30歲如下、長相中等以上而且是高收入者或中等以上收入的公務員,那麼這個能夠用下圖表示女孩的決策邏輯:性能

0_1326015919pgCu.gif

    也就是說,對未知的選項均可以歸類到已知的選項分類類別中。學習

 

二、決策樹描述                                                              測試


     決策樹,又稱爲斷定樹,是一種相似二叉樹或多叉樹的樹結構。樹中的每一個非葉節點(包括根節點)對應於訓練樣本集中一個非類別屬性的測試,非葉節點的每一個分支對應屬性的一個測試結果,每一個葉子節點則表明一個類或類分佈。從根節點到葉子節點的一條路徑造成一條分類規則。決策樹能夠很方便地轉化爲分類規則,是一種很是直觀的分類模式表示形式。決策樹方法的起源是概念學習系統CLS,而後發展到ID3方法而爲高潮,最後演化爲能處理連續屬性的C4.5。有名的決策樹方法還有CART和Assistant。是應用最廣的概括推理算法之一。大數據

    決策樹學習是一種概括學習方法,當前國際上最有影響的示例學習方法首推的應當是R.Quinlan提出的ID3算法,其前身是概念學習系統CLS。ID3算法是全部可能決策樹空間中一種自頂向下、貪婪的搜索方法,以信息熵的降低速度爲選取測試屬性的標準,即在每一個節點選取還還沒有被用來劃分的具備最高信息增益的屬性做爲劃分標準,而後繼續這個過程,直到生成的決策樹能完美分類訓練樣例。ui

    在決策樹構造中,如何選取一個條件屬性做爲造成決策樹的節點是建樹的核心。通常狀況下,選取的屬性能最大程度反映訓練樣本集的分類特徵。ID3算法做爲決策構造中的經典算法,引入了信息論的方法,應用信息論中的熵的概念,採用信息增益做爲選擇屬性的標準來對訓練樣本集進行劃分,選取信息增益最大的屬性做爲當前節點。計算信息增益還要涉及三個概念:信息熵、信息增益和信息條件熵。this

信息熵

    信息熵也稱爲香農熵,是隨機變量的指望。度量信息的不肯定程度。信息的熵越大,信息就越不容易搞清楚。處理信息就是爲了把信息搞清楚,就是熵減小的過程。

1

計算香農熵的Python代碼爲:

#計算給定數據集的香農熵
def calcShannonEnt(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

信息條件熵

2

(以上公式爲屬性A的信息條件熵)

信息增益

用於度量屬性A下降樣本集合X熵的貢獻大小。信息增益越大,越適於對X分類。

3

三、ID3算法


ID3算法是決策樹算法的一種。想了解什麼是ID3算法以前,咱們得先明白一個概念:奧卡姆剃刀。

  • 奧卡姆剃刀(Occam's Razor, Ockham's Razor),又稱「奧坎的剃刀」,是由14世紀邏輯學家、聖方濟各會修士奧卡姆的威廉(William of Occam,約1285年至1349年)提出,他在《箴言書注》2卷15題說「切勿浪費較多東西,去作‘用較少的東西,一樣能夠作好的事情’。簡單點說,即是:be simple。

     ID3算法(Iterative Dichotomiser 3 迭代二叉樹3代)是一個由Ross Quinlan發明的用於決策樹的算法。這個算法即是創建在上述所介紹的奧卡姆剃刀的基礎上:越是小型的決策樹越優於大的決策樹(be simple簡單理論)。儘管如此,該算法也不是老是生成最小的樹形結構,而是一個啓發式算法。

    OK,從信息論知識中咱們知道,指望信息越小,信息增益越大,從而純度越高。ID3算法的核心思想就是以信息增益度量屬性選擇,選擇分裂後信息增益(很快,由下文你就會知道信息增益又是怎麼一回事)最大的屬性進行分裂。該算法採用自頂向下的貪婪搜索遍歷可能的決策樹空間。

     因此,ID3的思想即是:

  1. 自頂向下的貪婪搜索遍歷可能的決策樹空間構造決策樹(此方法是ID3算法和C4.5算法的基礎);
  2. 從「哪個屬性將在樹的根節點被測試」開始;
  3. 使用統計測試來肯定每個實例屬性單獨分類訓練樣例的能力,分類能力最好的屬性做爲樹的根結點測試(如何定義或者評判一個屬性是分類能力最好的呢?這即是下文將要介紹的信息增益,or 信息增益率)。
  4. 而後爲根結點屬性的每一個可能值產生一個分支,並把訓練樣例排列到適當的分支(也就是說,樣例的該屬性值對應的分支)之下。
  5. 重複這個過程,用每一個分支結點關聯的訓練樣例來選取在該點被測試的最佳屬性。

這造成了對合格決策樹的貪婪搜索,也就是算法從不回溯從新考慮之前的選擇。

尋找最佳屬性的Python代碼:

#選擇最好的數據集劃分方式
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0])-1
    baseEntropy = calcShannonEnt(dataSet)                  #計算香農熵
    bestInfoGain = 0.0;bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntroy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prop = len(subDataSet)/float(len(dataSet))
            newEntroy += prop * calcShannonEnt(subDataSet)  #計算條件信息熵
        infoGain = baseEntropy – newEntroy                   #信息增益
        if(infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i    
    return bestFeature

咱們經過一個具體的例子來說解ID3算法。主要經過兩個代碼文件實現。ID3決策樹算法的相關操做放在文件trees.py中

# -*- coding: utf-8 -*- 
'''
Created on 2015年7月27日

@author: pcithhb
'''
from math import log
import operator
#計算給定數據集的香農熵
def calcShannonEnt(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

#按照給定特徵劃分數據集
#dataSet:待劃分的數據集
#axis:劃分數據集的特徵--數據的第幾列
#value:須要返回的特徵值
def splitDataSet(dataSet,axis,value):
    retDataSet = []
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]           #獲取從第0列到特徵列的數據
            reducedFeatVec.extend(featVec[axis+1:])   #獲取從特徵列以後的數據
            retDataSet.append(reducedFeatVec)
    return retDataSet

#選擇最好的數據集劃分方式
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0])-1
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0;bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntroy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prop = len(subDataSet)/float(len(dataSet))
            newEntroy += prop * calcShannonEnt(subDataSet)
        infoGain = baseEntropy - newEntroy
        if(infoGain > bestInfoGain):
            bestInfoGain = infoGain
            bestFeature = i    
    return bestFeature
#
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classList.iteritems(),key=operator.itemgetter(1),reverse=True)#利用operator操做鍵值排序字典
    return sortedClassCount[0][0]

#建立樹的函數    
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)
    for value in uniqueVals:
        subLabels = labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
        
    return myTree

#建立數據集
def createDataSetFromTXT(filename):
    dataSet = []; labels = [] 
    fr = open(filename)
    linenumber=0
    for line in fr.readlines():
        line = line.strip()
        listFromLine = line.strip().split()
        lineset = []
        for cel in listFromLine:
            lineset.append(cel)
            
        if(linenumber==0):
            labels=lineset
        else:
            dataSet.append(lineset)
            
        linenumber = linenumber+1
    return dataSet,labels

 

決策樹計算圖形化相關操做放在treePlotter.py文件中

# -*- coding: utf-8 -*- 
'''
Created on 2015年7月27日

@author: pcithhb
'''
import matplotlib.pyplot as plt

decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle="<-")

#獲取葉節點的數目
def getNumLeafs(myTree):
    numLeafs = 0
    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':#測試節點的數據是否爲字典,以此判斷是否爲葉節點
            numLeafs += getNumLeafs(secondDict[key])
        else:   numLeafs +=1
    return numLeafs

#獲取樹的層數
def getTreeDepth(myTree):
    maxDepth = 0
    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':#測試節點的數據是否爲字典,以此判斷是否爲葉節點
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:   thisDepth = 1
        if thisDepth > maxDepth: maxDepth = thisDepth
    return maxDepth

#繪製節點
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 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, va="center", ha="center", rotation=30)

#繪製樹結構  
def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on
    numLeafs = getNumLeafs(myTree)  #this determines the x width of this tree
    depth = getTreeDepth(myTree)
    firstStr = myTree.keys()[0]     #the text label for this node should be this
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
    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

#建立決策樹圖形    
def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')
    fig.clf()
    axprops = dict(xticks=[], yticks=[])
    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))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
    plotTree(inTree, (0.5,1.0), '')
    plt.show()

假設在某一電站運行過程當中,判斷某類事故發生與不發生,而事故時由各類部件的故障特徵表現出來的,爲了簡單,咱們假設訓練集包含10條元素(存放於文件dataset.txt)。其中T1-T6表示各部件的值,Y1表示某種事故,1-表示發生,0-表示不發生

image

咱們先經過從文件加載到數據集中,而後計算數據集的信息熵

# -*- coding: utf-8 -*- 
'''
Created on 2015年7月27日

@author: pcithhb
'''
import trees
import treePlotter

if __name__ == '__main__':
    pass

myDat,labels = trees.createDataSetFromTXT("dataset.txt")

shan = trees.calcShannonEnt(myDat)
print shan

結果爲:0.881290899231

而後經過計算信息增益,獲得第一次最佳的分割屬性:

col = trees.chooseBestFeatureToSplit(myDat)
print col

結果爲:4,意味着最佳的分割屬性爲T5.

最後經過構建決策樹,

Tree = trees.createTree(myDat, labels)
print Tree

treePlotter.createPlot(Tree)

結果爲:{'T5': {'0.25': {'T1': {'0.5': '0', '0.75': '1'}}, '0.5': '0', '0.75': '0'}}

圖形化的結果圖爲:

figure_1

相關文章
相關標籤/搜索