決策樹——從原理走向實戰

決策樹——既能分類又能迴歸的模型

在現實生活中,我們會遇到各種選擇,不論是選擇男女朋友,還是挑選水果,都是基於以往的經驗來做判斷。如果把判斷背後的邏輯整理成一個結構圖,你會發現它實際上是一個樹狀圖,這就是我們今天要講的決策樹。

決策樹學習的目的是爲了生成一顆泛化能力強,即處理未見示例能力強的決策樹,其基本流程遵循簡單而直觀的 分而治之 (Divide and Conquer) 策略,如下圖所示:

Decision_tree_Learning

決策樹的生成是一個自根結點一直到葉結點的遞歸生成過程。

在遞歸生成的僞代碼表述中,可以看到,有三個地方導致遞歸返回:

(行 3) 當前結點包含的樣本全部屬於同一個類別,無需劃分;
(行 6) 當前屬性集爲空,或是所有樣本在所有屬性上取值相同,無法劃分。在這種情況下,把當前結點標記爲葉結點,並且將其類別設定爲該結點所含樣本最多的類別;
(行 12) 當前結點包含的樣本集和爲空,不能劃分,把當前結點標記爲葉結點,但是將其類別設定爲其父結點所含樣本最多的類別,周志華老師的《機器學習》中在該條件下執行了 return,但是按照我的理解由於這裏處於 for 循環中,雖然屬性中的一個取值樣本集合爲空,但是其它取值情況下還有有可能有樣本集合的,如果這裏執行了 return,那麼就跳過了其它取值判斷的可能。

另外,其中第 14 行 A{a∗}
A{a∗} 表示從 AA 中去除 a∗a∗ 屬性。

決策樹基本上就是把我們以前的經驗總結出來。如果我們要出門打籃球,一般會根據“天氣”、“溫度”、“溼度”、“颳風”這幾個條件來判斷,最後得到結果:去打籃球?還是不去?

img

上面這個圖就是一棵典型的決策樹。我們在做決策樹的時候,會經歷兩個階段:構造剪枝

構造

構造就是生成一棵完整的決策樹。簡單來說,構造的過程就是選擇什麼屬性作爲節點的過程,那麼在構造過程中,會存在三種節點:

  1. 根節點:就是樹的最頂端,最開始的那個節點。在上圖中,“天氣”就是一個根節點;
  2. 內部節點:就是樹中間的那些節點,比如說“溫度”、“溼度”、“颳風”;
  3. 葉節點:就是樹最底部的節點,也就是決策結果。

節點之間存在父子關係。比如根節點會有子節點,子節點會有子子節點,但是到了葉節點就停止了,葉節點不存在子節點。那麼在構造過程中,你要解決三個重要的問題:

  1. 選擇哪個屬性作爲根節點;
  2. 選擇哪些屬性作爲子節點;
  3. 什麼時候停止並得到目標狀態,即葉節點。

剪枝

剪枝就是給決策樹瘦身,這一步想實現的目標就是,不需要太多的判斷,同樣可以得到不錯的結果。之所以這麼做,是爲了防止“過擬合”(Overfitting)現象的發生。

決策樹的剪枝基本策略有 預剪枝 (Pre-Pruning) 和 後剪枝 (Post-Pruning) [Quinlan, 1933]。根據周志華老師《機器學習》一書中所描述是先對數據集劃分成訓練集和驗證集,訓練集用來決定樹生成過程中每個結點劃分所選擇的屬性;驗證集在預剪枝中用於決定該結點是否有必要依據該屬性進行展開,在後剪枝中用於判斷該結點是否需要進行剪枝。
預剪枝與後剪枝的區別和優缺點

預剪枝

