決策樹算法也是分類算法中非常常用的一個算法,首先我們先來了解一下它的基本實現原理:
在給定的一個訓練樣本中可能包含很多的屬性,我們可以根據屬性值來劃分數據集,通過不斷的劃分數據集直到所有的葉子節點上的數據集屬於同意類。就構造好了一個決策樹算法。
接下來我們來仔細的探討一下這個算法的實現過程:
首先我們來創建一個名爲trees.py的文件,在進行編碼工作之前我們需要了解一個信息增益的概念。在劃分數據集之前和劃分數據集之後信息發生的變化稱爲信息增益,這樣我們就可以計算哪一個特徵作爲根節點,也就是哪一個特徵作爲第一個劃分參考值,這個很重要,關係到數據劃分的準確度。計算每個特徵值劃分數據集獲得的信息增益,獲得信息增益最高的特徵就是最好的選擇。集合信息的度量方式稱爲香農熵或熵。這裏公式就不給大家展示了,(確實不太容易打出來)稍後代碼中會給出。這裏大家拿來用就行不用進行修改。
from math import log
import operator
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
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)
return shannonEnt
第一個函數是數據集的創建,dataSet是一個二維數組包含了訓練數據,這裏注意最後一列爲標籤,labels代表每個屬性值的含義。接下來的一個函數就是香濃熵的計算,首先計算出數據集中實例的個數,然後遍歷數據集並創建一個字典包含標籤健以及其在數據集中出現的次數。第二個循環就是用來計算香濃熵。知道怎麼計算香濃熵我們就可以用它來進行劃分數據集:
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
首先我們進行劃分數據集,然後計算劃分數據集的熵,來是否正確的劃分了數據集。上面的代碼就是用來劃分數據集。這個函數有三個輸入參數:待劃分數據集、劃分數據集的特徵、需要返回的特徵值。這段代碼還是比較簡單的,就是將特徵值和需要返回的特徵值相等的數據想添加到列表中返回,用於後邊的熵計算。
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
if (infoGain > bestInfoGain):
bestInfoGain = infoGain
bestFeature = i
return bestFeature
上面代碼就是實現選取特徵,劃分數據集,計算出最好的劃分數據集的特徵。在函數調用中數據需要滿足一定的數據要求:第一就是數據必須是一種由列表元素組成的元素,而且所有的列表元素都要具有相同的數據長度;第二個要求就是,數據的最後一列或者每個實例的最後一個元素是當前實例的類別標籤。這裏的數據類型可以是數字或者是字符串。在開始劃分數據集之前計算出整個數據集的原始香濃熵,代碼的第3行。第一層循環遍歷整個數據集創建一個唯一的分類標籤。第2層循環就是重點,計算出每種劃分方式的信息熵,if語句用於選擇出最大的信息增益,返回最好劃分的索引值。到此我們已經找出最好劃分的特徵值。接下來就可以進行構建決策樹,下面我們就用遞歸的方式構建決策樹:
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]
if len(dataSet[0]) == 1:
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:]
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
遞歸結束的條件是:程序遍歷完所有劃分數據集的屬性,或者每個分支下的所有實例都具有相同的分類。這裏的第一個函數類似於投票表決,首先遍歷數據集中的所有標籤,創建一個字典包含每個標籤以及其出現次數,最後排序返回出現次數最高的那個標籤。第二個函數就是創建樹的代碼。這個函數有兩個輸入參數,第一個是數據集第二個是屬性值的含義,算法本身不需要這個變量,在這裏僅僅爲了數據含義的更加明確。首先判斷數據集的所有標籤是否全部相同如果全部相同就返回,沒有必要繼續進行劃分,否則遍歷所有特徵返回出現次數最高的標籤。接下來就是遞歸創建樹的過程,每鎖定一個劃分特徵就遞歸尋找數據子集中的下一個最好的劃分特徵值。直到滿足結束的要求。就返回構建好的樹。接下來就是進行測試樹:
def classify(inputTree,featLabels,testVec):
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
key = testVec[featIndex]
valueOfFeat = secondDict[key]
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel
上面的代碼就是一個測試代碼,輸入參數爲剛纔建立好的樹,屬性標籤,還有測試數據。這個函數不再進行詳細介紹,最後返回測試結果,大家可以在IDLE輸入一下代碼進行測試:
>>>myDat,labels = trees.createDataSet()
>>>mytree = trees.createTree(myDat,labels)
>>>trees.classify(mytree,labels,[1,0])
'no'
由於決策樹是一個計算複雜的算法,它需要消耗大量的時間,所以我們可以將生成的樹模型存儲起來,下次使用的時候就不用再次訓練模型:
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
上面兩個函數第一個是將模型進行序列化然後存儲起來,第二個是在使用的時候將它讀出來。到此這個算法已經全部講解完畢!