《機器學習》(周誌華)第4章 決策樹 筆記 理論及實現——“西瓜樹”
參考書籍:《機器學習》(周誌華)
說 明:本篇內容為讀書筆記,主要參考教材為《機器學習》(周誌華)。詳細內容請參閱書籍——第4章 決策樹。部分內容參考網絡資源,在此感謝所有原創者的工作。
=================================================================
第一部分 理論基礎
1. 純度(purity)
對於一個分支結點,如果該結點所包含的樣本都屬於同一類,那麽它的純度為1,而我們總是希望純度越高越好,也就是盡可能多的樣本屬於同一類別。那麽如何衡量“純度”呢?由此引入“信息熵”的概念。
2. 信息熵(information entropy)
假定當前樣本集合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
3. 信息增益(information gain)
假定離散屬性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
4. 增益率(gain ratio)
基於信息增益的最優屬性劃分原則——信息增益準則,對可取值數據較多的屬性有所偏好。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算法並不是直接使用增益率準則,而是先從候選劃分屬性中找出信息增益高於平均水平的屬性,再從中選擇增益率最高的。
5. 基尼指數(Gini index)
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”。這裏,我們構建的是一棵基於信息增益準則的決策樹,比較簡單,適合初學。
1. 算法
此處先略。^_^
2. Python代碼實現
代碼框架參考了部分網絡資源,然後就是悶頭去寫了。本質上都是大同小異,重要的還是抱著學習的心態,去自主實現一下,才能對決策樹有更多的思考。
2.1 數據樣本說明
本案例基於教材《機器學習》P76表4.1 西瓜數據集2.0,嘗試用Python實現決策樹構建。一共17條樣本數據。理論上,建立的樹應該和P78圖4.4一致。
樣本數據截圖如下:
2.2 實現代碼
(一)導入模塊部分
#導入模塊 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()
3.幾點說明
Python實現並沒有很復雜的東西,只要能很好的理解遞歸在這裏是如何體現的就足夠了。
在構造樹的時候,這裏的樹定義為一個嵌套的字典(dict)結構,樹根對應的屬性是字典最外層的關鍵字,其值是仍一個字典。遞歸就是這樣用下一層返回的樹作為上一層樹某個分支(字典的關鍵字)的值,一層層往下(一棵倒樹)填充,直至遇到葉結點。在定義的構造樹函數中,終止條件(兩個if)是很重要的,決定了遞歸在什麽時候停止,也就是樹在什麽時候停止生長。
生成的樹(字典結構)如下:
一個字典結構的樹是極其不友好的,暫時沒有將其可視化,後續會學習一下。
從結果看,根節點的屬性是紋理。紋理為稍糊的,下一個結點的屬性是觸感,觸感為軟粘的瓜,判斷為好瓜(紋理為稍糊且觸感為軟粘的瓜),觸感為硬滑的瓜,判定為壞瓜(紋理為稍糊且觸感為硬滑的瓜)。紋理為模糊的,直接判定為壞瓜(買瓜的要註意了);紋理為清晰的情形較為復雜。紋理為清晰的,下一個結點屬性為根蒂,對於根蒂為硬挺的,判斷為壞瓜(紋理為清晰且根蒂為硬挺的瓜),根蒂為蜷縮的,判斷為好瓜(紋理為清晰且根蒂為蜷縮的瓜)。根蒂為稍蜷的,下一個結點的屬性是色澤,對於色澤為青綠的,判斷為好瓜(紋理為清晰,根蒂為稍蜷且色澤為青綠的瓜),對於色澤為烏黑的,下一個結點屬性是觸感,對於觸感為軟粘的,判定為壞瓜(紋理為清晰,根蒂為稍蜷,色澤為烏黑且觸感為軟粘的瓜),對於觸感為硬滑的,判定為好瓜(紋理為清晰,根蒂為稍蜷,色澤為烏黑且觸感為硬滑的瓜)。這裏有一個小的問題,一會兒再說。
先看《機器學習》教材上給出的樹:
我們獲得的結果和書本中的結果基本是一致的,唯一的一個區別是我們缺少一個葉——色澤為淺白的葉。這是因為,樣本數據中不存在紋理為清晰、根蒂為稍蜷且色澤為淺白的瓜,導致在生成樹的時候少了一個葉。這種情況需要特殊處理,比如處理成父類的類別。這裏沒有多做處理,該機制添加進去並不難。
4.寫在最後
熱烈慶祝在帝都正式工作的第100天!(??ヽ(°▽°)ノ?)
(晚上加個班的好處就是可以看看書,做點自己喜歡的事——回家吃飯(#^.^#))
《機器學習》(周誌華)第4章 決策樹 筆記 理論及實現——“西瓜樹”