[數據挖掘]決策樹ID3和C45


數據分類

數據分類就是建立模型把具有某種共同屬性或特征的數據歸並在一起,通過其類別的屬性或特征來對數據進行區別。這個模型稱為分類器,預測分類的(無序,離散)類標號。

數據分類通常分為兩個過程,學習階段(模型構建)和分類階段(預測類標號)。例如下圖我們預測客戶是否會購買計算機


a) 學習過程 b) 分類過程


決策樹歸納

決策樹(Decision Tree)是在已知各種情況發生概率的基礎上,通過構成決策樹來求取凈現值的期望值大於等於零的概率,評價項目風險,判斷其可行性的決策分析方法,是直觀運用概率分析的一種圖解法。由於這種決策分支畫成圖形很像一棵樹的枝干,故稱決策樹。在機器學習中,決策樹是一個預測模型,他代表的是對象屬性與對象值之間的一種映射關系。Entropy = 系統的凌亂程度,使用算法ID3, C4.5和C5.0生成樹算法使用熵。這一度量是基於信息學理論中熵的概念。
決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的測試,每個分支代表一個測試輸出,每個葉節點代表一種類別。
分類樹(決策樹)是一種十分常用的分類方法。他是一種監管學習,所謂監管學習就是給定一堆樣本,每個樣本都有一組屬性和一個類別,這些類別是事先確定的,那么通過學習得到一個分類器,這個分類器能夠對新出現的對象給出正確的分類。這樣的機器學習就被稱之為監督學習。

決策樹(Decision Tree)是在已知各種情況發生概率的基礎上,通過構成決策樹來求取凈現值的期望值大於等於零的概率,評價項目風險,判斷其可行性的決策分析方法,是直觀運用概率分析的一種圖解法。由於這種決策分支畫成圖形很像一棵樹的枝干,故稱決策樹。在機器學習中,決策樹是一個預測模型,他代表的是對象屬性與對象值之間的一種映射關系。Entropy = 系統的凌亂程度,使用算法ID3, C4.5和C5.0生成樹算法使用熵。這一度量是基於信息學理論中熵的概念。
決策樹是一種樹形結構,其中每個內部節點表示一個屬性上的測試,每個分支代表一個測試輸出,每個葉節點代表一種類別。

分類樹(決策樹)是一種十分常用的分類方法。他是一種監管學習,所謂監管學習就是給定一堆樣本,每個樣本都有一組屬性和一個類別,這些類別是事先確定的,那么通過學習得到一個分類器,這個分類器能夠對新出現的對象給出正確的分類。這樣的機器學習就被稱之為監督學習。



特征選擇

特征選擇問題希望選取對訓練數據具有良好分類能力的特征,這樣可以提高決策樹學習的效率。如果利用一個特征進行分類的結果與隨機分類的結果沒有很大差別,則稱這個特征是沒有分類能力的(對象是否喜歡打游戲應該不會成為關鍵特征吧,也許也會……)。為了解決特征選擇問題,找出最優特征,先要介紹一些信息論里面的概念。 
1.熵(entropy) 
熵是表示隨機變量不確定性的度量。設XX是一個取有限個值的離散隨機變量,其概率分布為

P(X=xi)=pi,i=1,2,,nP(X=xi)=pi,i=1,2,⋯,n
則隨機變量的熵定義為
H(X)=i=1npilogpiH(X)=−∑i=1npilog⁡pi
另外, 0log0=00log⁡0=0 ,當對數的底為2時,熵的單位為bit;為e時,單位為nat。 
熵越大,隨機變量的不確定性就越大。從定義可驗證
0H(p)logn

2.條件熵(conditional entropy) 
設有隨機變量(X,Y)(X,Y),其聯合概率分布為

P(X=xi,Y=yj)=pij,i=1,2,,n;j=1,2,,mP(X=xi,Y=yj)=pij,i=1,2,⋯,n;j=1,2,…,m
條件熵 H(Y|X)H(Y|X) 表示在已知隨機變量 XX 的條件下隨機變量 YY 的不確定性。隨機變量X給定的條件下隨機變量 YY 的條件熵 H(Y|X)H(Y|X) ,定義為 XX 給定條件下 YY 的條件概率分布的熵對 XX 的數學期望
H(Y|X)=i=1npiH(Y|X=xi)H(Y|X)=∑i=1npiH(Y|X=xi)
這里, pi=P(X=xi),i=1,2,,n.

pi=P(X=xi),i=1,2,⋯,n.


