python機器學習筆記:深入學習決策樹算法原理


  分類技術(或分類法)是一種根據輸入數據建立分類模型的系統方法,分類法的例子包括決策分類法,基於規則的分類法,神經網絡,支持向量機和朴素貝葉斯分類法。這些技術都使用一種學習算法(learning algorithm)確定分類模型,該模型能夠很好的擬合輸入數據中類標號和屬性集之間的聯系,學習算法得到的模型不僅要很好地擬合輸入數據,還要能夠正確的預測未知樣本的類標號。因此,訓練算法的主要目標就是建立具有很好泛化能力模型,即建立能夠准確的預測未知樣本類標號的模型。

  那我們首先說一下分類與回歸的區別。

回歸(regression)

  回歸問題的應用場景(預測的結果是連續的,例如預測明天的溫度:23,24,25度等等)

  所以說回歸問題通常是用來預測一個值,如預測房價,未來的天氣情況等等,例如一個產品的實際價格為500元,通過回歸分析預測值為501元,我們認為這是一個比較好的回歸分析。一個比較常見的回歸算法是線性回歸算法(LR)。另外,回歸分析用在神經網絡上,其最上層是不需要softmax函數的,而是直接對前一層累加即可。回歸是對真實值的一種逼近預測。

分類(classification)

  分類問題的應用場景(預測的結果是離散的,例如預測明天天氣:陰,晴,雨等等)

  例如判斷一幅圖片上的動物是一只貓還是一只狗,分類通常是建立在回歸之上,分類的最后一層通常要使用softmax函數進行判斷其所屬類別。分類並沒有逼近的概念,最終正確結果只有一個,錯誤的就是錯誤的,不會有相近的概念。最常見的分類方法是邏輯回歸,或者叫邏輯分類。

如何選擇模型呢?(這里借助別人的圖來選擇分類,回歸,聚類,降維)

  決策數(Decision Tree)在機器學習中也是比較常見的一種算法,屬於監督學習中的一種。看字面意思應該也比較容易理解,而作為一個碼農,經常不斷的敲 if  else,其實就已經用到了決策樹的思想了,所以相比其他算法比如支持向量機(SVM)或神經網絡,似乎決策樹感覺“親切”許多。

決策樹算法思想

  決策樹(decision tree)是一個樹結構(可以是二叉樹或者非二叉樹)。決策樹分為分類樹和回歸樹兩種,分類樹對離散變量做決策樹,回歸樹對連續變量做決策樹。

  其中每個非葉節點表示一個特征屬性上的測試,每個分支代表這個特征屬性在某個值域上的輸出,而每個葉節點存放在一個類別。

  使用決策樹進行決策的過程就是從根節點開始,測試待分類項中相應的特征屬性,並按照其值選擇輸出分支,直到到達葉子節點,將葉子節點存放的類別作為決策結果。

  舉個例子,讓我們看一下決策樹從根節點開始一步步走到葉子節點:

   如上圖所示,比如小明一家五口人,讓我們使用決策樹來判斷誰喜歡玩電子游戲。一般情況下,在一家人中年齡小的喜歡玩,年齡大的就不那么熱愛游戲了,所以我們先做一個判斷,年齡是否小於15歲,然后再細分,判斷是男孩還是女孩,一般情況下,我們知道男孩比較喜歡玩電子游戲,所以我們再做一個判斷,性別是男還是女,最后得到是小明。

  從這么一個簡單的例子,我們可以看出一個決策樹的組成,分為三個:根節點;非葉子節點與分支;葉子節點。具體意思如下:

  • 根節點:第一個選擇點
  • 非葉子節點與分支:中間過程
  • 葉子節點:最終的決策結果

  當然我們也發現:一旦構造好了決策樹,那么分類或預測任務就很簡單了,只需要走一遍即可,但是難點就在於如何構造出來一棵樹。構造樹需要考慮的問題有很多。

1,決策樹學習算法主要由三部分構成

1.1  特征選擇

  特征選擇是指從訓練數據中眾多的特征中選擇一個特征作為當前節點的分裂標准,如何選擇特征有着很多不同量化評估標准,從而衍生出不同的決策樹算法。

1.2  決策樹生成

  根據選擇的特征評估標准,從上至下遞歸地生成子節點,直到數據集不可分則停止決策樹停止生長。樹結構來說,遞歸結構是最容易理解的方式。

