【數據科學系統學習】機器學習算法 # 西瓜書學習記錄 [10] 決策樹實踐

本篇內容爲《機器學習實戰》第 3 章決策樹部分程序清單。所用代碼爲 python3。python


決策樹
優勢:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,能夠處理不相關特徵數據。
缺點:可能會產生過分匹配問題。
適用數據類型:數值型和標稱型

在構造決策樹時,咱們須要解決的第一個問題就是,當前數據集上哪一個特徵在劃分數據分類時起決定性做用。爲了找到決定性的特徵,劃分出最好的結果,咱們必須評估每一個特徵。完成測試以後,原始數據集就被劃分爲幾個數據子集。這些數據子集會分佈在第一個決策點的全部分支上。若是某個分支下的數據屬於同一類型,則無需進一步對數據集進行分割。若是數據子集內的數據不屬於同一類型,則須要重複劃分數據子集的過程。劃分數據子集的算法和劃分原始數據集的方法相同,直到全部具備相同類型的數據均在一個數據子集內。算法

建立分支的僞代碼函數createBranch()以下所示:app

檢測數據集中的每一個子項是否屬於同一分類:
    If so return 類標籤
    Else
        尋找劃分數據集的最好特徵
        劃分數據集
        建立分支節點
            for 每一個劃分的子集
                調整函數createBranch()並增長返回結果到分支節點中
        return 分支節點

下面咱們採用量化的方法來斷定如何劃分數據,咱們如下圖所示的數據集爲例:機器學習

clipboard.png

程序清單 3-1 計算給定數據集的香農熵

'''
Created on Sep 16, 2018

@author: yufei
'''

# 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

    # 以 2 爲底求對數
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries
        shannonEnt -= prob * log(prob, 2)
    return shannonEnt

"""
獲得數據集
"""
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

在 python 提示符下,執行代碼並獲得結果:函數

>>> import trees
>>> myDat, labels = trees.createDataSet()
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.calcShannonEnt(myDat)
0.9709505944546686

程序清單 3-2 按照給定特徵劃分數據集

# 參數:待劃分的數據集、劃分數據集的特徵、須要返回的特徵的值
def splitDataSet(dataSet, axis, value):
    # 爲了避免修改原始數據集,建立一個新的列表對象
    retDataSet = []
    for featVec in dataSet:
        # 將符合特徵的數據抽取出來
        # 當咱們按照某個特徵劃分數據集時,就須要將全部符合要求的元素抽取出來
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

測試函數splitDataSet(),在 python 提示符下,執行代碼並獲得結果:學習

>>> myDat, labels = trees.createDataSet()
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.splitDataSet(myDat, 0, 0)
[[1, 'no'], [1, 'no']]

程序清單 3-3 選擇最好的數據集劃分方式

"""
函數功能:選擇特徵,劃分數據集,計算得出最好的劃分數據集的特徵

數據集需知足:
一、數據是一種由列表元素組成的列表,且全部的列表元素都要具備相同的數據長度
二、數據的最後一列或每一個實例的最後一個元素是當前實例的類別標籤
"""
def chooseBestFeatureToSplit(dataSet):
    # 斷定當前數據集包含多少特徵屬性
    numFeatures = len(dataSet[0]) - 1
    # 計算整個數據集的原始香農熵,即最初的無序度量值
    baseEntropy = calcShannonEnt(dataSet)

    bestInfoGain = 0.0
    bestFeatures = -1

    # 遍歷數據集中的全部特徵
    for i in range(numFeatures):
        # 建立惟一的分類標籤列表,將數據集中全部第 i 個特徵值寫入這個 list 中
        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):
            bestInfoGain = infoGain
            bestFeatures = i
    return bestFeatures

在 python 提示符下,執行代碼並獲得結果:測試

>>> myDat, labels = trees.createDataSet()
>>> trees.chooseBestFeatureToSplit(myDat)
0
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]

代碼運行結果告訴咱們,第 0 個特徵是最好的用於劃分數據集的特徵。也就是說第一個特徵是 1 的放在一個組,第一個特徵是 0 的放在另外一個組。由於這個數據集比較簡單,咱們直接觀察能夠看到第一種劃分更好地處理了相關數據。spa

下面咱們會介紹如何將上述實現的函數功能放在一塊兒,構建決策樹。code


程序清單 3-4 建立樹的函數代碼

"""
使用分類名稱的列表,建立數據字典
返回出現次數最多的分類名稱
"""
import operator
def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote in classList:
            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 的列表變量,包含了數據集的全部類標籤
    classList = [example[-1] for example in dataSet]
    # 遞歸函數的第一個中止條件:全部類標籤徹底相同,則直接返回該類標籤
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 遞歸函數的第二個中止條件:使用完全部特徵,仍然不能將數據集劃分紅僅包含惟一類別的分組
    # 因爲沒法簡單地返回惟一的類標籤,這裏遍歷完全部特徵時使用 majorityCnt 函數返回出現次數最多的類別
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)

    # 當前數據集選取的最好特徵存儲在變量 bestFeat 中,獲得列表包含的全部屬性值
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    # 字典變量 myTree 存儲了樹的全部信息
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])

    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    # 遍歷當前選擇特徵包含的全部屬性值
    for value in uniqueVals:
        # 複製類標籤,將其存儲在新列表變量 subLabels 中
        # 在python語言中,函數參數是列表類型時,參數是按照引用方式傳遞的
        # 爲了保證每次調用函數 createTree 時不改變原始列表的內容
        subLabels = labels[:]
        # 在每一個數據集劃分上遞歸的調用函數 createTree()
        # 獲得的返回值被插入字典變量 myTree 中
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
    return myTree

在 python 提示符下,執行代碼並獲得結果:對象

>>> myDat, labels = trees.createDataSet()
>>> myTree = trees.createTree(myDat, labels)
>>> myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

最後獲得的變量myTree包含了不少表明樹結構信息的嵌套字典。這棵樹包含了 3 個葉子節點以及 2 個判斷節點,形狀以下圖所示:

clipboard.png


不足之處,歡迎指正。

相關文章
相關標籤/搜索