預剪枝會使得決策樹的很多分支沒有展開,也就是沒有繼續分類下去,這不僅降低了過擬合的風險,還顯著減少了決策樹的訓練時間開銷和測試時間開銷。但是另一方面,有些分支的當前劃分雖不能提升泛化性能、甚至可能導致泛化性能暫時下降,但是在其基礎上進行的後續劃分有可能導致性能顯著提升。預剪枝基於’貪心’本質,也就是能多剪枝就多剪枝,使得預剪枝策略給決策樹帶來了欠擬合的風險。

後剪枝

後剪枝決策樹通常比預剪枝決策樹保留了更多的分支。一般情況下,後剪枝決策樹的欠擬合風險很小,泛化性能往往由於預剪枝決策樹,但是後剪枝過程是在生成完全決策樹後進行的,並且要自下往上地對樹中的非葉子節點逐一進行考察計算,因此訓練時間的開銷比爲剪枝和預剪枝決策樹都要大得多。

過擬合:指的是模型的訓練結果“太好了”,以至於在實際應用的過程中,會存在“死板”的情況,導致分類錯誤。

欠擬合:指的是模型的訓練結果不理想。

img

造成過擬合的原因

一是因爲訓練集中樣本量較小。如果決策樹選擇的屬性過多,構造出來的決策樹一定能夠“完美”地把訓練集中的樣本分類,但是這樣就會把訓練集中一些數據的特點當成所有數據的特點,但這個特點不一定是全部數據的特點,這就使得這個決策樹在真實的數據分類中出現錯誤,也就是模型的“泛化能力”差。

泛化能力:指的分類器是通過訓練集抽象出來的分類能力,你也可以理解是舉一反三的能力。如果我們太依賴於訓練集的數據,那麼得到的決策樹容錯率就會比較低,泛化能力差。因爲訓練集只是全部數據的抽樣,並不能體現全部數據的特點。

剪枝的方法

  • 預剪枝:在決策樹構造時就進行剪枝。方法是,在構造的過程中對節點進行評估,如果對某個節點進行劃分,在驗證集中不能帶來準確性的提升,那麼對這個節點進行劃分就沒有意義,這時就會把當前節點作爲葉節點,不對其進行劃分。
  • 後剪枝:在生成決策樹之後再進行剪枝。通常會從決策樹的葉節點開始,逐層向上對每個節點進行評估。如果剪掉這個節點子樹,與保留該節點子樹在分類準確性上差別不大,或者剪掉該節點子樹,能在驗證集中帶來準確性的提升,那麼就可以把該節點子樹進行剪枝。方法是:用這個節點子樹的葉子節點來替代該節點,類標記爲這個節點子樹中最頻繁的那個類。

如何判斷要不要去打籃球?

img

我們該如何構造一個判斷是否去打籃球的決策樹呢?再回顧一下決策樹的構造原理,在決策過程中有三個重要的問題:將哪個屬性作爲根節點?選擇哪些屬性作爲後繼節點?什麼時候停止並得到目標值?

顯然將哪個屬性(天氣、溫度、溼度、颳風)作爲根節點是個關鍵問題,在這裏我們先介紹兩個指標**:純度信息熵**。

純度:

你可以把決策樹的構造過程理解成爲尋找純淨劃分的過程。數學上,我們可以用純度來表示,純度換一種方式來解釋就是讓目標變量的分歧最小

舉個例子,假設有 3 個集合:

  • 集合 1:6 次都去打籃球;
  • 集合 2:4 次去打籃球,2 次不去打籃球;
  • 集合 3:3 次去打籃球,3 次不去打籃球。

按照純度指標來說,集合 1> 集合 2> 集合 3。因爲集合1 的分歧最小,集合 3 的分歧最大。

信息熵:表示信息的不確定度

在信息論中,隨機離散事件出現的概率存在着不確定性。爲了衡量這種信息的不確定性,信息學之父香農引入了信息熵的概念,並給出了計算信息熵的數學公式:

img

p(i|t) 代表了節點 t 爲分類 i 的概率,其中 log2 爲取以 2 爲底的對數。這裏我們不是來介紹公式的,而是說存在一種度量,它能幫我們反映出來這個信息的不確定度。當不確定性越大時,它所包含的信息量也就越大,信息熵也就越高。