3.信息增益(information gain) 
信息增益表示得知特征XX的信息而使得類YY的信息的不確定性減少的程度。特征AA對訓練數據集D的信息增益g(D,A)g(D,A),定義為集合DD的經驗熵H(D)H(D)與特征AA給定條件下DD的經驗條件熵H(D|A)H(D|A)之差,即

g(D,A)=H(D)H(D|A)g(D,A)=H(D)−H(D|A)
這個差又稱為互信息。信息增益大的特征具有更強的分類能力。 
根據信息增益准則的特征選擇方法是:對訓練數據集(或子集)計算其每個特征的信息增益,選擇信息增益最大的特征。 
計算信息增益的算法如下: 
輸入:訓練數據集 DD 和特征 AA ; 
輸出:特征 AA 對訓練數據集 DD 的信息增益 g(D,A)g(D,A)
(1)計算數據集 DD 的經驗熵 H(D)H(D)
H(D)=k=1K|Ck||D|log2|Ck||D|H(D)=−∑k=1K|Ck||D|log2⁡|Ck||D|
(2)計算特征 AA 對數據集 DD 的經驗條件熵 H(D|A)H(D|A)
H(D|A)=i=1n|Di||D|H(Di)=i=1n|Di||D|k=1K|Dik||Di|log2|Dik||Di|H(D|A)=∑i=1n|Di||D|H(Di)=−∑i=1n|Di||D|∑k=1K|Dik||Di|log2⁡|Dik||Di|

(3)計算信息增益
g(D,A)=H(D)H(D|A)g(D,A)=H(D)−H(D|A)

相對應地,以信息增益作為划分訓練數據集的特征的算法稱為ID3算法,后面會講述。

4.信息增益比(information gain ratio) 
特征AA對訓練數據集DD的信息增益比gR(D,A)gR(D,A)定義為其信息增益g(D,A)g(D,A)與訓練數據集DD關於特征AA的值的熵HA(D)HA(D)之比,即

gR(D,A)=g(D,A)HA(D)gR(D,A)=g(D,A)HA(D)
其中, HA(D)=ni=1|Di||D|log2|Di||D|HA(D)=−∑i=1n|Di||D|log2⁡|Di||D| nn 是特征 A

A取值的個數。

相應地,用信息增益比來選擇特征的算法稱為C4.5算法。


決策樹的生成 
此處主要介紹兩種決策樹學習的生成算法:ID3和C4.5。 
1.ID3算法 
ID3算法由Ross Quinlan發明,建立在“奧卡姆剃刀”的基礎上:越是小型的決策樹越優於大的決策樹(be simple簡單理論)。ID3算法中根據信息增益評估和選擇特征,每次選擇信息增益最大的特征作為判斷模塊建立子結點。ID3算法可用於划分標稱型數據集,沒有剪枝的過程,為了去除過度數據匹配的問題,可通過裁剪合並相鄰的無法產生大量信息增益的葉子節點(例如設置信息增益閥值)。使用信息增益的話其實是有一個缺點,那就是它偏向於具有大量值的屬性。就是說在訓練集中,某個屬性所取的不同值的個數越多,那么越有可能拿它來作為分裂屬性,而這樣做有時候是沒有意義的,另外ID3不能處理連續分布的數據特征,於是就有了C4.5算法。CART算法也支持連續分布的數據特征。 
算法步驟如下: 
這里寫圖片描述 
Python實現:

def chooseBestFeatureToSplitByID3(dataSet):
    ''' 選擇最好的數據集划分方式 :param dataSet:數據集 :return: 划分結果 '''
    numFeatures = len(dataSet[0]) - 1  # 最后一列yes分類標簽,不屬於特征向量
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0
    bestFeature = -1
    for i in range(numFeatures):  # 遍歷所有特征
        infoGain = calcInformationGain(dataSet, baseEntropy, i)     # 計算信息增益
        if (infoGain > bestInfoGain):  # 選擇最大的信息增益
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature  # 返回最優特征對應的維度

def majorityCnt(classList):
    ''' 采用多數表決的方法決定葉結點的分類 :param: 所有的類標簽列表 :return: 出現次數最多的類 '''
    classCount={}
    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) # 排序
    return sortedClassCount[0][0]

