1. 程式人生 > >機器學習 (七) 決策樹演算法研究與實現

機器學習 (七) 決策樹演算法研究與實現

前言

       從決策樹這三個字中我們既可以看出來它的主要用途幫助決策某一類問題,樹是輔助我們來決策用的,如下圖一個簡單的判斷不同階段人年齡的圖:
這裡寫圖片描述

       看到上圖是有幾個問題的,我們的目的是將左邊人分類,首先根據什麼屬性分類最佳?如何量化角度來識別?從圖中也可以看出來葉子結點不一定是單個數據元素,可能是一個集合也可能是一個元素,不過他們是屬於同一個屬性集合的,例如右邊三個人都符合大於15這個特性,和前面介紹的演算法類似,適用於分類和迴歸的一種演算法,該演算法通過該構建一個樹結構來進行分類,決策樹原理簡單隻通過簡單的分支判斷既可得到最終分類結果。

原理結構

        在上面的例子中,不同的節點代表不同的含義:
這裡寫圖片描述
       最上層表示根節點、最下層為葉子節點,葉子節點即結果資料點、中間一層為非葉子節點,各個分叉表示分支,各個節點代表的含義如下:
這裡寫圖片描述

接下來我們就需要考慮如何構造決策樹,只有構造出來樹才能對資料分類,分為了兩個階段訓練階段和分類階段:
這裡寫圖片描述
讓我們再來看一個例子,如下:
這裡寫圖片描述

這是構造一個樹第一個需要解決的問題,每次拆分子集時哪個屬性和值來當條件,這裡涉及到了量化指標熵,熵是資訊理論中的內容是熵農第一次提出表示資訊的混亂程度,在化學中表示分子的運動混亂程度,公式如下:
這裡寫圖片描述

在公式中,i表示事件,注意不要和樣本個數弄混,P表示一件事情發生的概念,-(概率*概率的對數值)累加和即熵值,先來看一下ln(x)函式,在x右半軸成單調遞增,
這裡寫圖片描述
讓我們通過數學上面空間來說明一下,假設有兩個集合A和B各5個元素,A ={1,2,3,4,5} ,B={1,1,1,1,1} 各元素互相獨立,A中每個元素髮生概率為0.2 ,B中每個元素髮生概率為1,計算各自的熵值:
熵A= -0.2*ln(0.2) + -0.2*ln(0.2) + -0.2*ln(0.2) + -0.2*ln(0.2) + -0.2*ln(0.2) 約等於 0.32 ,熵B= - 1*ln(1) *5 = 0 ,熵值越大表示混亂程度越大,從計劃結果表明A集合的混亂程度比B大,除了通過熵值來華為還有一個屬性是基尼不純度,暫且不討論。
有了熵值之後還不能確定讓哪個屬性來進行劃分資料集合,我們的目標是最快的方式劃分集合,因此就需要找到能使得熵值減少最快的屬性來劃分,引入了資訊增益概念,資訊增益就是熵值減少的多少,找到資訊增益最大的也就是熵值減少最快的那一個屬性即可。

下面有份資料表示天氣的四個屬性,標籤為是否去打籃球,如下:
這裡寫圖片描述
為了根據天氣的四個屬性決策是否去打籃球,此時就需要構建決策樹,第一步計劃初始標籤的原始熵,四個屬性劃分後熵值分別是多少,如:
這裡寫圖片描述
有了上面的樣本後,先來計劃原始的資訊熵,即沒有天氣因素影響的情況下,標籤的概率,樣本共14個,yes打球事件9個樣本,no不打球事件5個,則打球事件概率9/14,不打球事件概率5/14,由熵公式計劃 :
原始熵=-9/14*ln(9/14)-5/14*ln(5/14) = 0.94
然後我們依次計算天氣的各個屬性作為根節點的資訊熵,天氣劃分時如下:
outlook有三種情況 sunny、overcast、rainy ,這三種情況的概率分別為Poutlook(sunny)=5/14,Poutlook(overcast)=0,Poutlook(rainy)=5/14,他們的熵值為
outlook(sunny)=-2/5*ln(2/5)-3/5*ln(3/5)=0.971
outlook(overcast)=0
outlook(rainy)=-2/5*ln(2/5)-3/5*ln(3/5)=0.971
天氣作為劃分屬性時熵值為=0.971*5/14+0+0.971*5/14=0.693
其它三種屬性請自行計劃,經比較選擇天氣第一個屬性華為最佳,資訊增益最大。