舉個例子,假設有 2 個集合:

  • 集合 1:5 次去打籃球,1 次不去打籃球;
  • 集合 2:3 次去打籃球,3 次不去打籃球。

在集合 1 中,有 6 次決策,其中打籃球是 5 次,不打籃球是 1 次。那麼假設:類別 1 爲“打籃球”,即次數爲 5;類別 2 爲“不打籃球”,即次數爲 1。那麼節點劃分爲類別1的概率是 5/6,爲類別2的概率是1/6,帶入上述信息熵公式可以計算得出:

img

同樣,集合 2 中,也是一共 6 次決策,其中類別 1 中“打籃球”的次數是 3,類別 2“不打籃球”的次數也是 3,那麼信息熵爲多少呢?我們可以計算得出:

img

從上面的計算結果中可以看出,信息熵越大,純度越低。當集合中的所有樣本均勻混合時,信息熵最大,純度最低。

我們在構造決策樹的時候,會基於純度來構建。而經典的 “不純度”的指標有三種,分別是信息增益(ID3 算法)、信息增益率(C4.5 算法)以及基尼指數(Cart 算法)

信息增益:

信息增益指的就是劃分可以帶來純度的提高,信息熵的下降。它的計算公式,是父親節點的信息熵減去所有子節點的信息熵。在計算的過程中,我們會計算每個子節點的歸一化信息熵,即按照每個子節點在父節點中出現的概率,來計算這些子節點的信息熵。所以信息增益的公式可以表示爲:

img

公式中 D 是父親節點,Di 是子節點,Gain(D,a)中的 a 作爲 D 節點的屬性選擇。

假設D 天氣 = 晴的時候,會有 5 次去打籃球,5 次不打籃球。其中 D1 颳風 = 是,有 2 次打籃球,1 次不打籃球。D2 颳風 = 否,有 3 次打籃球,4 次不打籃球。那麼a 代表節點的屬性,即天氣 = 晴。

img

針對圖上這個例子,D 作爲節點的信息增益爲:

img

也就是 D 節點的信息熵 -2 個子節點的歸一化信息熵。2個子節點歸一化信息熵 =3/10 的 D1 信息熵 +7/10 的 D2 信息熵。

我們基於 ID3 的算法規則,完整地計算下我們的訓練集,訓練集中一共有 7 條數據,3 個打籃球,4 個不打籃球,所以根節點的信息熵是:

img

如果你將天氣作爲屬性的劃分,會有三個葉子節點 D1、D2 和D3,分別對應的是晴天、陰天和小雨。我們用 + 代表去打籃球,- 代表不去打籃球。那麼第一條記錄,晴天不去打籃球,可以記爲 1-,於是我們可以用下面的方式來記錄 D1,D2,D3:

D1(天氣 = 晴天)={1-,2-,6+}

D2(天氣 = 陰天)={3+,7-}

D3(天氣 = 小雨)={4+,5-}

我們先分別計算三個葉子節點的信息熵:

img

因爲 D1 有 3 個記錄,D2 有 2 個記錄,D3 有2 個記錄,所以 D 中的記錄一共是 3+2+2=7,即總數爲 7。所以 D1 在 D(父節點)中的概率是 3/7,D2在父節點的概率是 2/7,D3 在父節點的概率是 2/7。那麼作爲子節點的歸一化信息熵 = 3/70.918+2/71.0=0.965。

因爲我們用 ID3 中的信息增益來構造決策樹,所以要計算每個節點的信息增益。

天氣作爲屬性節點的信息增益爲,Gain(D , 天氣)=0.985-0.965=0.020。

同理我們可以計算出其他屬性作爲根節點的信息增益,它們分別爲:

Gain(D , 溫度)=0.128

Gain(D , 溼度)=0.020