1.3  決策樹的剪枝

  決策樹容易過擬合,一般來需要剪枝,縮小樹結構規則,緩解過擬合,剪枝技術有預剪枝和后剪枝兩種。

  • 預剪枝:邊建立決策樹邊進行剪枝的操作(更實用),預剪枝需要限制深度,葉子節點個數,葉子節點樣本數,信息增益量等。
  • 后剪枝:當建立完決策樹后來進行剪枝操作,通過一定的衡量標准(葉子節點越多,損失越大)

2,決策實例一

  決策樹表示如下(借用周志華老師西瓜書的圖)

  假如我現在告訴你,我買了一個西瓜,它的特點是紋理是清晰,根蒂是硬挺的瓜,你來給我判斷一下是好瓜還是壞瓜,恰好,你構建了一顆決策樹,告訴他,沒問題,我馬上告訴你是好瓜,還是壞瓜?

  判斷步驟如下:

  根據紋理特征,已知是清晰,那么走下面這條路,紅色標記:

  好了,現在到了第二層,這個時候,由決策樹的圖,我們可以看到,我們需要知道根蒂的特征是什么了?對是硬挺,於是我們繼續走,如下藍色的圖:

  此時,我們到達葉子結點了,根據上面總結的點,可知,葉子結點代表一種類別,我們從如上決策樹中可以知道,這是一個壞瓜!!

  所以根據上面的例子,非常直觀容易的得到了一個實例的類別判斷,只要你告訴我各個特征的具體值,決策樹的判定過程就相當於樹中從跟節點到某一個葉子節點的遍歷,每一步如何遍歷是由數據各個特征的具體特征屬性決定。

  好了,可能有人要問了,說了這么多,給你訓練數據,你的決策樹是怎么構建的呢?沒有樹,談何遍歷,談何分類?

  於是構建決策樹也就成了最重要的工作!!

  比如,給我下面的訓練數據,我們如何構建出決策樹?

  我們可以從上面的決策樹看出,每一次子結點的產生,是由於我在當前層數選擇了不同的特征來作為我的分裂因素造成的,比如下面紅色三角形表示選擇的特征:

  每一層選擇了指定的特征之后,我們就可以繼續由該特征的不同屬性值進行划分,依次一直到葉子節點。

  看起來一切很順利,但是,細心地小伙伴可能會問了,為什么在第一次選擇特征分裂的時候,不選擇觸感呢?而是選擇紋理,比如如下:

  不換成抽干,或者是其他特征呢?為什么選擇的是紋理,這是以什么標准來選擇特征的?這個問題先留着,讓我們再看一個例子

3,決策實例二

  一天,老師問了個問題,只根據頭發和聲音怎么判斷一位同學的性別。 
  為了解決這個問題,同學們馬上簡單的統計了8位同學的相關特征,數據如下:

機智的同學A想了想,先根據頭發判斷,若判斷不出,再根據聲音判斷,於是畫了一幅圖,如下: 
同學A 
  於是,一個簡單、直觀的決策樹就這么出來了。頭發長、聲音粗就是男生;頭發長、聲音細就是女生;頭發短、聲音粗是男生;頭發短、聲音細是女生。 
  
  這時又蹦出個同學B,想先根據聲音判斷,然后再根據頭發來判斷,如是大手一揮也畫了個決策樹: 
同學B 
  同學B的決策樹:首先判斷聲音,聲音細,就是女生;聲音粗、頭發長是男生;聲音粗、頭發長是女生。

  那么問題來了:同學A和同學B誰的決策樹好些?計算機做決策樹的時候,面對多個特征,該如何選哪個特征為最佳的划分特征?下面我們就要說一下決策樹的特征選擇了。

4,決策樹的特征選擇

  划分數據集的大原則是:將無序的數據變得更加有序。
  我們可以使用多種方法划分數據集,但是每種方法都有各自的優缺點。於是我們這么想,如果我們能測量數據的復雜度,對比按不同特征分類后的數據復雜度,若按某一特征分類后復雜度減少的更多,那么這個特征即為最佳分類特征。 

  我們的目標是:通過一種衡量標准來計算通過不同特征進行分支選擇后的分類情況,找出來最好的那個當做根節點,以此類推。而衡量標准就是:熵
  Claude Shannon 定義了熵(entropy)和信息增益(information gain)。 下面先介紹一下概念:

5,信息論基礎知識

