Machine Learning In Action 學習筆記之 決策樹

決策樹是一種常見的機器學習算法,也是很容易理解的。顧名思義,它是基於樹結構進行決策的。

如下圖所示:
在這裏插入圖片描述
正方形表示 判斷模塊 , 橢圓形表示 終止模塊,一棵決策樹包含一個根結點、若干個內部結點和若干個葉結點,葉節點表示決策結果。


1. 決策樹的構造

構造決策樹,需要解決的第一個問題就是,當前數據集上 哪個特徵 在劃分數據分類時其決定性作用(就是選擇哪個特徵進行劃分)

創建分支的僞代碼createBranch()如下所示:
在這裏插入圖片描述
這是一個 遞歸 函數。

決策數算法的流程如下:

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

1.1 信息增益

爲了進行數據的劃分,即選擇通過什麼特徵進行劃分,採取的一個原則爲:將無序的數據變得有序,即希望決策樹的分支節點所包含的樣本儘可能屬於同一類別,即結點的 “純度” 越來越高。

  • 信息:符號 xix_i的信息定義爲 l(xi)=log2p(xi)l(x_i) = -log_2p(x_i),其中 p(xi)p(x_i) 是選擇該分類的概率。
  • 信息熵:熵定義爲信息的期望值,它是度量樣本集合純度最常用的一種指標,公式爲 Ent(D)=i=1np(xi)log2p(xi)Ent(D) = -\sum_{i=1}^n p(x_i)log_2p(x_i)其中n爲分類的數目。
  • 信息增益:當我們選擇一個特徵 aa 進行劃分後,會產生 VV 個分支節點,其中第 vv 個分支結點包含 DD 中所有在屬性 aa 上取值爲 ava^v 的樣本,記爲 DvD^v。信息增益定義爲:Gain(D,a)=Ent(D)v=1VDvDEnt(Dv) Gain(D, a) = Ent(D) - \sum_{v=1}^V\frac{|D^v|}{|D|}Ent(D^v),信息增益是越大越好。

舉例如下:
在這裏插入圖片描述
首先我們創建一個數據集:

#創建數據集
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

然後計算信息熵:

#計算信息熵
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
    shannoEnt = 0.0
    for key in labelCounts:
        #計算每個類別所佔樣本集合的比例
        prob = float(labelCounts[key])/numEntries
        #求信息熵
        shannoEnt -= prob * log(prob, 2)
    return shannoEnt

最後求的結果就是 (25log225+35log235)=0.97095...-(\frac{2}{5}log_2\frac{2}{5} + \frac{3}{5}log_2\frac{3}{5}) = 0.97095...


1.2 劃分數據集

#按照給定特徵劃分數據集
#參數:待劃分的數據集、劃分數據集的特徵、特徵的返回值
def splitDataSet(dataSet, axis, value):
    # 創建新的list對象
    retDataSet = []
    # 遍歷數據集的每一行
    for featVec in dataSet:
        if featVec[axis] == value:
       	 	# 去掉axis特徵
            reducedFeatVec = featVec[:axis]
            # 將符合條件的添加到返回的數據集
            # extend() 函數用於在列表末尾一次性追加另一個序列中的多個值(用新列表擴展原來的列表)。
            reducedFeatVec.extend(featVec[axis+1:])
            # 列表中嵌套列表
            retDataSet.append(reducedFeatVec)
    return retDataSet

在這裏插入圖片描述
選擇最好的數據集劃分方法, 代碼如下:

#選擇最好的數據集劃分方式
def chooseBestFeatureToSplit(dataSet):
    #算出特徵的數目
    numFeatures = len(dataSet[0]) - 1
    #print(numFeatures)
    #求得所有樣本的信息熵
    baseEntropy = calcShannonEnt(dataSet)
    #定義最優信息增益和最優劃分屬性
    bestInfoGain = 0.0
    bestFeature = -1
    # 遍歷所有特徵
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        #print(uniqueVals)
        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

在這裏插入圖片描述
表示第0個特徵爲最佳劃分屬性。


1.3 遞歸構建決策樹

當我們選擇好劃分的屬性之後,數據將被分支開來,被向下傳遞到樹分支的下一個結點,在這個結點上,再次劃分數據,這樣就是 遞歸 的一個過程。

遞歸結束的條件爲:

  • 程序處理完所有劃分數據集的屬性,這樣類標籤卻不是唯一的,那麼就採用“少數服從多數”原則進行類別的確定。

  • 每個分支下的所有實例都有相同的分類(這樣就沒必要在劃分了),這樣就得到了一個葉子結點,類別就是這個葉子結點上所有數據相同的類別。

“少數服從多數” 代碼如下:

#統計classList中出現次數最多的元素(類標籤)
def majorityCnt(classList):
    classCount = {}
    # 統計classList中每個元素出現的次數
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        classCount[vote] += 1
    # 根據字典的值降序排序
    # operator.itemgetter(1)獲取對象的第1列的值
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # 返回出現次數最多的元素
    return sortedClassCount[0][0]

創建樹的函數代碼如下:

#創建樹
def createTree(dataSet, labels, featLabels):
    # 取分類標籤
    classList = [example[-1] for example in dataSet]
    # 如果類別完全相同則停止繼續劃分
    if classList.count(classList[0]) == len(classList):
        #print(classList)
        return classList[0]
    # 遍歷完所有特徵時得不到唯一類別的分組,返回出現次數最多的類標籤
    if len(dataSet[0]) == 1:
        return majorityCnt(classList)
    # 選擇最優特徵
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    featLabels.append(bestFeatLabel)
    # 根據標籤生成樹
    myTree = {bestFeatLabel:{}}
    # 刪除已經使用的特徵標籤
    del(labels[bestFeat])
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        #遞歸構建決策樹
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
    return myTree

運行後,生成一棵如下的樹:
在這裏插入圖片描述


1.4 使用決策樹進行分類

#使用決策樹的分類函數
def classify(inputTree, featLabels, testVec):
    #獲取根節點
    firstStr = next(iter(inputTree))
    #獲取子樹
    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

在這裏插入圖片描述

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