Gain(D , 颳風)=0.020

我們能看出來溫度作爲屬性的信息增益最大。因爲 ID3 就是要將信息增益最大的節點作爲父節點,這樣可以得到純度高的決策樹,所以我們將溫度作爲根節點。其決策樹狀圖分裂爲下圖所示:

img

然後我們要將上圖中第一個葉節點,也就是 D1={1-,2-,3+,4+}進一步進行分裂,往下劃分,計算其不同屬性(天氣、溼度、颳風)作爲節點的信息增益,可以得到:

Gain(D , 天氣)=0

Gain(D , 溼度)=0

Gain(D , 颳風)=0.0615

我們能看到颳風爲 D1 的節點都可以得到最大的信息增益,這裏我們選取颳風作爲節點。同理,我們可以按照上面的計算步驟得到完整的決策樹,結果如下:

img

於是我們通過 ID3 算法得到了一棵決策樹。ID3 的算法規則相對簡單,可解釋性強。同樣也存在缺陷,比如我們會發現 ID3 算法傾向於選擇取值比較多的屬性。這樣,如果我們把“編號”作爲一個屬性(一般情況下不會這麼做,這裏只是舉個例子),那麼“編號”將會被選爲最優屬性 。但實際上“編號”是無關屬性的,它對“打籃球”的分類並沒有太大作用。

**所以 ID3 有一個缺陷就是,有些屬性可能對分類任務沒有太大作用,但是他們仍然可能會被選爲最優屬性。**這種缺陷不是每次都會發生,只是存在一定的概率。在大部分情況下,ID3 都能生成不錯的決策樹分類。針對可能發生的缺陷,後人提出了新的算法進行改進。

在 ID3 算法上進行改進的 C4.5 算法

1. 採用信息增益率

因爲 ID3 在計算的時候,傾向於選擇取值多的屬性。爲了避免這個問題,C4.5 採用信息增益率的方式來選擇屬性。信息增益率 = 信息增益 / 屬性熵

當屬性有很多值的時候,相當於被劃分成了許多份,雖然信息增益變大了,但是對於 C4.5 來說,屬性熵也會變大,所以整體的信息增益率並不大。

2. 採用悲觀剪枝

ID3 構造決策樹的時候,容易產生過擬合的情況。在 C4.5中,會在決策樹構造之後採用悲觀剪枝(PEP),這樣可以提升決策樹的泛化能力。

悲觀剪枝是後剪枝技術中的一種,通過遞歸估算每個內部節點的分類錯誤率,比較剪枝前後這個節點的分類錯誤率來決定是否對其進行剪枝。這種剪枝方法不再需要一個單獨的測試數據集。

3. 離散化處理連續屬性

C4.5 可以處理連續屬性的情況,對連續的屬性進行離散化的處理。比如打籃球存在的“溼度”屬性,不按照“高、中”劃分,而是按照溼度值進行計算,那麼溼度取什麼值都有可能。該怎麼選擇這個閾值呢,C4.5 選擇具有最高信息增益的劃分所對應的閾值

4. 處理缺失值

針對數據集不完整的情況,C4.5 也可以進行處理。

假如我們得到的是如下的數據,你會發現這個數據中存在兩點問題。第一個問題是,數據集中存在數值缺失的情況,如何進行屬性選擇?第二個問題是,假設已經做了屬性劃分,但是樣本在這個屬性上有缺失值,該如何對樣本進行劃分?

img

我們不考慮缺失的數值,可以得到溫度 D={2-,3+,4+,5-,6+,7-}。溫度 = 高:D1={2-,3+,4+};溫度 = 中:D2={6+,7-};溫度 = 低:D3={5-} 。這裏 + 號代表打籃球,- 號代表不打籃球。比如ID=2 時,決策是不打籃球,我們可以記錄爲 2-。

所以三個葉節點的信息熵可以結算爲:

img