5.1  熵(entropy)

  在信息論與概率論中,熵(entropy)用於表示“隨機變量不確定性的度量

  (解釋一下:說白了就是物體內部的混亂程度,比如雜貨市場里面什么都有,那肯定亂,而專賣店里面只賣一個牌子,那就穩定了)

  在決策樹的算法中,熵是一個非常非常重要的概念,一件事發生的概率越小,我們說它蘊含的信息量越大,比如:我們聽到女人懷孕了,是不是不奇怪,但是某天聽到那個男人懷孕了,是不是????

  所以下面我們說一下信息量熵的定義。

  設X是一個有限狀態的離散型隨機變量,其概率分布為:

  則隨機變量X的熵定義為:

  其中 n代表X的 n 種不同的離散取值,而 pi代表了X取值為 i 的概率,log是以2為底的對數

  注意這里,為什么對熵定義取負號呢?首先,既然Pi是概率分布,那么pi的取值范圍肯定在(0,  1)之間,而log的底數肯定是大於1的,所以是下面紅色的log曲線,而在x屬於0到1之間,y是負數,取個負號則熵就成為正的了。

  熵越大,則隨機變量的不確定性越大。

  當隨機變量只有0和1兩種取值的時候,假設P(X=1)=p,則有:

  從而有:

  從而可知,當p=0.5時,熵取值最大,隨機變量不確定性最大。如圖:

5.2  條件熵(conditional entropy)

  隨機變量X給定的條件下,Y的條件概率分布的熵對X的數學期望,其數學推導如下:

  隨機變量Y的條件熵H(Y|X)定義為:

  其中:

  條件熵H(Y|X)表示在已知隨機變量X的條件下隨機變量Y的不確定性。注意一下,條件熵中X也是一個變量,意思是在一個變量X的條件下(變量X的每個值都會取到),另一個變量Y的熵對X的期望。

5.3  信息增益(information gain)

  信息增益表示的是:得知特征X的信息而使得類Y的信息的不確定性減少的程度。簡單說,就是當我們用另一個變量X對原變量Y分類后,原變量Y的不確定性就會減少了(即熵值減少)。而熵就是不確定性,不確定程度減少了多少其實就是信息增益,這就是信息增益的由來。

  所以信息增益的具體定義如下:

  特征A對訓練數據集D的信息增益g(D,A)定義為集合D的經驗熵H(D)與特征A給定條件下D的經驗條件熵H(D/A)之差,即:

  一般地,熵H(Y)與條件熵H(Y|X)之差成為互信息(mutual information)。

  根據信息增益准則而進行特征選擇的方法是:對訓練數據集D,計算其每個特征的信息增益,並比他們大小,從而選擇信息增益最大的特征。

  假設訓練數據集為D,樣本容量為|D|,由k個類別Ck,|Ck|為類別Ck的樣本個數,某一特征A由n個不同的取值a1,a2,a3,.....an。根據特征A的取值可將數據集D划分為n個子集D1,D2.....Dn,|Di|為Di的樣本個數,並記子集Di中屬於類Ck,的樣本的集合為Dik,|Dik|為Dik的樣本個數。

  則信息增益的算法如下:

  • 輸入:訓練數據集D和特征A
  • 輸出:特征A對訓練數據集D的信息增益g(D,A)

(1) 計算數據集D的經驗熵H(D)。

 (2)計算特征A對數據集D的經驗條件H(D|A)

(3)計算信息增益

5.4  信息增益比(information gain ratio)

  以信息增益作為特征選擇准則,會存在偏向於選擇取值較多的特征的問題,可以采用信息增益比對這個問題進行校正。

  特征A對訓練數據集D的信息增益比定義為其信息增益與訓練集D關於特征A的值的熵之比,即:

其中:

6,計算實例二的信息增益

  我們下面接着案例二來繼續,首先計算未分類前的熵,總共有8位同學,男生3位,女生5位。 

  • 熵(總)=-3/8*log2(3/8)-5/8*log2(5/8)=0.9544 

  接着分別計算同學A和同學B分類后信息熵。
  同學A首先按頭發分類,分類后的結果為:長頭發中有1男3女。短頭發中有2男2女。 

  • 熵(同學A長發)=-1/4*log2(1/4)-3/4*log2(3/4)=0.8113 
  • 熵(同學A短發)=-2/4*log2(2/4)-2/4*log2(2/4)=1 
  • 熵(同學A)=4/8*0.8113+4/8*1=0.9057 

