《機器學習實戰》學習筆記(二)決策樹
1. 定義
- 決策樹的結構和數據結構中的樹結構類似,但是可以有多個分支。決策樹從根節點出發,每個非葉子節點爲一個判斷條件,每一個葉子節點是結論,從根節點出發,經過多次判斷最終得到結論。在我理解就是一堆信息的邏輯組合。
- 專家系統中經常使用決策樹,而且決策樹給出結果往往可以匹敵在當前領域具有幾十年工作經驗的人類專家。
2. 決策樹的構造
(1) 收集數據:可以使用任何方法。
(2) 準備數據:樹構造算法只適用於標稱型數據,因此數值型數據必須離散化。
(3) 分析數據:可以使用任何方法,構造樹完成之後,我們應該檢查圖形是否符合預期。
(4) 訓練算法:構造樹的數據結構。
(5) 測試算法:使用經驗樹計算錯誤率。
(6) 使用算法:此步驟可以適用於任何監督學習算法,而使用決策樹可以更好地理解數據的內在含義
3. 數據集劃分原則
- 劃分數據集的大原則是:將無序的數據變得更加有序。這裏採用信息熵作爲劃分依據。
- 是使用信息論度量信息,用信息熵表示數據的狀態 ,信息熵可以作爲一個系統複雜程度的度量,如果系統越複雜,出現不同情況的種類越多,那麼它的信息熵是比較大的。
- 信息的定義如下:
其中:其中p(xi)是某一類的樣本數量佔種樣本數量的比例 - 信息熵的定義:所有信息的期望值
- 信息的定義如下:
- 另一個度量集合無序程度的方法是基尼不純度。
- 是使用信息論度量信息,用信息熵表示數據的狀態 ,信息熵可以作爲一個系統複雜程度的度量,如果系統越複雜,出現不同情況的種類越多,那麼它的信息熵是比較大的。
4. 算法實現
- 這裏採用了ID3算法:
- 原理:首先檢測所有屬性,選擇信息增益最大的屬性產生決策樹節點,由該屬性的不同取值建立分支,再對各分支的子集遞歸調用該方法建立決策樹節點的分支,直到所有子集僅包含同一類別的數據爲止。
遞歸結束條件:
1. 該分支的所有樣本屬於同一類;此時返回該類
2. 所有特徵已經使用完畢,不能繼續進行分裂;此時返回樣本出現最多的類。此處也可以自定義結束條件,所剩餘的特徵爲n時結束。
3. 遍歷所有特徵,選擇使信息熵增加最多的特徵爲依據建立分支,然後遞歸此方法,直到條件結束。
- 原理:首先檢測所有屬性,選擇信息增益最大的屬性產生決策樹節點,由該屬性的不同取值建立分支,再對各分支的子集遞歸調用該方法建立決策樹節點的分支,直到所有子集僅包含同一類別的數據爲止。
- 算法實現:
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 26 17:15:43 2019
@author: xinglin
"""
from math import log
class Trees:
def __init__(self):
pass
# 計算信息熵
def calcShannonEnt(self,dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts[currentLabel] = 0
labelCounts[currentLabel] += 1
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob*log(prob,2)
return shannonEnt
# 按照特徵劃分數據集
def splitDataSet(self,dataSet,axis,value):
retDataSet = []
for i in dataSet:
if i[axis] == value:
ret = i[:axis]
ret += i[axis+1:]
retDataSet.append(ret)
return retDataSet
# 選擇最佳劃分依據,依據:信息熵減少最多的特徵
def chooseBestFeatureToSplit(self,dataSet):
numFeatures = len(dataSet[0]) - 1
baseEntropy = self.calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeature = -1
for i in range(numFeatures):
#列出特徵i的所有可能取值 val
featValList = [example[i] for example in dataSet]
featValSet = set(featValList)
newEntropy = 0.0
for value in featValSet: #求出每種val的信息熵的和
splitData = self.splitDataSet(dataSet,i,value)
prob = len(splitData)/float(len(dataSet))
newEntropy += prob*self.calcShannonEnt(splitData)
infoGain = baseEntropy - newEntropy
if infoGain >= bestInfoGain:
bestInfoGain = infoGain
bestFeature = i
return bestFeature
# 返回出現次數最多的類別,原函數採用了operator模塊的一些方法,本人對該模塊不怎麼了解
#這裏稍作改動,直接遍歷列表求出現次數最多的
def majorityCnt(self,classList):
temp = 0
for i in set(classList):
if classList.count(i) > temp:
maxClassList = i
temp = classList.count(i)
return maxClassList
# 資料的原函數
# {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
def createTree(self,dataSet,labels):
classList = [i[-1] for i in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]
if len(dataSet[0]) == 1:
return self.majorityCnt(classList)
bestFeat = self.chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [i[bestFeat] for i in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = \
self.createTree(self.splitDataSet(dataSet,bestFeat,value),subLabels)
return myTree
# 每一分支節點改爲鍵值feat記錄當前節點劃分依據,featVal記錄子節點,以feat爲依據遍歷featVal可以直接得到結果
# {'feat': 0, 'featVal': {0: 'no', 1: {'feat': 0, 'featVal': {0: 'no', 1: 'yes'}}}}
def myCreateTree(self,data): #輸入數據最後一列爲標籤
label = [i[-1] for i in data]
if label.count(label[0]) == len(label):
return label[0]
if len(data[0]) == 1:
return self.majorityCnt(label)
bestFeat = self.chooseBestFeatureToSplit(data)
myTree = {'feat':bestFeat,'featVal':{}}
featValues = [i[bestFeat] for i in data]
uniqueVals = set(featValues)
for val in uniqueVals:
myTree['featVal'][val] = self.myCreateTree(self.splitDataSet(data,bestFeat,val))
return myTree
# 訓練,返回訓練好的模型
def fit(self,x_train,y_train):
'''
x_train數據格式:每一列表示一個屬性,每一行表示一個樣本
y_train數據格式:一維數組,表示標籤,與X_train相對應
'''
lence = len(y_train)
for i in range(lence):
x_train[i].append(y_train[i])
return self.myCreateTree(x_train)
# 預測predict,
def predict(self,inputTree,test_data):
if inputTree is None:
return 'Decision tree not created !please run fit()'
index = inputTree['feat']
valueKey = test_data[index]
nextTree = inputTree['featVal'][valueKey]
if type(nextTree) is dict:
del(test_data[index])
return self.predict(nextTree,test_data)
else:
return nextTree
# 示例數據
def creatDataSet(self):
dataSet = [
[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no'],
]
labels = ['no surfacing','flippers']
return dataSet,labels
if __name__=='__main__':
Model = Trees()
myData,labels = Model.creatDataSet()
print('myData的信息熵:',Model.calcShannonEnt(myData))
# print(Model.myCreateTree(myData))
data_x = [
[1,1],
[1,1],
[1,0],
[0,1],
[0,1]
]
data_y = ['yes','yes','no','no','no']
itree = Model.fit(data_x,data_y)
print('決策樹結構:',itree)
print('[1,0]預測結果:',Model.predict(itree,[1,0]))
5. 總結
- 決策樹分類器就像帶有終止塊的流程圖,終止塊表示分類結果;
- 決策樹可能會過度匹配訓練數據,從而導致過擬合。這裏可以裁剪決策樹,去掉一些不必要的葉子節點。如果葉子節點只能增加少許信息,則可以刪除該節點,將它併入到其他葉子節點中。