決策樹之ID3算法及其Python實現

決策樹之ID3算法及其Python實現

  • 主要內容
    • 決策樹背景知識
    • 決策樹一般構建過程
    • ID3算法分裂屬性的選擇
    • ID3算法流程及其優缺點分析
    • ID3算法Python代碼實現

1. 決策樹背景知識
  決策樹是數據挖掘中最重要且最常用的方法之一,主要應用於數據挖掘中的分類和預測。決策樹是知識的一種呈現方式,決策樹中從頂點到每個結點的路徑都是一條分類規則。決策樹算法最先基於信息論發展起來,經過幾十年發展,目前常用的算法有:ID3、C4.5、CART算法等。

2. 決策樹一般構建過程
  構建決策樹是一個自頂向下的過程。樹的生長過程是一個不斷把數據進行切分細分的過程,每一次切分都會產生一個數據子集對應的節點。從包含所有數據的根節點開始,根據選取分裂屬性的屬性值把訓練集劃分成不同的數據子集,生成由每個訓練數據子集對應新的非葉子節點。對生成的非葉子節點再重複以上過程,直到滿足特定的終止條件,停止對數據子集劃分,生成數據子集對應的葉子節點,即所需類別。測試集在決策樹構建完成後檢驗其性能。如果性能不達標,我們需要對決策樹算法進行改善,直到達到預期的性能指標。
  注:分裂屬性的選取是決策樹生產過程中的關鍵,它決定了生成的決策樹的性能、結構。分裂屬性選擇的評判標準是決策樹算法之間的根本區別。

3. ID3算法分裂屬性的選擇——信息增益
  屬性的選擇是決策樹算法中的核心。是對決策樹的結構、性能起到決定性的作用。ID3算法基於信息增益的分裂屬性選擇。基於信息增益的屬性選擇是指以信息熵的下降速度作爲選擇屬性的方法。它以的信息論爲基礎,選擇具有最高信息增益的屬性作爲當前節點的分裂屬性。選擇該屬性作爲分裂屬性後,使得分裂後的樣本的信息量最大,不確定性最小,即熵最小。
  信息增益的定義爲變化前後熵的差值,而熵的定義爲信息的期望值,因此在瞭解熵和信息增益之前,我們需要了解信息的定義。
  信息:分類標籤xi 在樣本集 S 中出現的頻率記爲 p(xi) ,則 xi 的信息定義爲:log2p(xi)
  分裂之前樣本集的熵:E(S)=Ni=1p(xi)log2p(xi) ,其中 N 爲分類標籤的個數。
  通過屬性A分裂之後樣本集的熵:EA(S)=mj=1|Sj||S|E(Sj) ,其中 m 代表原始樣本集通過屬性A的屬性值劃分爲 m 個子樣本集,|Sj| 表示第j個子樣本集中樣本數量,|S| 表示分裂之前數據集中樣本總數量。
  通過屬性A 分裂之後樣本集的信息增益:InfoGain(S,A)=E(S)EA(S)
  注:分裂屬性的選擇標準爲:分裂前後信息增益越大越好,即分裂後的熵越小越好。

4. ID3算法
  ID3算法是一種基於信息增益屬性選擇的決策樹學習方法。核心思想是:通過計算屬性的信息增益來選擇決策樹各級節點上的分裂屬性,使得在每一個非葉子節點進行測試時,獲得關於被測試樣本最大的類別信息。基本方法是:計算所有的屬性,選擇信息增益最大的屬性分裂產生決策樹節點,基於該屬性的不同屬性值建立各分支,再對各分支的子集遞歸調用該方法建立子節點的分支,直到所有子集僅包括同一類別或沒有可分裂的屬性爲止。由此得到一棵決策樹,可用來對新樣本數據進行分類。

  • ID3算法流程:
    (1) 創建一個初始節點。如果該節點中的樣本都在同一類別,則算法終止,把該節點標記爲葉節點,並用該類別標記。
    (2) 否則,依據算法選取信息增益最大的屬性,該屬性作爲該節點的分裂屬性。
    (3) 對該分裂屬性中的每一個值,延伸相應的一個分支,並依據屬性值劃分樣本。
    (4) 使用同樣的過程,自頂向下的遞歸,直到滿足下面三個條件中的一個時就停止遞歸。
      A、待分裂節點的所有樣本同屬於一類。
      B、訓練樣本集中所有樣本均完成分類。
      C、所有屬性均被作爲分裂屬性執行一次。若此時,葉子結點中仍有屬於不同類別的樣本時,選取葉子結點中包含樣本最多的類別,作爲該葉子結點的分類。

  • ID3算法優缺點分析
    優點:構建決策樹的速度比較快,算法實現簡單,生成的規則容易理解。
    缺點:在屬性選擇時,傾向於選擇那些擁有多個屬性值的屬性作爲分裂屬性,而這些屬性不一定是最佳分裂屬性;不能處理屬性值連續的屬性;無修剪過程,無法對決策樹進行優化,生成的決策樹可能存在過度擬合的情況。


