第三章 決策樹 3.1決策樹構造

http://cn.akinator.com/  “神燈猜名人”這個遊戲很多人都玩過吧,問很多問題,然後逐步猜測你想的名人是誰。決策樹的工作原理與這個類似,輸入一系列數據,然後給出遊戲答案。決策樹也是最經常使用的數據挖掘算法。書上給了一個流程圖決策樹,很簡單易懂。


這裏,橢圓形就是判斷模塊,方塊就是終止模塊。kNN 方法也可以完成分類任務,但是缺點是無法給出數據的內在含義。決策樹的主要優勢就在於數據形式容易理解。

==============================================================================

決策樹

優點:計算複雜度不高,輸出結果容易理解,對中間值缺失不敏感,可以處理不相關特徵數據。

缺點:可能會產生過度匹配問題。

適用數據類型:數值型和標稱型。


僞代碼:

creatBranch():
	if so return 類標籤:
    else:
        尋找劃分數據集的最好特徵
        劃分數據集
        創建分支節點
            for 每個劃分的子集
                調用函數 creatBranch() 並增加返回結果到分支節點中
        return 分支節點

可以看出這是一個遞歸函數,在裏面直接調用了自己。

==============================================================================

決策樹的一般流程:

  • 收集數據:可以使用任何方法
  • 準備數據:樹構造算法只適用於標稱型數據,因此數值型數據必須離散化。
  • 分析數據:可以使用任何方法,構造樹完成以後,我們應該檢查圖形是否符合預期。
  • 訓練算法:構造樹的數據結構。
  • 測試算法:使用經驗樹計算錯誤率。
  • 使用算法:此步驟可以適用於任何監督學習算法,而使用決策樹可以更好地理解數據的內在含義。

一些決策樹算法採用二分法,我們不用這種方法。我們可能會遇到更多的選項,比如四個,然後創立四個不同分支。本書將使用 ID3 算法劃分數據集。


==============================================================================

信息增益:

劃分數據集的大原則是:將無序的數據變得更加有序。

在劃分數據集之前之後信息發生的變化稱爲信息增益

集合信息的度量方式稱爲香農熵或者簡稱爲熵。熵定義爲信息的期望值。公式略過不表。


給一段代碼,計算給定數據集的熵:

# -*- coding:utf-8 -*-
from math import log
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) # 以 2 爲底求對數
    return shannonEnt


然後自己利用 createDataSet() 函數來得到35頁表 3-1 的魚類鑑定數據集。


def createDataSet():
    dataSet = [[1,1,'yes'],
                [1,1,'yes'],
                [1,0,'no'],
                [0,1,'no'],
                [0,1,'no']]
    labels = ['no surfacing','flippers']
    return dataSet, labels

 最後來運行一下試試,我們建立一個 run_trees.py

# -*- coding:utf-8 -*-
# run_trees.py
import trees
myDat,labels = trees.createDataSet()
print myDat
print trees.calcShannonEnt(myDat)


熵越多高,說明混合數據越多。這裏添加一個 “maybe” 分類,表示可能爲魚類。

測試:

# -*- coding:utf-8 -*-
import trees
myDat,labels = trees.createDataSet()
print myDat
print trees.calcShannonEnt(myDat)
print '*********************************'
myDat[0][2] = 'maybe' # 0 指的是dataSet第一個[],-1 指[]裏面倒數第一個元素
print myDat
print trees.calcShannonEnt(myDat)

結果:


====================================================================================================

3.1.2 劃分數據集

分類算法除了需要測量信息熵,還要劃分數據集。

我們對每個特徵劃分數據集的結果計算一次信息熵,然後判斷按照哪個特徵劃分數據集是最好的方式。

按照給定特徵劃分數據集,代碼接着 trees.py 寫:

def splitDataSet(dataSet, axis, value):
    retDataSet= [] # 創建新的 list 對象
    for featVec in dataSet:
        if featVec[axis] == value:
            # 抽取
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

注意 append() 和 extend() 的區別:

    >>>a = [1,2,3]
    >>>b = [4,5,6]
    >>>a.append(b)
    >>>a
    [1,2,3,[4,5,6]]


    >>>a = [1,2,3]
    >>>a.extend(b)
    >>>a
    [1,2,3,4,5,6]


