原文連接:cuijiahua.com/blog/2017/1…html
有讀者反映,說我上篇文章機器學習實戰教程(一):k-近鄰算法(史詩級乾貨長文),太長了。一看那麼長,讀的慾望都下降了。既然如此,決策樹的內容,我就分開講好了。本篇討論決策樹的原理和決策樹構建的準備工做,完整實例內容會在下一篇文章進行講解。node
本文出現的全部代碼,都可在個人github上下載,歡迎Follow、Star:Github代碼地址python
決策樹是什麼?決策樹(decision tree)是一種基本的分類與迴歸方法。舉個通俗易懂的例子,以下圖所示的流程圖就是一個決策樹,長方形表明判斷模塊(decision block),橢圓造成表明終止模塊(terminating block),表示已經得出結論,能夠終止運行。從判斷模塊引出的左右箭頭稱做爲分支(branch),它能夠達到另外一個判斷模塊或者終止模塊。咱們還能夠這樣理解,分類決策樹模型是一種描述對實例進行分類的樹形結構。決策樹由結點(node)和有向邊(directed edge)組成。結點有兩種類型:內部結點(internal node)和葉結點(leaf node)。內部結點表示一個特徵或屬性,葉結點表示一個類。蒙圈沒??以下圖所示的決策樹,長方形和橢圓形都是結點。長方形的結點屬於內部結點,橢圓形的結點屬於葉結點,從結點引出的左右箭頭就是有向邊。而最上面的結點就是決策樹的根結點(root node)。這樣,結點說法就與模塊說法對應上了,理解就好。git
咱們回到這個流程圖,對,你沒看錯,這就是一個假想的相親對象分類系統。它首先檢測相親對方是否有房。若是有房,則對於這個相親對象能夠考慮進一步接觸。若是沒有房,則觀察相親對象是否有上進心,若是沒有,直接Say Goodbye,此時能夠說:"你人很好,可是咱們不合適。"若是有,則能夠把這個相親對象列入候選名單,好聽點叫候選名單,有點瑕疵地講,那就是備胎。github
不過這只是個簡單的相親對象分類系統,只是作了簡單的分類。真實狀況可能要複雜得多,考慮因素也能夠是五花八門。脾氣好嗎?會作飯嗎?願意作家務嗎?家裏幾個孩子?父母是幹什麼的?天啊,我不想再說下去了,想一想均可怕。算法
咱們能夠把決策樹當作一個if-then規則的集合,將決策樹轉換成if-then規則的過程是這樣的:由決策樹的根結點(root node)到葉結點(leaf node)的每一條路徑構建一條規則;路徑上內部結點的特徵對應着規則的條件,而葉結點的類對應着規則的結論。決策樹的路徑或其對應的if-then規則集合具備一個重要的性質:互斥而且完備。這就是說,每個實例都被一條路徑或一條規則所覆蓋,並且只被一條路徑或一條規則所覆蓋。這裏所覆蓋是指實例的特徵與路徑上的特徵一致或實例知足規則的條件。bash
使用決策樹作預測須要如下過程:數據結構
使用決策樹作預測的每一步驟都很重要,數據收集不到位,將會致使沒有足夠的特徵讓咱們構建錯誤率低的決策樹。數據特徵充足,可是不知道用哪些特徵好,將會致使沒法構建出分類效果好的決策樹模型。從算法方面看,決策樹的構建是咱們的核心內容。app
決策樹要如何構建呢?一般,這一過程能夠歸納爲3個步驟:特徵選擇、決策樹的生成和決策樹的修剪。機器學習
特徵選擇在於選取對訓練數據具備分類能力的特徵。這樣能夠提升決策樹學習的效率,若是利用一個特徵進行分類的結果與隨機分類的結果沒有很大差異,則稱這個特徵是沒有分類能力的。經驗上扔掉這樣的特徵對決策樹學習的精度影響不大。一般特徵選擇的標準是信息增益(information gain)或信息增益比,爲了簡單,本文使用信息增益做爲選擇特徵的標準。那麼,什麼是信息增益?在講解信息增益以前,讓咱們看一組實例,貸款申請樣本數據表。
但願經過所給的訓練數據學習一個貸款申請的決策樹,用於對將來的貸款申請進行分類,即當新的客戶提出貸款申請時,根據申請人的特徵利用決策樹決定是否批准貸款申請。
特徵選擇就是決定用哪一個特徵來劃分特徵空間。好比,咱們經過上述數據表獲得兩個可能的決策樹,分別由兩個不一樣特徵的根結點構成。
圖(a)所示的根結點的特徵是年齡,有3個取值,對應於不一樣的取值有不一樣的子結點。圖(b)所示的根節點的特徵是工做,有2個取值,對應於不一樣的取值有不一樣的子結點。兩個決策樹均可以今後延續下去。問題是:究竟選擇哪一個特徵更好些?這就要求肯定選擇特徵的準則。直觀上,若是一個特徵具備更好的分類能力,或者說,按照這一特徵將訓練數據集分割成子集,使得各個子集在當前條件下有最好的分類,那麼就更應該選擇這個特徵。信息增益就可以很好地表示這一直觀的準則。
什麼是信息增益呢?在劃分數據集以後信息發生的變化稱爲信息增益,知道如何計算信息增益,咱們就能夠計算每一個特徵值劃分數據集得到的信息增益,得到信息增益最高的特徵就是最好的選擇。
在能夠評測哪一個數據劃分方式是最好的數據劃分以前,咱們必須學習如何計算信息增益。集合信息的度量方式稱爲香農熵或者簡稱爲熵(entropy),這個名字來源於信息論之父克勞德·香農。
若是看不明白什麼是信息增益和熵,請不要着急,由於他們自誕生的那一天起,就註定會令世人十分費解。克勞德·香農寫完信息論以後,約翰·馮·諾依曼建議使用"熵"這個術語,由於你們都不知道它是什麼意思。
熵定義爲信息的指望值。在信息論與機率統計中,熵是表示隨機變量不肯定性的度量。若是待分類的事物可能劃分在多個分類之中,則符號xi的信息定義爲 :
其中p(xi)是選擇該分類的機率。有人可能會問,信息爲啥這樣定義啊?答曰:前輩得出的結論。這就跟1+1等於2同樣,記住而且會用便可。上述式中的對數以2爲底,也能夠e爲底(天然對數)。
經過上式,咱們能夠獲得全部類別的信息。爲了計算熵,咱們須要計算全部類別全部可能值包含的信息指望值(數學指望),經過下面的公式獲得:
期中n是分類的數目。熵越大,隨機變量的不肯定性就越大。
當熵中的機率由數據估計(特別是最大似然估計)獲得時,所對應的熵稱爲經驗熵(empirical entropy)。什麼叫由數據估計?好比有10個數據,一共有兩個類別,A類和B類。其中有7個數據屬於A類,則該A類的機率即爲十分之七。其中有3個數據屬於B類,則該B類的機率即爲十分之三。淺顯的解釋就是,這機率是咱們根據數據數出來的。咱們定義貸款申請樣本數據表中的數據爲訓練數據集D,則訓練數據集D的經驗熵爲H(D),|D|表示其樣本容量,及樣本個數。設有K個類Ck, = 1,2,3,...,K,|Ck|爲屬於類Ck的樣本個數,所以經驗熵公式就能夠寫爲 :
根據此公式計算經驗熵H(D),分析貸款申請樣本數據表中的數據。最終分類結果只有兩類,即放貸和不放貸。根據表中的數據統計可知,在15個數據中,9個數據的結果爲放貸,6個數據的結果爲不放貸。因此數據集D的經驗熵H(D)爲:
通過計算可知,數據集D的經驗熵H(D)的值爲0.971。
在編寫代碼以前,咱們先對數據集進行屬性標註。
肯定這些以後,咱們就能夠建立數據集,並計算經驗熵了,代碼編寫以下:
# -*- coding: UTF-8 -*-
from math import log
""" 函數說明:建立測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 分類屬性 Author: Jack Cui Modify: 2017-07-20 """
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], #數據集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['不放貸', '放貸'] #分類屬性
return dataSet, labels #返回數據集和分類屬性
""" 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Modify: 2017-03-29 """
def calcShannonEnt(dataSet):
numEntires = len(dataSet) #返回數據集的行數
labelCounts = {} #保存每一個標籤(Label)出現次數的字典
for featVec in dataSet: #對每組特徵向量進行統計
currentLabel = featVec[-1] #提取標籤(Label)信息
if currentLabel not in labelCounts.keys(): #若是標籤(Label)沒有放入統計次數的字典,添加進去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #Label計數
shannonEnt = 0.0 #經驗熵(香農熵)
for key in labelCounts: #計算香農熵
prob = float(labelCounts[key]) / numEntires #選擇該標籤(Label)的機率
shannonEnt -= prob * log(prob, 2) #利用公式計算
return shannonEnt #返回經驗熵(香農熵)
if __name__ == '__main__':
dataSet, features = createDataSet()
print(dataSet)
print(calcShannonEnt(dataSet))
複製代碼
代碼運行結果以下圖所示,代碼是先打印訓練數據集,而後打印計算的經驗熵H(D),程序計算的結果與咱們統計計算的結果是一致的,程序沒有問題。
在上面,咱們已經說過,如何選擇特徵,須要看信息增益。也就是說,信息增益是相對於特徵而言的,信息增益越大,特徵對最終的分類結果影響也就越大,咱們就應該選擇對最終分類結果影響最大的那個特徵做爲咱們的分類特徵。
在講解信息增益定義以前,咱們還須要明確一個概念,條件熵。
熵咱們知道是什麼,條件熵又是個什麼鬼?條件熵H(Y|X)表示在已知隨機變量X的條件下隨機變量Y的不肯定性,隨機變量X給定的條件下隨機變量Y的條件熵(conditional entropy)H(Y|X),定義爲X給定條件下Y的條件機率分佈的熵對X的數學指望:
這裏,
同理,當條件熵中的機率由數據估計(特別是極大似然估計)獲得時,所對應的條件熵稱爲條件經驗熵(empirical conditional entropy)。
明確了條件熵和經驗條件熵的概念。接下來,讓咱們說說信息增益。前面也提到了,信息增益是相對於特徵而言的。因此,特徵A對訓練數據集D的信息增益g(D,A),定義爲集合D的經驗熵H(D)與特徵A給定條件下D的經驗條件熵H(D|A)之差,即:
通常地,熵H(D)與條件熵H(D|A)之差稱爲互信息(mutual information)。決策樹學習中的信息增益等價於訓練數據集中類與特徵的互信息。
設特徵A有n個不一樣的取值{a1,a2,···,an},根據特徵A的取值將D劃分爲n個子集{D1,D2,···,Dn},|Di|爲Di的樣本個數。記子集Di中屬於Ck的樣本的集合爲Dik,即Dik = Di ∩ Ck,|Dik|爲Dik的樣本個數。因而經驗條件熵的公式能夠寫爲:
說了這麼多概念性的東西,沒有聽懂也沒有關係,舉幾個例子,再回來看一下概念,就懂了。
以貸款申請樣本數據表爲例進行說明。看下年齡這一列的數據,也就是特徵A1,一共有三個類別,分別是:青年、中年和老年。咱們只看年齡是青年的數據,年齡是青年的數據一共有5個,因此年齡是青年的數據在訓練數據集出現的機率是十五分之五,也就是三分之一。同理,年齡是中年和老年的數據在訓練數據集出現的機率也都是三分之一。如今咱們只看年齡是青年的數據的最終獲得貸款的機率爲五分之二,由於在五個數據中,只有兩個數據顯示拿到了最終的貸款,同理,年齡是中年和老年的數據最終獲得貸款的機率分別爲五分之3、五分之四。因此計算年齡的信息增益,過程以下:
同理,計算其他特徵的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分別爲:
最後,比較特徵的信息增益,因爲特徵A3(有本身的房子)的信息增益值最大,因此選擇A3做爲最優特徵。
咱們已經學會了經過公式計算信息增益,接下來編寫代碼,計算信息增益。
# -*- coding: UTF-8 -*-
from math import log
""" 函數說明:計算給定數據集的經驗熵(香農熵) Parameters: dataSet - 數據集 Returns: shannonEnt - 經驗熵(香農熵) Author: Jack Cui Modify: 2017-03-29 """
def calcShannonEnt(dataSet):
numEntires = len(dataSet) #返回數據集的行數
labelCounts = {} #保存每一個標籤(Label)出現次數的字典
for featVec in dataSet: #對每組特徵向量進行統計
currentLabel = featVec[-1] #提取標籤(Label)信息
if currentLabel not in labelCounts.keys(): #若是標籤(Label)沒有放入統計次數的字典,添加進去
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1 #Label計數
shannonEnt = 0.0 #經驗熵(香農熵)
for key in labelCounts: #計算香農熵
prob = float(labelCounts[key]) / numEntires #選擇該標籤(Label)的機率
shannonEnt -= prob * log(prob, 2) #利用公式計算
return shannonEnt #返回經驗熵(香農熵)
""" 函數說明:建立測試數據集 Parameters: 無 Returns: dataSet - 數據集 labels - 分類屬性 Author: Jack Cui Modify: 2017-07-20 """
def createDataSet():
dataSet = [[0, 0, 0, 0, 'no'], #數據集
[0, 0, 0, 1, 'no'],
[0, 1, 0, 1, 'yes'],
[0, 1, 1, 0, 'yes'],
[0, 0, 0, 0, 'no'],
[1, 0, 0, 0, 'no'],
[1, 0, 0, 1, 'no'],
[1, 1, 1, 1, 'yes'],
[1, 0, 1, 2, 'yes'],
[1, 0, 1, 2, 'yes'],
[2, 0, 1, 2, 'yes'],
[2, 0, 1, 1, 'yes'],
[2, 1, 0, 1, 'yes'],
[2, 1, 0, 2, 'yes'],
[2, 0, 0, 0, 'no']]
labels = ['不放貸', '放貸'] #分類屬性
return dataSet, labels #返回數據集和分類屬性
""" 函數說明:按照給定特徵劃分數據集 Parameters: dataSet - 待劃分的數據集 axis - 劃分數據集的特徵 value - 須要返回的特徵的值 Returns: 無 Author: Jack Cui Modify: 2017-03-30 """
def splitDataSet(dataSet, axis, value):
retDataSet = [] #建立返回的數據集列表
for featVec in dataSet: #遍歷數據集
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #去掉axis特徵
reducedFeatVec.extend(featVec[axis+1:]) #將符合條件的添加到返回的數據集
retDataSet.append(reducedFeatVec)
return retDataSet #返回劃分後的數據集
""" 函數說明:選擇最優特徵 Parameters: dataSet - 數據集 Returns: bestFeature - 信息增益最大的(最優)特徵的索引值 Author: Jack Cui Modify: 2017-03-30 """
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #特徵數量
baseEntropy = calcShannonEnt(dataSet) #計算數據集的香農熵
bestInfoGain = 0.0 #信息增益
bestFeature = -1 #最優特徵的索引值
for i in range(numFeatures): #遍歷全部特徵
#獲取dataSet的第i個全部特徵
featList = [example[i] for example in dataSet]
uniqueVals = set(featList) #建立set集合{},元素不可重複
newEntropy = 0.0 #經驗條件熵
for value in uniqueVals: #計算信息增益
subDataSet = splitDataSet(dataSet, i, value) #subDataSet劃分後的子集
prob = len(subDataSet) / float(len(dataSet)) #計算子集的機率
newEntropy += prob * calcShannonEnt(subDataSet) #根據公式計算經驗條件熵
infoGain = baseEntropy - newEntropy #信息增益
print("第%d個特徵的增益爲%.3f" % (i, infoGain)) #打印每一個特徵的信息增益
if (infoGain > bestInfoGain): #計算信息增益
bestInfoGain = infoGain #更新信息增益,找到最大的信息增益
bestFeature = i #記錄信息增益最大的特徵的索引值
return bestFeature #返回信息增益最大的特徵的索引值
if __name__ == '__main__':
dataSet, features = createDataSet()
print("最優特徵索引值:" + str(chooseBestFeatureToSplit(dataSet)))
複製代碼
splitDataSet函數是用來選擇各個特徵的子集的,好比選擇年齡(第0個特徵)的青年(用0表明)的本身,咱們能夠調用splitDataSet(dataSet,0,0)這樣返回的子集就是年齡爲青年的5個數據集。chooseBestFeatureToSplit是選擇選擇最優特徵的函數。運行代碼結果以下:
對比咱們本身計算的結果,發現結果徹底正確!最優特徵的索引值爲2,也就是特徵A3(有本身的房子)。
咱們已經學習了從數據集構造決策樹算法所須要的子功能模塊,包括經驗熵的計算和最優特徵的選擇,其工做原理以下:獲得原始數據集,而後基於最好的屬性值劃分數據集,因爲特徵值可能多於兩個,所以可能存在大於兩個分支的數據集劃分。第一次劃分以後,數據集被向下傳遞到樹的分支的下一個結點。在這個結點上,咱們能夠再次劃分數據。所以咱們能夠採用遞歸的原則處理數據集。
構建決策樹的算法有不少,好比C4.五、ID3和CART,這些算法在運行時並不老是在每次劃分數據分組時都會消耗特徵。因爲特徵數目並非每次劃分數據分組時都減小,所以這些算法在實際使用時可能引發必定的問題。目前咱們並不須要考慮這個問題,只須要在算法開始運行前計算列的數目,查看算法是否使用了全部屬性便可。
決策樹生成算法遞歸地產生決策樹,直到不能繼續下去未爲止。這樣產生的樹每每對訓練數據的分類很準確,但對未知的測試數據的分類卻沒有那麼準確,即出現過擬合現象。過擬合的緣由在於學習時過多地考慮如何提升對訓練數據的正確分類,從而構建出過於複雜的決策樹。解決這個問題的辦法是考慮決策樹的複雜度,對已生成的決策樹進行簡化。
本篇文章講解了如何計算數據集的經驗熵和如何選擇最優特徵做爲分類特徵。決策樹如何生成、修剪、可視化,以及總體實例練習,會在後續的文章中進行講解。
圓方圓學院聚集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:ke.qq.com/course/3627…
加入python學習討論羣 78486745 ,獲取資料,和廣大羣友一塊兒學習。