1. 程式人生 > >機器學習實戰讀書筆記(2)--決策樹

機器學習實戰讀書筆記(2)--決策樹

決策樹

決策樹的一個重要任務是為了資料中所蘊含的知識資訊,因此決策樹可以使用一系列不熟悉的資料集合,並從中提取系列規則,在這些機器根據資料集建立規則時,就是機器學習的過程.專家系統中經常使用決策樹

決策樹的構造

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

缺點:可能會產生過度匹配的問題(overfit)

適用資料型別:數值型和標稱型

決策樹構建的關鍵是如何找出當前作為根節點的特徵,使得一次劃分的劃分效果最好,所謂劃分效果最好從數學概念上來說就是資訊熵下降最多.
構造決策樹時,需要解決的第一個問題是當前資料集上哪個特徵對分類起到決定性作用.按照這個特徵對資料集劃分後,會產生一些子資料集,如果一個數據集中所有的元素都屬於同一類,則分類結束,如果不是並且還有待劃分的特徵的話則繼續劃分.

資訊增益

在劃分資料集前後資訊價值發生的變化叫做資訊增益.而劃分效果最好的特徵即是使資訊增益最大的特徵.那麼首先要解決如何計算資訊的價值,資訊 x i 的價值可以用 l (

x i ) = l o g 2 P ( x i
) 來定義,如果 P ( x i ) = 1 也即這件事必然發生,則資訊的價值為0.為了計算熵,我們需要計算所有類別的所有可能包含的資訊的期望值,有

H = i = 1 n P ( x i ) l o g 2 P ( x i )
接下來使用Python計算資訊熵

from math import log
import operator

def calshannonEnt(dataSet):
    ent = 0.0
    numData = len(dataSet)
    LabelList = [example[-1] for example in dataSet ]
    UniqueLabel = set(LabelList)
    for label in UniqueLabel:
            ent -= LabelList.count(label)/float(numData) * log(LabelList.count(label)/float(numData),2)
    return ent

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

myDat,labels = createDataSet()
calshannonEnt(myDat)
0.9709505944546686

熵越高,則混合的資料越多.得到熵後,就可以按照獲取最大資訊增益的方法劃分資料集

則選擇當前的用於分類的屬性的思路就是對當前可以用於分類的屬性遍歷,計算以各個屬性作為分類屬性時獲得的資訊增益,最後選擇增益最高的屬性,繼續進行遞迴處理.

遞迴終止的三種情況:
- 一個子集中的所有元素都屬於同一類
- 已經沒有可供分類的屬性使用,子集中根據少數服從多數的形式給出標籤
- 一個分類下的某個指標達到設定的閾值

def splitDataSet(dataSet, axis, value):
    '''從dataSet中選出axis維度上的值為value的行,將value屬性從
    資料行刪除,剩餘的形成新的行,最後作為列表返回'''
    returnVec = []
    for data in dataSet:
        if data[axis] == value:
            reduceFeatVec = data[:axis]
            reduceFeatVec.extend(data[axis+1:]) # 把axis左右的屬性組成一個新的行
            returnVec.append(reduceFeatVec)
    return returnVec

def chooseBestFeatureToSplit(dataSet):
    '''遍歷所有可用的屬性,計算每個屬性對應的資訊增益,
    選擇資訊增益最大的屬性,作為一次分類使用的屬性'''
    numFeat = len(dataSet[0])
    numData = len(dataSet)
    bestGain = 0.0
    bestFeat = -1
    baseEnt = calshannonEnt(dataSet)
    for i in range(numFeat):
        FeatList = [example[i] for example in dataSet]
        uniqueFeat = set(FeatList) # 用集合把重複的屬性消掉
        newEnt = 0.0
        for value in uniqueFeat:
            subDataSet = splitDataSet(dataSet,i,value)
            prob = len(subDataSet) / float(numData)
            newEnt += prob * calshannonEnt(subDataSet) # 計算一個屬性的不同值的資訊熵,並進行累加
        infoGain = baseEnt - newEnt # 計算當前屬性得到的資訊增益
        if infoGain > bestGain:
            bestGain = infoGain
            bestFeat = i
    return bestFeat