這三個節點的歸一化信息熵爲 3/60.918+2/61.0+1/6*0=0.792。

針對將屬性選擇爲溫度的信息增益率爲:

Gain(D′, 溫度)=Ent(D′)-0.792=1.0-0.792=0.208

D′的樣本個數爲 6,而 D 的樣本個數爲 7,所以所佔權重比例爲 6/7,所以 Gain(D′,溫度) 所佔權重比例爲6/7,所以:

Gain(D, 溫度)=6/7*0.208=0.178

這樣即使在溫度屬性的數值有缺失的情況下,我們依然可以計算信息增益,並對屬性進行選擇。

小結:

首先 ID3 算法的優點是方法簡單,缺點是對噪聲敏感。訓練數據如果有少量錯誤,可能會產生決策樹分類錯誤。C4.5 在 IID3 的基礎上,用信息增益率代替了信息增益,解決了噪聲敏感的問題,並且可以對構造樹進行剪枝、處理連續數值以及數值缺失等情況,但是由於 C4.5 需要對數據集進行多次掃描,算法效率相對較低。

最後附上決策樹基礎代碼:

只根據頭髮和聲音怎麼判斷一位同學的性別。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Oc6h0hJR-1590627021355)(D:\CSDN\pic\決策樹\1590626759783.png)]

構建決策樹形狀

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bRFJIRtJ-1590627021358)(D:\CSDN\pic\決策樹\1590626853882.png)]

代碼實現:

from math import log
import operator

def calcShannonEnt(dataSet):  # 計算數據的熵(entropy)
    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
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries # 計算單個類的熵值
        shannonEnt-=prob*log(prob,2) # 累加每個類的熵值
    return shannonEnt

def createDataSet1():    # 創造示例數據
    dataSet = [['長', '粗', '男'],
               ['短', '粗', '男'],
               ['短', '粗', '男'],
               ['長', '細', '女'],
               ['短', '細', '女'],
               ['短', '粗', '女'],
               ['長', '粗', '女'],
               ['長', '粗', '女']]
    labels = ['頭髮','聲音']  #兩個特徵
    return dataSet,labels

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

def chooseBestFeatureToSplit(dataSet):  # 選擇最優的分類特徵
    numFeatures = len(dataSet[0])-1
    baseEntropy = calcShannonEnt(dataSet)  # 原始的熵
    bestInfoGain = 0
    bestFeature = -1
    for i in range(numFeatures):
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)
        newEntropy = 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):    #按分類後類別數量排序,比如:最後分類爲2男1女,則判定爲男;
    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[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:
        subLabels=labels[:]
        myTree[bestFeatLabel][value]=createTree(splitDataSet\
                            (dataSet,bestFeat,value),subLabels)
    return myTree


if __name__=='__main__':
    dataSet, labels=createDataSet1()  # 創造示列數據
    print(createTree(dataSet, labels))  # 輸出決策樹模型結果

輸出結果爲:

1 {‘聲音’: {‘細’: ‘女’, ‘粗’: {‘頭髮’: {‘短’: ‘男’, ‘長’: ‘女’}}}}

這個結果的意思是:首先按聲音分類,聲音細爲女生;然後再按頭髮分類:聲音粗,頭髮短爲男生;聲音粗,頭髮長爲女生。
這個結果也正是同學B的結果。
補充說明:判定分類結束的依據是,若按某特徵分類後出現了最終類(男或女),則判定分類結束。使用這種方法,在數據比較大,特徵比較多的情況下,很容易造成過擬合,於是需進行決策樹枝剪,一般枝剪方法是當按某一特徵分類後的熵小於設定值時,停止分類。

又到了文末, 我上文那些美麗的圖片來自網絡資源,自己不會畫這麼好看的圖,借鑑了一下。能劃到這裏的都是有緣人,不然早不看了,另外推薦一下自己的深度學習交流羣~一起進步!
周小夏(CV調包俠)
在這裏插入圖片描述

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