=============================================================================================

現在可以在前面的簡單樣本數據上測試函數 splitDataSet()

在 run_trees.py 裏面加些代碼:

# -*- coding:utf-8 -*-
# run_trees.py
import trees
myDat,labels = trees.createDataSet()
print '>>> myDat'
print myDat
print '>>> trees.calcShannonEnt(myDat)'
print trees.calcShannonEnt(myDat)
print '*********************************'
myDat[0][2] = 'maybe' # 0 指的是dataSet第一個[],-1 指[]裏面倒數第一個元素
print '>>> myDat'
print myDat
print '>>> trees.calcShannonEnt(myDat)'
print trees.calcShannonEnt(myDat)
print '*********************************'
reload(trees)
myDat,labels = trees.createDataSet()
print '>>> myDat'
print myDat
print '>>> trees.splitDataSet(myDat,0,1)'
print trees.splitDataSet(myDat,0,1)
print '>>> trees.splitDataSet(myDat,0,0)'
print trees.splitDataSet(myDat,0,0)
結果如下:


=======================================================================

接下來我們要遍歷整個數據集,循環計算香農熵和 splitDataSet() 函數,找到最好的特徵劃分方式。熵計算將會告訴我們如何劃分數據集是最好的組織方式。

加代碼:

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) # set 是一個集合
        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):
            # 計算最好的收益
            bestInfoGain = infoGain
            bestFeature = i
    return bestFeature

這段代碼就是選取特徵,劃分數據集,計算得出最好的劃分數據集的特徵。

在 run_trees.py 裏面加些代碼:

print '*********************************'
reload(trees)
print '>>> myDat, labels = trees.createDataSet()'
myDat, labels = trees.createDataSet()
print '>>> trees.chooseBestFeatureToSplit(myDat)'
print trees.chooseBestFeatureToSplit(myDat)
print '>>> myDat'
print myDat

結果如下:


代碼的意義在於,告訴我們第0個特徵(不浮出水面是否可以生存)是最好的用於劃分數據集的特徵。


如果不相信這個結果,可以修改 calcShannonEnt(dataSet) 函數來測試不同特徵分組的輸出結果。

===============================================================================

3.1.3 遞歸構建決策樹

從數據集構造決策樹算法所需要的子功能模塊,原理如下:得到原始數據集,然後基於最好的屬性值劃分數據集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的數據集劃分。第一次劃分後,數據將被鄉下傳遞到樹分支的下一個節點,在這個節點上,我們可以再次劃分數據。我們可以採用遞歸的原則處理數據集。

在添加代碼前,在 trees.py 頂部加上一行代碼:

import operator

然後添加:

def majorityCnt(classList):
    classCount = {} # 創建鍵值爲 classList 中唯一值的數據字典
    for vote in classList:
        if vote not in classCount.keys():classCount[vote] = 0
        classCount[vote] += 1 # 儲存了 classList 中每個類標籤出現的頻率
    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:
        # 爲了保證每次調用函數 createTree() 時不改變原始列表類型,使用新變量 subLabels 代替原始列表
        subLabels = labels[:] # 這行代碼複製了類標籤,並將其存儲在新列表變量 subLabels 中
        myTree[bestFeatLabel][value] = createTree(splitDataSet\
                                (dataSet,bestFeat,value),subLabels)
    return myTree

下一步開始創建樹,使用 字典 類型來保存樹的信息,當然也可以聲明特殊的數據類型儲存樹,但是這裏沒有必要。

當前數據集選取的最好特徵存儲在變量 bestFeat 中,得到列表包含的所有屬性值。

現在運行代碼,在 run_trees.py 裏面添加:

print '*********************************'
reload(trees)
print '>>> myDat, labels = trees.createDataSet()'
myDat, labels = trees.createDataSet()
print '>>> myTree = trees.  createTree(myDat, labels)'
myTree = trees.  createTree(myDat, labels)
print '>>> myTree'
print myTree
結果:

變量 myTree 包含了很多代表樹結構信息的嵌套字典。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章