def createTree(dataSet,labels):
    ''' 創建決策樹 :param: dataSet:訓練數據集 :return: 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 = chooseBestFeatureToSplitByID3(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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

這里使用Python語言的字典類型存儲樹的信息,簡單方便。當然也可以定義一個新的數據結構存儲樹。 
2.可視化 
程序如下:

import matplotlib.pyplot as plt
import tree
# 定義文本框和箭頭格式
decisionNode = dict(boxstyle="round4", color='#3366FF')  # 定義判斷結點形態
leafNode = dict(boxstyle="circle", color='#FF6633')  # 定義葉結點形態
arrow_args = dict(arrowstyle="<-", color='g')  # 定義箭頭

#計算葉結點數
def getNumLeafs(myTree):
    numLeafs = 0
    firstStr = list(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 = list(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):
    numLeafs = getNumLeafs(myTree)  # 計算寬與高
    depth = getTreeDepth(myTree)
    firstStr = list(myTree.keys())[0]      
    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 # 減少y偏移
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':   
            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=[])
    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()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

我們使用貸款申請樣本數據表作為測試(使用找對象數據也沒問題,反正隨自己編):

# 導入數據
def createDataSet():
    dataSet = [['youth', 'no', 'no', 1, 'refuse'],
               ['youth', 'no', 'no', '2', 'refuse'],
               ['youth', 'yes', 'no', '2', 'agree'],
               ['youth', 'yes', 'yes', 1, 'agree'],
               ['youth', 'no', 'no', 1, 'refuse'],
               ['mid', 'no', 'no', 1, 'refuse'],
               ['mid', 'no', 'no', '2', 'refuse'],
               ['mid', 'yes', 'yes', '2', 'agree'],
               ['mid', 'no', 'yes', '3', 'agree'],
               ['mid', 'no', 'yes', '3', 'agree'],
               ['elder', 'no', 'yes', '3', 'agree'],
               ['elder', 'no', 'yes', '2', 'agree'],
               ['elder', 'yes', 'no', '2', 'agree'],
               ['elder', 'yes', 'no', '3', 'agree'],
               ['elder', 'no', 'no', 1, 'refuse'],
               ]
    labels = ['age', 'working?', 'house?', 'credit_situation']
    return dataSet, labels
# 測試代碼
if __name__ == "__main__":
    myDat, labels = tree.createDataSet()
    myTree = tree.createTree(myDat, labels)
    print(myTree)
    createPlot(myTree)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

繪圖效果如下: 
這里寫圖片描述

3.C4.5算法 
C4.5算法用信息增益率來選擇屬性,繼承了ID3算法的優點。並在以下幾方面對ID3算法進行了改進:

  • 克服了用信息增益選擇屬性時偏向選擇取值多的屬性的不足;
  • 在樹構造過程中進行剪枝;
  • 能夠完成對連續屬性的離散化處理;
  • 能夠對不完整數據進行處理。

C4.5算法產生的分類規則易於理解、准確率較高;但效率低,因樹構造過程中,需要對數據集進行多次的順序掃描和排序。也是因為必須多次數據集掃描,C4.5只適合於能夠駐留於內存的數據集。在實現過程中,C4.5算法在結構與遞歸上與ID3完全相同,區別只在於選取決決策特征時的決策依據不同,二者都有貪心性質:即通過局部最優構造全局最優。以下是算法步驟:這里寫圖片描述 
Python實現如下:

def chooseBestFeatureToSplitByC45(dataSet):
    ''' 選擇最好的數據集划分方式 :param dataSet: :return: 划分結果 '''
    numFeatures = len(dataSet[0]) - 1  # 最后一列yes分類標簽,不屬於特征變量
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGainRate = 0.0
    bestFeature = -1
    for i in range(numFeatures):  # 遍歷所有維度特征
        infoGainRate = calcInformationGainRatio(dataSet, baseEntropy, i)    # 計算信息增益比 
        if (infoGainRate > bestInfoGainRate):  # 選擇最大的信息增益比
            bestInfoGainRate = infoGainRate
            bestFeature = i
    return bestFeature  # 返回最佳特征對應的維度
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

其他部分都相同,只是改變一下調用選擇特征的函數。畫出來的決策樹並沒有變化:這里寫圖片描述

5.執行分類 
構造了決策樹之后,我們就可以將它用於實際數據的分類,在執行分類時,需要輸入決策樹和用於構造樹的所有類標簽向量。然后,程序比較測試數據與決策樹上的數值,遞歸執行該過程直到進入葉結點;最后將測試數據定義為葉結點所屬的類型。Python實現如下:

def classify(inputTree,featLabels,testVec):
    ''' 利用決策樹進行分類 :param: inputTree:構造好的決策樹模型 :param: featLabels:所有的類標簽 :param: testVec:測試數據 :return: 分類決策結果 '''
    firstStr = inputTree.keys()[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)
    key = testVec[featIndex]
    valueOfFeat = secondDict[key]
    if isinstance(valueOfFeat, dict): 
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat
    return classLabel
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

測試實例如下: 
這里寫圖片描述

Java 代碼實現  https://github.com/wujiazhong/DataMining  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM