一、決策樹(ID3算法)及其python實現
總結一下前一陣對於決策樹的學習。爲了從算法的層次理解並熟練運用機器學習的多種算法,我首先從用Python編寫算法入手:
(一)決策樹的概念
決策樹屬於機器學習中的監督學習,也就是說,其數據集需要人工對feature與label標定,比如表1,天氣的各種屬性與天氣好壞的關係表:
項目 | Outlook(Feature1) | Temperature(Feature2) | Humidity(Feature3) | Windy(Feature4) | Good_State?(Label) |
---|---|---|---|---|---|
1 | sunny | hot | high | FALSE | no |
2 | sunny | hot | high | TRUE | no |
3 | sunny | hot | high | TRUE | no |
4 | overcast | hot | high | FALSE | yes |
5 | rainy | mild | high | FALSE | yes |
6 | rainy | cool | normal | FALSE | yes |
7 | rainy | cool | normal | TRUE | no |
8 | overcast | cool | normal | TRUE | yes |
9 | sunny | mild | high | FALSE | no |
10 | sunny | cool | normal | FALSE | yes |
11 | rainy | mild | normal | FALSE | yes |
12 | sunny | mild | normal | TRUE | yes |
13 | overcast | mild | high | TRUE | yes |
14 | overcast | hot | normal | FALSE | yes |
15 | rainy | mild | high | TRUE | no |
我們需要決策樹做的就是找到一種分類器根據天氣的四個feature判斷出其所屬State的標籤。
下面介紹決策樹:它從一組無次序、無規則的實例中推理出以決策樹表示的分類規則[1]。採用自頂向下的遞歸方式,在決策樹的內部節點(feature)進行屬性的比較(比如根據feature2比較天氣的溫度高低),並根據不同屬性值判斷從該節點向下的分支,在決策樹的葉節點得到結論。
如下圖1,我們可以根據此決策樹來表示決策邏輯。
(二)生成決策樹的算法
- ID3 (Iterative Dichotomiser 3) (基於信息論)
- C4.5(對ID3算法的改進,增加了對決策樹的修剪等)
- 隨機森林(Random Forest)
- 分類及迴歸樹(Classification And Regression Tree, CART) (基於最小GNIN指數)
- 多元自適應迴歸樣條(MARS)
- 梯度推進機(Gradient Boosting Machine, GBM)
(三)ID3算法介紹
1、過程
(1)出發點:哪一個屬性將在樹的根節點被測試?--> 分類能力最好的屬性
(2)對於這個屬性的每個可能值產生一個分支,然後將訓練樣例分配到樣例屬性值所對應的分支之下
(3)對每個節點不斷測試對其而言分類能力最好的屬性
(4)重複4過程直到所有屬性被測試
2、如何選擇分類能力最好的屬性?(ID3算法中利用信息論中的熵與信息增益)
(1)熵
高中物理我們學過熱力學第二定律:在孤立系統中,體系與環境沒有能量交換,體系總是自發地像混亂度增大的方向變化,總使整個系統的熵值增大。熵就是表示分子狀態混亂程度的物理量。
在信息論中,香農用熵的概念來描述信源的不確定度。我們可以用熵來表示數據集的純度,與熱熵類似,信息熵越小,數據集越純淨,即越多的數據具有相同的類別。
信息熵定義爲:Entropy(D) = -Σp(i)*log(p(i))
其中p(i)是數據集D中標籤label取值爲i的數據所佔比例(概率)。在這裏定義0log0=0,這裏的log以2爲底。
至於爲什麼信息熵要定義爲如上形式,可以參考《Pattern Recognition and Machine Learning》[2]
(2)信息增益
熵:表示隨機變量的不確定性。
條件熵:在一個條件下,隨機變量的不確定性。(類似條件概率的理解)
信息增益即爲 **熵 - 條件熵** ,即在一個條件下,信息不確定性減少的程度。
詳細的解釋可以參考這篇博客[3]。
於是信息增益定義爲:Gain(D,A) = Entropy(D) - Σ( |D(v)|/|D| * Entropy(D(v)))
其中,v屬於某個Feature(屬性)A的所有可能值的集合,D(v)是數據集D中FeatureA值爲v的子集。Entropy(D)是D未用FeatureA分割之前的熵,Entropy(D(v))是D用FeatureA值爲v分割之後的熵。
FeatureA的每一個可能取值都有一個熵(Entropy(D(v))),該熵的權重是取該FeatureA的可能值所佔數據在所有數據集D中的比例(|D(v)|/|D|)。
(3)選擇分類能力最好的節點(Feature)
選擇信息增益最大的屬性A作爲當前節點的決策Feature。
原因:熵刻畫了數據集的純度,數據集越混亂,熵就越大。熵越小,數據集越純淨,越多的數據具有相同的類別。當熵爲0時,數據集中的數據都相等。
FeatureA的信息增益就是按A來劃分數據集時,數據集能比原來純淨多少。通過不斷地劃分,得到儘可能純的節點,相當於降低數據集的熵。
當決策樹劃分到葉子節點時,其熵大多爲0(除了部分葉子節點中標籤還未一致,但數據集中所有Feature均已劃分完畢)。
3、僞代碼及例題
如果上述講解仍覺得不甚理解,可以去找一道關於決策樹的例題,手工計算信息熵及信息增益便會有更深的理解。
至於僞代碼及具體的從算法層面的編程流程,推薦《人工智能導論》鮑軍鵬、張選平編著。
4、Python實現(具體代碼及註釋已經附上)
# -*- coding: utf-8 -*-
"""
Created on Sun Nov 19 19:58:10 2017
@author: Lesley
"""
from math import log
#------------------------數據集-------------------------------
#四個因變量(屬性) -> feature : "Outlook","Temperature","Humidity","Windy"
#標籤 -> 是否出門 -> 'no','yes'
#-------------------------------------------------------------
def createDataSet():
dataSet=[["sunny","hot","high",'FALSE','no'],
["sunny","hot","high",'TRUE','no'],
["overcast","hot","high",'FALSE','yes'],
["rainy","mild","high",'FALSE','yes'],
["rainy","cool","normal",'FALSE','yes'],
["rainy","cool","normal",'TRUE','no'],
["overcast","cool","normal",'TRUE','yes'],
["sunny","mild","high",'FALSE','no'],
["sunny","cool","normal",'FALSE','yes'],
["rainy","mild","normal",'FALSE','yes'],
["sunny","mild","normal",'TRUE','yes'],
["overcast","mild","high",'TRUE','yes'],
["overcast","hot","normal",'FALSE','yes'],
["rainy","mild","high",'TRUE','no']
]
feature = ["Outlook","Temperature","Humidity","Windy"]
return dataSet, feature
#-------------計算數據集整體及各個feature的不同值的熵------------
def Entropy(dataSet):
sample_num = len(dataSet)
label_category = {}
for sample in dataSet:
if sample[-1] not in label_category:
label_category[sample[-1]] = 1
else:
label_category[sample[-1]] += 1 #各類別的數量
#print (label_category)
entropy = 0
for i in label_category:
temp = label_category[i]/sample_num
entropy -= temp * log(temp,2)
return entropy
#-------------根據不同feature的不同值篩選對應的數據集------------
def Extractdata(dataSet, axis, value): #篩選出的是第axis個feature中值爲value的sample集合 且此集合中不含第axis個featur對應的數據
extDataSet = []
for sample in dataSet:
if sample[axis] == value:
extSample = sample[:axis] #複製第axis個featur之前的數據
extSample.extend(sample[axis+1:])#複製第axis個featur之後的數據
extDataSet.append(extSample) #刪除第axis個featur對應的數據
return extDataSet
#--------對於每一節點得到最大的信息增益及對應的feature-----------
def BestFeature(dataSet):
feature_num = len(dataSet[0]) - 1 #數據集的最後一項是標籤
entropy = Entropy(dataSet)
bestEntropy = 0
bestInfoGain = 0
bestFeature = -1
for i in range(feature_num):
featuresample = [example[i] for example in dataSet]
uniqueVals = set(featuresample) #去除list中的重複元素
newEntropy = 0
for value in uniqueVals:
extDataSet = Extractdata(dataSet, i, value)
prob = len(extDataSet) / float(len(dataSet)) #當前feature中各個值所佔比重
newEntropy += prob * Entropy(extDataSet) #Entropy(extDataSet) 得到的是第i個feature中的第j個值的熵
gain = entropy - newEntropy #信息增益越大越好 -> 熵越小,數據越純淨
if gain > bestInfoGain:
bestInfoGain = gain
bestFeature = i
bestEntropy = entropy - gain
return bestInfoGain, bestFeature, bestEntropy
#--------------------------多數表決---------------------------
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
return max(classCount) #classlist中出現最多的元素
#----------------------------建樹-----------------------------
def CreateTree(dataSet,feature):
classList = [example[-1] for example in dataSet] #標籤項
best_infogain, best_feature, best_entropy= BestFeature(dataSet) #最大信息增益增益值與其所對應的feature的順序
#--------------------停止劃分的條件------------------------
#數據集中的最後一項:標籤爲同一個值(類別相同) best_entropy-> 最大熵爲0
if classList.count(classList[0]) ==len(classList):
#print(best_entropy)
return classList[0]
#最大信息熵小於等於0
if best_infogain < 0 or best_infogain == 0:
return majorityCnt(classList)
#所有特徵已經用完 -> 剩餘標籤
if len(dataSet[0]) == 1:
return majorityCnt(classList) #剩餘的list中所有feature已被用完,但標籤中仍沒有一致,這是採用多數表決
#---------------------------------------------------------
best_feature_name = feature[best_feature] #最大信息增益對應的feature的名稱
myTree = {best_feature_name:{}} #根節點 -> 某一個feature
del(feature[best_feature]) #刪去已經處理過的feature屬性
featuresample = [example[best_feature] for example in dataSet]
uniqueVals = set(featuresample) #對於每一個feature的各個值
for value in uniqueVals:
subfeature = feature[:]#複製,使進一步的調用函數不會對原feature產生影響。 python中函數傳入的list參數若修改會影響其原始值
myTree[best_feature_name][value] = CreateTree( Extractdata(dataSet, best_feature, value),subfeature)
return myTree
def main():
data, feature = createDataSet()
myTree = CreateTree(data,feature)
print (myTree)
if __name__=='__main__':
main()
5、運行結果
{'Outlook': {'rainy': {'Windy': {'FALSE': 'yes', 'TRUE': 'no'}}, 'overcast': 'yes', 'sunny': {'Humidity': {'high': 'no', 'normal': 'yes'}}}}
即生成了圖1中所得到的決策樹。
6、問題
不知道大家有沒有發現上述算法中存在的問題。當隨着決策樹的生長,其深度在不斷增加,這時就會出現過擬合問題。過擬合會導致數據的預測效果不好。而如何防止過擬合及欠擬合的問題呢?我們下次再講。