參考書籍:《機器學習》(周志華)算法
說 明:本篇內容爲讀書筆記,主要參考教材爲《機器學習》(周志華)。詳細內容請參閱書籍——第4章 決策樹。部份內容參考網絡資源,在此感謝全部原創者的工做。網絡
=================================================================app
對於一個分支結點,若是該結點所包含的樣本都屬於同一類,那麼它的純度爲1,而咱們老是但願純度越高越好,也就是儘量多的樣本屬於同一類別。那麼如何衡量「純度」呢?由此引入「信息熵」的概念。框架
假定當前樣本集合D中第k類樣本所佔的比例爲pk(k=1,,2,...,|y|),則D的信息熵定義爲:機器學習
Ent(D) = -∑k=1 pk·log2 pk (約定若p=0,則log2 p=0)函數
顯然,Ent(D)值越小,D的純度越高。由於0<=pk<= 1,故log2 pk<=0,Ent(D)>=0. 極限狀況下,考慮D中樣本同屬於同一類,則此時的Ent(D)值爲0(取到最小值)。當D中樣本都分別屬於不一樣類別時,Ent(D)取到最大值log2 |y|.工具
假定離散屬性a有V個可能的取值{a1,a2,...,aV}. 若使用a對樣本集D進行分類,則會產生V個分支結點,記Dv爲第v個分支結點包含的D中全部在屬性a上取值爲av的樣本。不一樣分支結點樣本數不一樣,咱們給予分支結點不一樣的權重:|Dv|/|D|, 該權重賦予樣本數較多的分支結點更大的影響、由此,用屬性a對樣本集D進行劃分所得到的信息增益定義爲:
學習
Gain(D,a) = Ent(D)-∑v=1 |Dv|/|D|·Ent(Dv)編碼
其中,Ent(D)是數據集D劃分前的信息熵,∑v=1 |Dv|/|D|·Ent(Dv)能夠表示爲劃分後的信息熵。「前-後」的結果代表了本次劃分所得到的信息熵減小量,也就是純度的提高度。顯然,Gain(D,a) 越大,得到的純度提高越大,這次劃分的效果越好。spa
基於信息增益的最優屬性劃分原則——信息增益準則,對可取值數據較多的屬性有所偏好。C4.5算法使用增益率替代信息增益來選擇最優劃分屬性,增益率定義爲:
Gain_ratio(D,a) = Gain(D,a)/IV(a)
其中
IV(a) = -∑v=1 |Dv|/|D|·log2 |Dv|/|D|
稱爲屬性a的固有值。屬性a的可能取值數目越多(即V越大),則IV(a)的值一般會越大。這在必定程度上消除了對可取值數據較多的屬性的偏好。
事實上,增益率準則對可取值數目較少的屬性有所偏好,C4.5算法並非直接使用增益率準則,而是先從候選劃分屬性中找出信息增益高於平均水平的屬性,再從中選擇增益率最高的。
CART決策樹算法使用基尼指數來選擇劃分屬性,基尼指數定義爲:
Gini(D) = ∑k=1 ∑k'≠1 pk·pk' = 1- ∑k=1 pk·pk
能夠這樣理解基尼指數:從數據集D中隨機抽取兩個樣本,其類別標記不一致的機率。Gini(D)越小,純度越高。
屬性a的基尼指數定義:
Gain_index(D,a) = ∑v=1 |Dv|/|D|·Gini(Dv)
使用基尼指數選擇最優劃分屬性,即選擇使得劃分後基尼指數最小的屬性做爲最優劃分屬性。
採用Python做爲實現工具,以書籍中的西瓜數據爲例,構造一棵「watermelon tree」。這裏,咱們構建的是一棵基於信息增益準則的決策樹,比較簡單,適合初學。
此處先略。^_^
代碼框架參考了部分網絡資源,而後就是悶頭去寫了。本質上都是大同小異,重要的仍是抱着學習的心態,去自主實現一下,才能對決策樹有更多的思考。
本案例基於教材《機器學習》P76表4.1 西瓜數據集2.0,嘗試用Python實現決策樹構建。一共17條樣本數據。理論上,創建的樹應該和P78圖4.4一致。
樣本數據截圖以下:
(一)導入模塊部分
#導入模塊 import pandas as pd import numpy as np from collections import Counter from math import log2
用pandas模塊的read_excel()函數讀取數據文本;用numpy模塊將dataframe轉換爲list(列表);用Counter來完成計數;用math模塊的log2函數計算對數。後邊代碼中會有對應體現。
(二)數據獲取與處理函數
#數據獲取與處理 def getData(filePath): data = pd.read_excel(filePath) return data def dataDeal(data): dataList = np.array(data).tolist() dataSet = [element[1:] for element in dataList] return dataSet
getData()經過pandas模塊中的read_excel()函數讀取樣本數據。嘗試過將數據文件保存爲csv格式,可是對於中文的處理不是很好,因此選擇了使用xls格式文件。
dataDeal()函數將dataframe轉換爲list,而且去掉了編號列。編號列並非西瓜的屬性,事實上,若是把它當作屬性,會得到最大的信息增益。
這兩個函數是徹底能夠合併爲同一個函數的,可是由於我想分別使用data(dataframe結構,帶屬性標籤)和dataSet(list)數據樣本,因此分開寫了兩個函數。
(三)獲取屬性名稱
#獲取屬性名稱 def getLabels(data): labels = list(data.columns)[1:-1] return labels
很簡單,獲取屬性名稱:紋理,色澤,根蒂,敲聲,臍部,觸感。
(四)獲取類別標記
#獲取類別標記 def targetClass(dataSet): classification = set([element[-1] for element in dataSet]) return classification
獲取一個樣本是否好瓜的標記(是與否)。
(五)葉結點標記
#將分支結點標記爲葉結點,選擇樣本數最多的類做爲類標記 def majorityRule(dataSet): mostKind = Counter([element[-1] for element in dataSet]).most_common(1) majorityKind = mostKind[0][0] return majorityKind
(六)計算信息熵
#計算信息熵 def infoEntropy(dataSet): classColumnCnt = Counter([element[-1] for element in dataSet]) Ent = 0 for symbol in classColumnCnt: p_k = classColumnCnt[symbol]/len(dataSet) Ent = Ent-p_k*log2(p_k) return Ent
(七)子數據集構建
#子數據集構建 def makeAttributeData(dataSet,value,iColumn): attributeData = [] for element in dataSet: if element[iColumn]==value: row = element[:iColumn] row.extend(element[iColumn+1:]) attributeData.append(row) return attributeData
在某一個屬性值下的數據,好比紋理爲清晰的數據集。
(八)計算信息增益
#計算信息增益 def infoGain(dataSet,iColumn): Ent = infoEntropy(dataSet) tempGain = 0.0 attribute = set([element[iColumn] for element in dataSet]) for value in attribute: attributeData = makeAttributeData(dataSet,value,iColumn) tempGain = tempGain+len(attributeData)/len(dataSet)*infoEntropy(attributeData) Gain = Ent-tempGain return Gain
(九)選擇最優屬性
#選擇最優屬性 def selectOptimalAttribute(dataSet,labels): bestGain = 0 sequence = 0 for iColumn in range(0,len(labels)):#不計最後的類別列 Gain = infoGain(dataSet,iColumn) if Gain>bestGain: bestGain = Gain sequence = iColumn print(labels[iColumn],Gain) return sequence
(十)創建決策樹
#創建決策樹 def createTree(dataSet,labels): classification = targetClass(dataSet) #獲取類別種類(集合去重) if len(classification) == 1: return list(classification)[0] if len(labels) == 1: return majorityRule(dataSet)#返回樣本種類較多的類別 sequence = selectOptimalAttribute(dataSet,labels) print(labels) optimalAttribute = labels[sequence] del(labels[sequence]) myTree = {optimalAttribute:{}} attribute = set([element[sequence] for element in dataSet]) for value in attribute: print(myTree) print(value) subLabels = labels[:] myTree[optimalAttribute][value] = \ createTree(makeAttributeData(dataSet,value,sequence),subLabels) return myTree
樹自己並不複雜,採用遞歸的方式實現。
(十一)定義主函數
def main(): filePath = 'watermelonData.xls' data = getData(filePath) dataSet = dataDeal(data) labels = getLabels(data) myTree = createTree(dataSet,labels) return myTree
主函數隨便寫寫了,主要是實現功能。
(十二)生成樹
if __name__ == '__main__': myTree = main()
Python實現並無很複雜的東西,只要能很好的理解遞歸在這裏是如何體現的就足夠了。
在構造樹的時候,這裏的樹定義爲一個嵌套的字典(dict)結構,樹根對應的屬性是字典最外層的關鍵字,其值是仍一個字典。遞歸就是這樣用下一層返回的樹做爲上一層樹某個分支(字典的關鍵字)的值,一層層往下(一棵倒樹)填充,直至遇到葉結點。在定義的構造樹函數中,終止條件(兩個if)是很重要的,決定了遞歸在何時中止,也就是樹在何時中止生長。
生成的樹(字典結構)以下:
一個字典結構的樹是極其不友好的,暫時沒有將其可視化,後續會學習一下。
從結果看,根節點的屬性是紋理。紋理爲稍糊的,下一個結點的屬性是觸感,觸感爲軟粘的瓜,判斷爲好瓜(紋理爲稍糊且觸感爲軟粘的瓜),觸感爲硬滑的瓜,斷定爲壞瓜(紋理爲稍糊且觸感爲硬滑的瓜)。紋理爲模糊的,直接斷定爲壞瓜(買瓜的要注意了);紋理爲清晰的情形較爲複雜。紋理爲清晰的,下一個結點屬性爲根蒂,對於根蒂爲硬挺的,判斷爲壞瓜(紋理爲清晰且根蒂爲硬挺的瓜),根蒂爲蜷縮的,判斷爲好瓜(紋理爲清晰且根蒂爲蜷縮的瓜)。根蒂爲稍蜷的,下一個結點的屬性是色澤,對於色澤爲青綠的,判斷爲好瓜(紋理爲清晰,根蒂爲稍蜷且色澤爲青綠的瓜),對於色澤爲烏黑的,下一個結點屬性是觸感,對於觸感爲軟粘的,斷定爲壞瓜(紋理爲清晰,根蒂爲稍蜷,色澤爲烏黑且觸感爲軟粘的瓜),對於觸感爲硬滑的,斷定爲好瓜(紋理爲清晰,根蒂爲稍蜷,色澤爲烏黑且觸感爲硬滑的瓜)。這裏有一個小的問題,一下子再說。
先看《機器學習》教材上給出的樹:
咱們得到的結果和書本中的結果基本是一致的,惟一的一個區別是咱們缺乏一個葉——色澤爲淺白的葉。這是由於,樣本數據中不存在紋理爲清晰、根蒂爲稍蜷且色澤爲淺白的瓜,致使在生成樹的時候少了一個葉。這種狀況須要特殊處理,好比處理成父類的類別。這裏沒有多作處理,該機制添加進去並不難。
熱烈慶祝在帝都正式工做的第100天!(✿✿ヽ(°▽°)ノ✿)
(晚上加個班的好處就是能夠看看書,作點本身喜歡的事——回家吃飯(#^.^#))