信息增益(同學A)=熵(總)-熵(同學A)=0.9544-0.9057=0.0487
  同理,按同學B的方法,首先按聲音特征來分,分類后的結果為:聲音粗中有3男3女。聲音細中有0男2女。 

  • 熵(同學B聲音粗)=-3/6*log2(3/6)-3/6*log2(3/6)=1 
  • 熵(同學B聲音粗)=-2/2*log2(2/2)=0 
  • 熵(同學B)=6/8*1+2/8*0=0.75 

信息增益(同學B)=熵(總)-熵(同學A)=0.9544-0.75=0.2087

  按同學B的方法,先按聲音特征分類,信息增益更大,區分樣本的能力更強,更具有代表性。 
以上就是決策樹ID3算法的核心思想。 

  代碼如下:

from math import log
import operator

# 計算數據的熵(entropy)
def calcShannonRnt(dataSet):
    # 數據條數,計算數據集中實例的總數
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:
        # 每行數據的最后一個類別(也就是標簽)
        currentLable = featVec[-1]
        if currentLable not in labelCounts.keys():
            labelCounts[currentLable] = 0
        # 統計有多少個類以及每個類的數量
        labelCounts[currentLable]  += 1
    shannonEnt = 0.0
    for key in labelCounts:
        # 計算單個類的熵值
        prob = float(labelCounts[key]) / numEntries
        # 累加每個類的熵值
        shannonEnt -= prob * log(prob , 2)
    return shannonEnt

# 創建數據集
def createDataSet():
    dataSet = [['Long', 'Think', 'male'],
               ['Short', 'Think', 'male'],
               ['Short', 'Think', 'male'],
               ['Long', 'Thin', 'female'],
               ['Short', 'Thin', 'female'],
               ['Short', 'Think', 'female'],
               ['Long', 'Think', 'female'],
               ['Long', 'Think', 'female']]
    labels = ['hair', 'voice']
    return dataSet,labels

# 按照給定特征划分數據集
def splitDataSet(dataSet,axis,value):
    '''

    :param dataSet: 待划分的數據集
    :param axis: 划分數據集的特征
    :param value: 特征的返回值
    :return:
    '''
    retDataSet = []
    for featVec in dataSet:
        # 如果發現符合要求的特征,將其添加到新創建的列表中
        if featVec[axis] == value:
            reduceFeatVec = featVec[:axis]
            reduceFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reduceFeatVec)

    return retDataSet

