一、什麼是熵
假設符號Xi的信息定義爲:
則熵定義爲信息的期望值,爲了計算熵,我們需要計算所有類別所有可能值包含的信息期望值,計算方式如下:
熵愈大,不穩定性愈高,即在決策樹中一個樣本可選擇的分支會愈多。從公式來理解是:假如每個p(xi)愈少,則i值愈大,即信息量愈大,就是有可以有很多中分類。同時,H是關於p(xi)的減函數,
熵表示數據的混亂程度,假如每個p(xi)愈少,數據愈分散,則最後求出的嫡愈大,熵的本質是一個系統“內在的混亂程度”。所以,熵、不確定性、信息量,這三者是同一個數值。
二、構建樹的理解
首先我認爲要先理解怎樣用一堆數據集訓練出來一棵樹。數據集中的數據包括特徵值和類標籤值。注意了這裏的特徵值是隻有值,這些值要根據項目的背景歸納出來特徵。這些特徵就是訓練決策樹時要輸入的標籤。
然後,我個人理解創建決策樹的過程就是判斷哪個特徵(就是上面所說的要輸入的標籤)是父節點,哪些特徵是子節點,哪些特徵是葉子節點。構建決策樹的目的是把數據集合中的不同類型的數據劃分得清清楚楚,即不同類型的數據必須分開。也就是說,只有決策樹的分支愈多,把不同類型的數據分開的概率才能愈大。所以在創建樹的過程中,會優先選擇一些特徵作爲上層節點。這些特徵的特點是:在根據這個特徵劃分數據集之後,數據子集的熵是最大的(熵愈大,不穩定性愈高,則在後續的節點中可以分出更多的類別)。
以下是計算熵和劃分數據集的代碼
#計算熵值
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts={}
for featVect in dataSet:
currentLabel = featVect[-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(dataSet,axis,value):
retDataSet=[]
for featVec in dataSet:
if featVec[axis]==value:
reduceFeatVec=featVec[:axis]
#這裏獲取的是取出了該特徵之後的數據子集
reduceFeatVec.extend(featVec[axis+1:])
retDataSet.append(reduceFeatVec)
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)
#print uniqueVals
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
至此,把數據集劃分完畢,接下來是進行構建樹。工作原理是:對原始的數據集基於最好的特徵(即根據該特徵劃分之後子集熵最大)進行劃分數據集。在一次劃分之後,數據將會被傳到下一個節點,在這個節點上可以再次劃分數據。每次遞歸都會在數據子集上選擇一個最好的特徵進行數據的分割,遞歸結束的條件是,所有程序遍歷完所有的特徵,或者是每個分支下所有的實例都具有相同的分類。
以下是創建樹的代碼:
#假如已經遍歷了所有的特徵,但是最終的分類標籤仍然不是唯一的,則採用少數服從多數的原則決定這個標籤
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys():
classCount[vote]=0#zhege if yuju xiangdangyu yige chushihua tiaojian
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
測試分類器的代碼如下:
#test
#featLabels就是上面所說的要輸入的標籤,而testVec就是屬性值
def classify(inputTree,featLabels,testVec):
firstStr=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':
classLabel=classify(secondDict[key],featLabels,testVec)
else:classLabel=secondDict[key]
return classLabel
總結:一步一個腳印!