找到第一個根節點之後,還需要繼續往下構建樹,繼續找第二個節點,遞迴往下構建,思路如下:
決策樹分類演算法的流程如下:
1.初始化根結點,此時所有的觀測樣本均屬於根結點
2.根據下文中介紹的劃分選擇,選擇當前最優的劃分屬性。根據屬性取值的不同對觀測樣本進行分割
對分割後得到的節點重複使用步驟2,直到
分割得到的觀測樣本屬於同一類或屬性用完或者達到預先設定的條件,如樹的深度。以葉子節點中大多數樣本的類別作為該葉子節點的類別

上面提到的資訊增益 也稱為ID3演算法(iterater Dichotomiser),該演算法也有缺點,當屬性值過多,每個屬性值中劃分出來的樣本很少時會不起作用,如當有一列是id值時,id一般是自增主鍵,每個值對應一個樣本,假如根據這一列來計算熵值會劃分出來很多子集,每個子集只有一個樣本,熵值都為0,由此引入了資訊增益率 C4.5演算法,可以避免這個問題,他們都是如何選擇屬性來劃分資料集的方法。

程式碼

       以下是實現時寫的,如下:


def calcShannonEnt(dataSet):
    '''
    計算資料集的資訊熵
    :param dataSet:
    :return:
    '''
    numEntries = len(dataSet)
    print "numEntries = %s" % numEntries
    labelCounts = {}
    for featVec in dataSet:
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel] = 0
        labelCounts[currentLabel] += 1
    print "labelCounts = %s" % labelCounts

    shannonEnt = 0.0
    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


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

def appendExtendTest():
    a = [1,2,3]
    b = [4,5,6]
    a.append(b)
    print a

    a.extend(b)
    print a


def chooseBestFeatureToSplit(dataSet):
    '''
    選擇最好的資料集劃分方式
    :param dataSet: 原始資料集
    :return: 最優分類特徵的索引
    '''
    numFeatures = len(dataSet[0]) - 1
    print "numFeatures = %s" % numFeatures

    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0;bestFeature = -1
    for i in range(numFeatures):
        # 建立唯一的分類標籤列表
        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
            bestFeature = i
    return bestFeature


def majorityCnt(classList):
    classCount = {}
    for vote in classList:
        if vote not in classCount.keys():
            classCount[vote] = 0
        else:
            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]
    print "classList[0] = %s" % classList[0]
    print "classList.count(classList[0]) = %s" % classList.count(classList[0])
    # 如果類別完全相同則停止繼續劃分
    if classList.count(classList[0]) == len(classList):
        return classList[0]
    # 遍歷完所有特徵時返回出現次數最多的類別
    print "dataSet[0] = %s" % dataSet[0]
    print "len(dataSet[0]) = %s" % len(dataSet[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[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),subLabels)
    return myTree






if  __name__ == '__main__':
    myDat,labels = createDataSet()
    print "myDat = %s" % myDat
    print "labels = %s" % labels

    # shannonEnt = calcShannonEnt(myDat)
    # print shannonEnt

    # myDat[0][-1] = 'maybe'
    # myDat[1][-1] = 'maybe'
    # print myDat
    # shannonEnt = calcShannonEnt(myDat)
    # print shannonEnt

    # appendExtendTest()
    # print splitDataSet(myDat,0,1)
    # print splitDataSet(myDat,0,0)
    # print chooseBestFeatureToSplit(myDat)

    myTree = createTree(myDat,labels)
    print myTree

總結

       決策樹是很多演算法的基礎,除此之外還有迴歸樹、森林樹等演算法,後續會一一介紹,不同演算法對應的是不同的資料例項,資料決定了演算法的形態和好壞。

題外思考

學會一技之長的好處
       有一技之長的人感覺生活比別人過的要好一些,也很可能在其他方面很快取的突破,它的意義不僅僅是為我們混口飯吃,通過掌握某一技能的過程增強了我們的學習能力,樹立了以後做好其他事情的信心和勇氣,以後在做其它事情也會有很多幫助,另外在學習一項新事物的時候假如花費20%的時間就容易掌握了80%的知識技能,那麼很快很多人就會進來,失去優勢,這種事物也是不值錢的,值不值錢要看稀缺性,越稀少的東西越貴,大家都知道沙漠裡水可能比金子貴就是這個道理,在學習和研究領導這個道理同樣適用。