# 選擇最好的數據集划分方式
def chooseBestFeatureTpSplit(dataSet):
    '''
        此函數中調用的數據滿足以下要求
        1,數據必須是一種由列表元素組成的列表,而且所有列表元素都要具有相同的數據長度
        2,數據的最后一列或者實例的最后一個元素是當前實例的類別標簽
    :param dataSet:
    :return:
    '''
    numFeatures = len(dataSet[0]) - 1
    # 原始的熵
    baseEntropy = calcShannonRnt(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 * calcShannonRnt(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
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(),
                              key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

# 創建樹的函數代碼
def createTree(dataSet,labels):
    '''

        :param dataSet:  輸入的數據集
        :param labels:  標簽列表(包含了數據集中所有特征的標簽)
        :return:
        '''
    # classList 包含了數據集中所有類的標簽
    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 = chooseBestFeatureTpSplit(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 =labels[:]
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet,bestFeat,value),
                                                  subLabels)

    return myTree

if __name__ == '__main__':
    myData,labels = createDataSet()
    print(myData)
    print(labels)
    
    myTree = createTree(myData,labels)
    print(myTree)
    
'''
[['Long', 'Think', 'male'], ['Short', 'Think', 'male'], ['Short', 'Think', 'male'], ['Long', 'Thin', 'female'], ['Short', 'Thin', 'female'], ['Short', 'Think', 'female'], ['Long', 'Think', 'female'], ['Long', 'Think', 'female']]
['hair', 'voice']
{'voice': {'Think': {'hair': {'Long': 'female', 'Short': 'male'}}, 'Thin': 'female'}}
'''

  這個結果的意思是:首先按聲音分類,聲音細為女生;然后再按頭發分類:聲音粗,頭發短為男生;聲音粗,頭發長為女生。 
  這個結果也正是同學B的結果。 

  使用Python中Matplotlib繪圖如下(可以看出與B同學的結果一模一樣):

  畫圖的代碼如下:

import matplotlib.pyplot as plt

# 定義文本框和箭頭格式(樹節點格式的常量)
decisionNode = dict(boxstyle='sawtooth',fc='0.8')
leafNode = dict(boxstyle='round4',fc='0.8')
arrows_args = dict(arrowstyle='<-')

# 繪制帶箭頭的注解
def plotNode(nodeTxt,centerPt,parentPt,nodeType):
    createPlot.ax1.annotate(nodeTxt,xy=parentPt,
                            xycoords='axes fraction',
                            xytext=centerPt,textcoords='axes fraction',
                            va='center',ha='center',bbox=nodeType,
                            arrowprops=arrows_args)


# 在父子節點間填充文本信息
def plotMidText(cntrPt,parentPt,txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    createPlot.ax1.text(xMid,yMid,txtString, va="center", ha="center", rotation=30)

def plotTree(myTree,parentPt,nodeTxt):
    # 求出寬和高
    numLeafs = getNumLeafs(myData)
    depth = getTreeDepth(myData)
    firstStides = list(myTree.keys())
    firstStr = firstStides[0]
    # 按照葉子結點個數划分x軸
    cntrPt = (plotTree.xOff + (0.1 + float(numLeafs)) /2.0/plotTree.totalW,plotTree.yOff)
    plotMidText(cntrPt,parentPt,nodeTxt)
    plotNode(firstStr,cntrPt,parentPt,decisionNode)
    secondDict = myTree[firstStr]
    # y方向上的擺放位置 自上而下繪制,因此遞減y值
    plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__  == 'dict':
            plotTree(secondDict[key],cntrPt,str(key))
        else:
            plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalW  # x方向計算結點坐標
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)  # 繪制
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))  # 添加文本信息
    plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD  # 下次重新調用時恢復y

def myTree():
    treeData = {'voice': {'Think': {'hair': {'Long': 'female', 'Short': 'male'}}, 'Thin': 'female'}}
    return treeData

# 獲取葉子節點的數目
def getNumLeafs(myTree):
    # 初始化結點數
    numLeafs = 0
    firstSides = list(myTree.keys())
    # 找到輸入的第一個元素,第一個關鍵詞為划分數據集類別的標簽
    firstStr = firstSides[0]
    secondDect = myTree[firstStr]
    for key in secondDect.keys():
        # 測試節點的數據類型是否為字典
        if type(secondDect[key]).__name__ == 'dict':
            numLeafs += getNumLeafs(secondDect[key])
        else:
            numLeafs +=1
    return numLeafs

# 獲取樹的層數
def getTreeDepth(myTree):
    maxDepth = 0
    firstSides = list(myTree.keys())
    firstStr = firstSides[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        # 測試節點的數據類型是否為字典
        if type(secondDict[key]).__name__ == 'dict':
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:
            thisDepth = 1
        if thisDepth > maxDepth:
            maxDepth = thisDepth

    return maxDepth

# 主函數
def createPlot(inTree):
    # 創建一個新圖形並清空繪圖區
    fig = plt.figure(1,facecolor='white')
    fig.clf()
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)  # no ticks
    # createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5 / plotTree.totalW
    plotTree.yOff = 1.0
    plotTree(inTree, (0.5, 1.0), '')
    plt.show()


if __name__ == '__main__':
    myData  = myTree()
    myData['voice'][3] = 'maybe'
    print(myData)
    # LeafNum = getNumLeafs(myData)
    # TreeDepth = getTreeDepth(myData)
    # print(LeafNum)
    # print(TreeDepth)
    createPlot(myData)

  補充說明:判定分類結束的依據是,若按某特征分類后出現了最終類(男或女),則判定分類結束。使用這種方法,在數據比較大,特征比較多的情況下,很容易造成過擬合,於是需進行決策樹枝剪,一般枝剪方法是當按某一特征分類后的熵小於設定值時,停止分類。

決策樹的生成算法介紹

  划分數據集的最大原則是:使無序的數據變的有序。如果一個訓練數據中有20個特征,那么選取哪個做划分依據?這就必須采用量化的方法來判斷,量化划分方法有多重,其中一項就是“信息論度量信息分類”。基於信息論的決策樹算法有ID3、CART和C4.5等算法,其中C4.5和CART兩種算法從ID3算法中衍生而來。

  決策樹的生成算法由很多變形,這里簡單說一下幾種經典的實現算法:ID3算法,C4.5算法和CART算法。這些算法的主要區別在於分類結點熵特征選擇的選取標准不同,下面了解一下算法的具體實現過程。