5. ID3算法Python代碼實現

# -*- coding: utf-8 -*-
__author__ = 'zhihua_oba'

import operator
from numpy import *
from math import log

#文件讀取
def file2matrix(filename, attribute_num):   #傳入參數:文件名,屬性個數
    fr = open(filename)
    arrayOLines = fr.readlines()
    numberOfLines = len(arrayOLines)    #統計數據集行數(樣本個數)
    dataMat = zeros((numberOfLines, attribute_num))
    classLabelVector = []   #分類標籤
    index = 0
    for line in arrayOLines:
        line = line.strip() #strip() 刪除字符串中的'\n'
        listFromLine = line.split() #將一個字符串分裂成多個字符串組成的列表,不帶參數時以空格進行分割,當代參數時,以該參數進行分割
        dataMat[index, : ] = listFromLine[0:attribute_num]    #讀取數據對象屬性值
        classLabelVector.append(listFromLine[-1])   #讀取分類信息
        index += 1
    dataSet = []    #數組轉化成列表
    index = 0
    for index in range(0, numberOfLines):
        temp = list(dataMat[index, :])
        temp.append(classLabelVector[index])
        dataSet.append(temp)
    return dataSet

#劃分數據集
def splitDataSet(dataSet, axis, value):
    retDataSet = []
    for featvec in dataSet: #每行
        if featvec[axis] == value:  #每行中第axis個元素和value相等   #刪除對應的元素,並將此行,加入到rerDataSet
            reducedFeatVec = featvec[:axis]
            reducedFeatVec.extend(featvec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

#計算香農熵  #計算數據集的香農熵 == 計算數據集類標籤的香農熵
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

#根據香農熵,選擇最優的劃分方式    #根據某一屬性劃分後,類標籤香農熵越低,效果越好
def chooseBestFeatureToSplit(dataSet):
    baseEntropy = calcShannonEnt(dataSet)   #計算數據集的香農熵
    numFeatures = len(dataSet[0])-1
    bestInfoGain = 0.0  #最大信息增益
    bestFeature = 0    #最優特徵
    for i in range(0, numFeatures):
        featList = [example[i] for example in dataSet]  #所有子列表(每行)的第i個元素,組成一個新的列表
        uniqueVals = set(featList)
        newEntorpy = 0.0
        for value in uniqueVals:    #數據集根據第i個屬性進行劃分,計算劃分後數據集的香農熵
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntorpy += prob*calcShannonEnt(subDataSet)
        infoGain = baseEntropy-newEntorpy   #劃分後的數據集,香農熵越小越好,即信息增益越大越好
        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(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    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[:]
        subDataSet = splitDataSet(dataSet, bestFeat, value)
        myTree[bestFeatLabel][value] = createTree(subDataSet, subLabels)
    return myTree

#測試算法:使用決策樹,對待分類樣本進行分類
def classify(inputTree, featLabels, testVec):   #傳入參數:決策樹,屬性標籤,待分類樣本
    firstStr = inputTree.keys()[0]  #樹根代表的屬性
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)  #樹根代表的屬性,所在屬性標籤中的位置,即第幾個屬性
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

def main():
    dataSet = file2matrix('test_sample.txt', 4)
    labels = ['attr01', 'attr02', 'attr03', 'attr04']
    labelsForCreateTree = labels[:]
    Tree = createTree(dataSet, labelsForCreateTree )
    testvec = [2, 3, 2, 3]
    print classify(Tree, labels, testvec)
if __name__ == '__main__':
     main()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章