def majorityCnt(classList):
    '''對classList進行一個排序,用於第二種結束遞迴的條件'''
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), \
                              key=operator.itemgetter(1), reverse=True)
    return sortedClassCount


def createTree(dataSet, labels):
    '''建立遞迴函式,從根節點建樹,樹以字典儲存'''
    # 判斷遞迴終止條件
    # 判斷是否資料集中的資料屬於同一類
    classList = [example[-1] for example in dataSet]
    uniqueClassList = set(classList)
    if len(uniqueClassList) == 1:
        return classList[0]
    if len(dataSet[0]) == 1: # 判斷是否還有屬性可以分類
        return majorityCnt(classList) # ?這裡為什麼返回的是一個列表而不是抓喲類別
    # 需要繼續遞迴的情況
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat] # 找到最佳的分類屬性,並找到它的標籤,作為根節點的key
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat]) # 刪除已被分類的屬性的標籤,可以繼續用到遞迴中
    values = [example[bestFeat] for example in dataSet]
    uniqueValue = set(values) # 找到屬性的所有值,形成一個新的字典,作為bestFeat這個key的value
    for value in uniqueValue:
        subDataSet = splitDataSet(dataSet,bestFeat,value)
        myTree[bestFeatLabel][value] = createTree(subDataSet,labels)
        # 在新的字典內部,每個值作為key,其value是子集構成的tree
    return myTree

mydata,labels = createDataSet()
myTree = createTree(mydata,labels)
myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

使用決策樹進行分類

建好決策樹後,拿到測試資料集,按照輸入的決策樹可以判斷資料的類別

def classify(inputTree, featLabels, testVec):
    '''
    將一個建好的決策樹和特徵標籤,以及一個待測向量輸入
    返回待測向量的類別
    '''
    firstStr = list(inputTree.keys())[0]
    secondDict = inputTree[firstStr]
    featIndex = featLabels.index(firstStr)  # 得到決策樹的第一個label的在featlabels中的索引
    for key in secondDict.keys():
        if testVec[featIndex] == key:  # 找到滿足的第二層label
            if type(secondDict[key]).__name__ == 'dict':
                classLabel = classify(secondDict[key], featLabels, testVec)
                # 如果第二層label對應的不是葉節點,再遞迴的去查詢
            else:
                classLabel = secondDict[key]
    return classLabel

決策樹調優

決策樹容易產生過擬合,這就需要對其進行合理的剪枝.
解決欠擬合過擬合的方法
- 增減模型的引數維度
- 增減多項式維度
- 正則化

剪枝是決策樹停止分支的方法之一,剪枝分為預先剪枝後剪枝兩種.

預先剪枝是在生長過程中設定指標(如樹的高度),當達到該指標時就停止生長.

  1. 定義一個高度,當決策樹達到該高度時就停止決策樹的生長
  2. 達到某個節點的例項具有相同的特徵向量,及時這些例項不屬於同一類,也可以停止決策樹的生長。這個方法對於處理資料的資料衝突問題比較有效。
  3. 定義一個閾值,當達到某個節點的例項個數小於閾值時就可以停止決策樹的生長
  4. 定義一個閾值,通過計算每次擴張對系統性能的增益,並比較增益值與該閾值大小來決定是否停止決策樹的生長。

後剪枝是在樹充分生長之後,對所有相鄰的成對節點檢查,消去之後是否能夠帶來讓人滿意的不純度增長,如果可以就消去,並將他們的公共父節點作為新的葉節點.

後剪枝的優勢是克服”視界侷限”,缺點是計算量大得多.對於小樣本的情況,後剪枝的方法優於預先剪枝.