一:ID3算法

  ID3算法所采用的度量標准就是我們前面提到的“信息增益”。當屬性a的信息增益最大時,則意味着用a屬性划分,其所獲得的“純度”提升最大,我們所要做的,就是找到信息增益最大的屬性。

  ID3算法的核心是在決策樹的各個節點上應用信息增益准則進行特征選擇,具體的做法是:

  • 從根節點上開始,對結點計算所有可能特征的信息增益,選擇信息增益最大的特征作為結點的特征,並由該特征的不同取值構建子節點;
  • 對於子節點遞歸的調用以上方法,構建決策樹;
  • 直到所有特征的信息增益均很小或者沒有特征可選擇的時候為止。

  ID3算法具體的算法過程如下:

  輸入的是 m 個樣本,樣本輸出集合為D,每個樣本有 n 個離散特征,特征集合為A,輸出為決策樹 T。

  • 1,初始化信息增益的閾值€
  • 2,判斷樣本是否為同一類輸出 Di,如果是則返回單節點樹T,標記類別為Di
  • 3,判斷特征是否為空,如果是則返回單節點樹T,標記類別為樣本值紅輸出類別D實例數最多的類別
  • 4,計算A 中的各個特征(一共 n 個)對輸出D的信息增益,選擇信息增益最大的特征 Ag
  • 5,如果Ag的信息增益小於閾值€,則返回單節點樹T,標記類別為樣本中輸出類別D實例樹最多的類別
  • 6,否則,按特征Ag 的不同取值 Agi 將對應的樣本輸出D分成不同的類別 Di,每個類別產生一個子節點。對應特征為 Agi,返回增加了節點的數 T
  • 7,對於所有的子節點,令 D= Di,A=A-{Ag} 遞歸調用 2~6 步,得到子樹Ti並返回

  ID3算法存在的缺點: 

  1. ID3算法在選擇根節點和內部節點中的分支屬性時,采用信息增益作為評價標准。信息增益的缺點是傾向於選擇取值較多是屬性,在有些情況下這類屬性可能不會提供太多有價值的信息。 
  2. ID3算法只能對描述屬性為離散型屬性的數據集構造決策樹 。

  3. ID3算法對於缺失值的情況沒做考慮。

  4.沒有考慮過擬合的問題。

