文章目錄
通過決策樹原理及相關概念細節我們知道,決策樹的學習算法主要包括3個步驟:特徵選擇、決策樹生成算法、決策樹剪枝,我們按照這個思路來一一實現相關功能。
本文的實現目前主要涉及特徵選擇、ID3及C4.5算法。剪枝及CART算法暫未涉及,後期補上。
1.ID3及C4.5算法基礎
前面文章我們提到過,ID3與C4.5的主要區別是特徵選擇準則的不同:
- ID3:信息增益
- C4.5:信息增益比
1.1 計算香農熵
不管是這兩者的哪一種,都涉及到信息增益的計算,而計算信息增益的基礎又是計算香農熵。所以我們先來實現計算香農熵的代碼。
from math import log
import operator
# 計算給定數據集的香農熵
def calcShannonEnt(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) # 以2爲底求對數
return shannonEnt
然後創建書中的數據集,並計算該數據集的香農熵:
# 創建自己的數據集
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
return dataSet, labels
myDat,labels=createDataSet()
myDat # [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
calcShannonEnt(myDat) # 0.9709505944546686
1.2 按照給定特徵劃分數據集
# 按照給定特徵劃分數據集
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
測試:
splitDataSet(myDat,0,1) # [[1, 'yes'], [1, 'yes'], [0, 'no']]
1.3 選擇最優特徵
# 選擇最好的數據集劃分方式
# 選擇最好的數據集劃分方式
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures):
# 創建唯一的分類標籤列表
featList = [example[i] for example in dataSet]
uniqueVals = set(featList)
newEntropy = 0.0
# 計算每種劃分方式的信息熵
for value in uniqueVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet)/float(len(dataSet))
newEntropy += prob * calcShannonEnt(subDataSet)
infoGain = baseEntropy - newEntropy # ID3
# infoGain = baseEntropy - newEntropy # C4.5
# 計算最好的信息增益
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
1.4 多數表決實現
在ID3、C4.5算法的停止條件之一是:沒有特徵可以選擇時停止算法,但如果這時該結點類標籤依然不是唯一的,此時我們需要決定如何定義該葉子結點。在這種情況下,通常採用多數表決的方法決定該葉子結點的分類。
# 多數表決實現
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
# 對字典進行排序
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
# Python3中不再支持iteritems(),將iteritems()改成items()
return sortedClassCount[0][0]
對於“多數表決實現”函數的註釋:
- 1.dict.items()
作用:是可以將字典中的所有項,以列表方式返回。因爲字典是無序的,所以用items方法返回字典的所有項,也是沒有順序的。 - 2.operator.itemgetter()
operator模塊提供的itemgetter函數用於獲取對象的哪些維的數據,參數爲一些序號. - 3.sorted()函數,排序
list.sort()是對已經存在的列表進行操作,進而可以改變進行操作的列表;
sorted返回的是一個新的list,而不是在原來的基礎上進行的操作
2.基於ID3、C4.5生成算法創建決策樹
這裏主要介紹基於ID3生成算法創建決策樹,C4.5只需要在ID3生成決策樹代碼上將chooseBestFeatureToSplit(dataSet)函數中infoGain = baseEntropy - newEntropy換成infoGain = baseEntropy - newEntropy即可 。
# 創建樹的函數代碼
def creatTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
labels2 = labels[:]
# 類別完全相同則停止繼續劃分
if classList.count(classList[0]) == len(classList):
return classList[0]
# 遍歷完所有特徵時返回出現次數最多的類別
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels2[bestFeat]
myTree = {bestFeatLabel:{}}
del (labels2[bestFeat])
# 得到列表包含的(選定爲最佳特徵的)所有屬性值
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels2[:] # 複製類標籤
# 遞歸
myTree[bestFeatLabel][value] = creatTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
對於“creatTree”函數的註釋:
-
1.list.count(obj)
統計某個元素在列表中出現的次數 -
2.del,list.remove(),list.pop()
del:根據索引位置來刪除單個值或指定範圍內的值;
list.remove():刪除單個元素,刪除首個符合條件的元素,按值刪除,返回值爲空;
list.pop():刪除索引位置元素,無參情況下刪除最後一個元素,返回刪除的元素值;
測試:
myTree = creatTree(myDat, labels)
myTree # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
3.使用決策樹進行分類
# 使用決策樹的分類函數
def classify(inputTree, featLabels, testVec):
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
for key in secondDict.keys():
if testVec[featIndex] == key:
if type(secondDict[key]).__name__=='dict':
#if isinstance(secondDict[key], dict): 這個也可以
classLabel = classify(secondDict[key],featLabels,testVec)
else:
classLabel = secondDict[key]
return classLabel
對於“classify”的註釋:
-
1.type(a).name == ‘dict’:
可判斷a的類型是否類型爲dict,list tuple 這些也適用 -
2.也可以用isinstance(變量名,類型)判斷類型:
判斷該變量是否是該類型,或者是否是該類和該類的父類類型;小注: type(變量名):獲取該變量名的類型,結合==判斷該變量的類型是否等於目標類型(等號右邊value值) 比如:a類繼承b類,實例c=a() isinstance(c,a)和isinstance(c,b)都是True type(c)的value值是a,a是不等於b的,所以a==b爲False即:type(c)==b爲False
-
3.==和is
==:變量名的value值是否相等
is:變量名的id(地址)是否相等(數字類型的value值相等則id相等)
測試:
classify(myTree, labels, [1,0]) # 'no'
classify(myTree, labels, [1,1]) # 'yes'
4.存儲決策樹
import pickle
# 使用pickle模塊存儲決策樹
def storeTree(inputTree, filename):
with open(filename, 'wb') as f:
pickle.dump(inputTree, f)
# 加載決策樹
def grabTree(filename):
with open(filename, 'rb') as f:
return pickle.load(f)
測試:
storeTree(myTree,'classifierStorage.txt')
grabTree('classifierStorage.txt') # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
參考資料:
《機器學習實戰》第三章