1.1  信息增益(Info-Gain)和信息增益比(Gain-ratio)的關系和差異

  參考文獻:https://www.zhihu.com/question/22928442

  對於缺點一,我們知道當信息增益偏向於選擇取值較多的特征,則容易過擬合。如何理解呢?就是說什么叫取值越多會導致信息增益越大?這里使用案例(天氣預報數據集):

   在這個例子中,最后一列是否出去玩,這里作為我們所要預測的標記值(label),而前四列就是我們需要借助的數據,每一列都是一個特征(feature)。對於這四種划分方式,那么問題就是誰來當根節點呢?

   下面我們對四個特征逐一分析,

  我們使用信息增益來算。初始狀態下,label列總共為 14列,有9個yes和5個no,所以label列初始信息熵為:

  假設我們先划分 outlook 這一列,分為sunny , rain, overcast 三類,計算過程在后面代碼里面,這里直接粘貼結果:

  根據數值統計,Outlook取值分別為sunny , rain, overcast 三類的概率分別為5/14  5/14   4/14考慮到每個類別下對應的label不同,可以計算划分后的信息比:

   信息增益:系統的熵值由原來的0.940 下降到0.693,增益為 0.247。

  我們可以用同樣的方式來計算其他特征的信息增益,那么我們選擇最大的就可以了,相當於我們遍歷了一遍特征,找出根節點,然后繼續找其余的特征的根節點。

  在ID3算法中,信息增益(Info-Gain)越大,划分越好,決策樹算法本質上就是找出每一列的最佳划分以及不同列划分的先后順序以及排布

  回到題中的問題,C4.5 中使用信息增益率(Gain-ration),ID3算法使用信息增益(Info-Gain),二者有何區別?

  根據上面的例子,Info-Gain在面對類別較少的離散數據時效果較好,上面的 Outlook,temperature等數據都是離散數據,而且每個類別都要一定數量的樣本,這種情況下使用ID3與C4.5的區別並不大。但如果面對連續的數據(如體重,身高,年齡,距離等),或者每列數據沒有明顯的類別之分(最極端的例子就是該列的數據都是獨一無二),在這種情況下,我們分析信息增益(Info-Gain)的效果:

  根據公式:

  E(S)為初始 label 列的熵,並未發生變換,則 IGain(S, A)的大小取決於 E(A)的大小,E(A)越小,IGain(S, A)越大,而根據前文例子:

   若某一列數據沒有重復,ID3算法傾向於把每個數據自成一類,此時:

  注意這里E(Sv)的計算(值為什么等於1 ,熵為什么等於0):

  (這里我太蠢了,混淆了概念,我們這里其實計算的是某一划分下對應的右邊label列的熵,最容易理解的例子就是上面我們算的基於天氣的划分中overcast的計算,如果n行分為n類,則右邊label列只對應一個元素,那么熵為0。我還將E(Sv)寫出來找原因。看來這個例子真的是個好例子,我真的。。。。)

   這樣E(A) 為最小,IGain(S,A)最大,程序會傾向於選擇這種划分,這種划分效果極差

  所以為了解決這個問題,引入了信息增益率(Gain-ratio)的概念,計算方式如下:

   這里Info為划分行為帶來的信息,信息增益率如下計算:

   這樣就能減輕了划分行為本身的影響。

  所以為了改進決策樹,又提出了C4.5算法和CART算法。

  注意:這里還有一個坑!就是在計算特征的每個取值下的熵的時候,需要估計每個類別k的概率,我們使直接用頻率去近似的概率。

  這個我覺得有必要引出來說一下(個人拙見):根據大數定理,只有當樣本數足夠多的時候,頻率才可以准確的近似概率。也就是說,樣本數越少,對概率的估計結果的方差就會越大(想象一下做拋硬幣實驗來近似正面向上的概率,如果只拋兩次,那么得到的正面向上的概率可能會非常離譜,而如果拋一萬次,不論何時何地幾乎總能得到近似0.5的概率)。而方差大概會導致什么結果呢?顯然就會導致該取值下的類別錯估計為非均勻分布,而非均勻分布,不就是導致該取值下的熵更小。

  所以說,ID3決策樹,或者說信息增益(條件熵)並不是說一定會偏向取值多的特征,而是數據集的不充分以及客觀存在的大數定理導致取值多的特征在計算條件熵時容易估計出偏小的條件熵。如果數據集足夠大,均分到某特征的每個取值的樣本足夠多,那么這時信息增益是沒有偏向性的。

  計算熵的小公式如下:

from math import log

# 初始熵值
Entropy_base = -(9 / 14) * log(9 / 14, 2) - (5 / 14) * log(5 / 14, 2)
# outlook=sunny
Entropy_Outlook_sunny = -(2 / 5) * log(2 / 5, 2) - (3 / 5) * log(3 / 5, 2)
# outlook=overcost
Entropy_Outlook_overcost = -(4 / 4) * log(4 / 4, 2)
# outlook=rainy
Entropy_Outlook_rainy = -(3 / 5) * log(3 / 5, 2) - (2 / 5) * log(2 / 5, 2)
print(Entropy_Outlook_sunny, Entropy_Outlook_overcost, Entropy_Outlook_rainy)
# 0.9709505944546686 -0.0 0.9709505944546686
Entropy_Outlook = (5 / 14) * Entropy_Outlook_sunny + (4 / 14) * Entropy_Outlook_overcost + (
        5 / 14) * Entropy_Outlook_rainy
print('Entropy_Outlook:', Entropy_Outlook)
Entropy_test = -(1 / 2) * log(1 / 2, 2) - (1 / 2) * log(1 / 2, 2)
Entropy_test1 = -(1 / 3) * log(1 / 3, 2) - (1 / 3) * log(1 / 3, 2) - (1 / 3) * log(1 / 3, 2)
Entropy_test2 = -(1 / 4) * log(1 / 4, 2) - (1 / 4) * log(1 / 4, 2) - (1 / 4) * log(1 / 4, 2) - (1 / 4) * log(1 / 4, 2)
print(Entropy_test, Entropy_test1, Entropy_test2)
# 1.0     1.584962500721156     2.0

 

  ID3算法代碼實踐博文:請點擊我

二:C4.5算法

  ID3算法的作者昆蘭基於上面的不足,對ID3算法做了改進,這就是C4.5算法,也許你會問,為什么不叫ID4,ID5之類的名字呢?那是因為決策樹當時太火爆了,它的ID3一出來,別人二次創新,很快就占了ID4,ID5,所以他另辟蹊徑,取名C4.5 算法,后來的進化版為C4.5算法。

  C4.5算法與ID3算法的區別主要是在於它在生產決策樹的過程中,使用信息增益比來進行特征選擇。

  實際上,信息增益准則對於可取值數目較多的屬性會有所偏好,為了減少這種偏好可能帶來的不利影響,C4.5決策樹算法不直接使用信息增益,而是使用“信息增益率”來選擇最優划分屬性,信息增益率定義為:

  其中,分子為信息增益,分母為屬性X的熵。

  需要注意的是,增益率准則對可取值數目較少的屬性有所偏好。所以一般這樣選取划分屬性:先從候選屬性中找到信息增益高於平均水平的屬性,再從中選擇增益率最高的。

  雖然C4.5 改善了ID3算法的幾個問題,仍然有優化的空間。

  • 1,由於決策樹算法非常容易過擬合,因此對於生成的決策樹必須要進行剪枝。剪枝的算法有非常多,C4.5的剪枝方法有優化的空間。思路主要有兩種:一種是預剪枝,即在生成決策樹的時候就決定是否剪枝。另一個是后剪枝,即先生成決策樹,再通過交叉驗證來剪枝。
  • 2,C4.5生成的是多叉樹,即一個父節點可以有多個節點。很多時候,在計算機中二叉樹模型會比多叉樹運算效率高。如果采用二叉樹,可以提高效率。
  • 3,C3.5算法只能用於分類,如果將決策樹用於回歸的話,可以擴大它的使用范圍。
  • 4,C4.5算法由於使用了熵模型,里面有大量的耗時的對數運算,如果是連續紙還有大量的排序運算。如果能夠加以模型簡化可以減少運算強度但不犧牲太多准確性的話,那就更好了。

三:CART算法

  分類與回歸樹(classification and regression tree,CART)與C4.5算法一樣,由ID3算法演化而來。CART假設決策樹是一個二叉樹,它通過遞歸地二分每個特征,將特征空間划分為有限個單元,並在這些單元上確定預測的概率分布。

  上面所說的C4.5的幾個缺點在CART樹里面加以改進。所以如果不考慮集成學習的話,在普通的決策樹算法里,CART算法就是比較優的算法了。sklearn的決策樹使用的也是CART算法。

  CART算法中,對於回歸樹,采用的是平方誤差最小化准則;對於分類樹,采用基尼指數最小化准則

平方誤差最小化

  假設已將輸入空間划分為M個單元R1,R2......Rm,並且在每個單元Rm上有一個固定的輸出值Cm,於是回歸樹可以表示為:

  當輸入空間的划分確定時,可以用平方誤差來表示回歸樹對於訓練數據的預測誤差。

基尼指數

  分類問題中,假設有K個類別,樣本點屬於第k類的概率為pk,則概率分布的基尼指數定義為:

  CART算法代碼實踐博文:請點擊我

決策樹的優缺點分析

優點:

  • 易於理解和解釋,甚至比線性回歸更直觀;
  • 與人類做決策思考的思維習慣契合;
  • 模型可以通過樹的形式進行可視化展示;
  • 可以直接處理非數值型數據,不需要進行啞變量的轉換,甚至可以直接處理含有缺失值的數據;

缺點:

  • 對於有大量數值型輸入和輸出的問題,決策樹未必是一個好的選擇;
  • 特別是當數值型變量之間存在許多錯綜復雜的關系,如金融數據分析;
  • 決策分類的因素取決於更多變量的復雜組合時;
  • 模型不夠穩健,某一個節點的小小變化可能導致整個樹會有很大的不同

 

完整代碼及其數據,請移步小編的GitHub

  傳送門:請點擊我

  如果點擊有誤:https://github.com/LeBron-Jian/MachineLearningNote

 

參考: 
Machine Learning in Action 
統計學習方法

https://zhuanlan.zhihu.com/p/26703300

https://www.cnblogs.com/pinard/p/6050306.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM