機(jī)器學(xué)習(xí)中,決策樹(shù)是一個(gè)預(yù)測(cè)模型;代表對(duì)象屬性和對(duì)象值之間的一種映射關(guān)系。樹(shù)中每個(gè)節(jié)點(diǎn)表示某個(gè)對(duì)象,而每個(gè)分叉表示某個(gè)可能的屬性,每個(gè)葉子節(jié)點(diǎn)則對(duì)應(yīng)從根節(jié)點(diǎn)到該葉子節(jié)點(diǎn)所經(jīng)歷的路徑所表示的對(duì)象的值。決策樹(shù)只有單一輸出,若想要復(fù)數(shù)輸出,可以建立獨(dú)立的決策樹(shù)以處理不同輸入。
數(shù)據(jù)挖掘中常用到?jīng)Q策樹(shù),可以用于分析數(shù)據(jù),也可以用于預(yù)測(cè)。
簡(jiǎn)單理解

如上圖,
前兩個(gè)是屬性,可以記為
['no surfacing','flippers']。則可以簡(jiǎn)單的構(gòu)建決策樹(shù)如下:
根據(jù)兩個(gè)屬性可以判斷是否屬于魚(yú)類。
那么首先決定選擇哪個(gè)屬性作為最開(kāi)始的分類?最簡(jiǎn)單的是ID3。改進(jìn)的C4.5, CART后面再進(jìn)行了解。
決策樹(shù)和ID3
決策樹(shù)與樹(shù)結(jié)構(gòu)類似,具有樹(shù)形結(jié)構(gòu)。每個(gè)內(nèi)部節(jié)點(diǎn)表示一個(gè)屬性的測(cè)試,每個(gè)分支代表一個(gè)測(cè)試輸出,每個(gè)葉子節(jié)點(diǎn)代表一種類別。如上圖一樣。
分類樹(shù)(決策樹(shù))常用于機(jī)器學(xué)習(xí)的分類,是一種監(jiān)督學(xué)習(xí)方法。由樹(shù)的分支對(duì)該類型的對(duì)象依靠屬性進(jìn)行分類。每個(gè)決策樹(shù)可以依靠對(duì)源數(shù)據(jù)庫(kù)分割進(jìn)行數(shù)據(jù)測(cè)試,遞歸修剪樹(shù)。知道一個(gè)單獨(dú)的類被應(yīng)用于某一分支,不能進(jìn)行分割,遞歸完成。
特點(diǎn):
- 多層次的決策樹(shù)形式易于理解。
- 只適用于標(biāo)稱行數(shù)據(jù),連續(xù)性數(shù)據(jù)處理的不好。
ID3算法
上面介紹,怎么在一系列屬性中首先選擇哪個(gè)屬性進(jìn)行分類。簡(jiǎn)單理解,如果哪個(gè)屬性比較混亂,直接就可以得到所屬類別。比如上面屬性水下是否可以生存,不能生存的可以分類為 不是魚(yú)。
那么怎么去量化,得到這個(gè)屬性呢?
ID3算法的核心是信息墑,通過(guò)計(jì)算每個(gè)屬性的信息增益,認(rèn)為增益高的是好屬性,易于分類。每次劃分選取信息增益最高的屬性作為劃分標(biāo)準(zhǔn),進(jìn)行重復(fù),直至生成一個(gè)能完美分類訓(xùn)練樣歷的決策樹(shù)。

上面信息增益的算法不是很好理解,后面看代碼很容易。
ID3算法和決策樹(shù)的流程
- 數(shù)據(jù)準(zhǔn)備:需要對(duì)數(shù)值型數(shù)據(jù)進(jìn)行離散化
- ID3算法構(gòu)建決策樹(shù):
- 如果數(shù)據(jù)類別完全相同,則停止劃分。
- 否則,繼續(xù)劃分:
- 計(jì)算信息墑和信息增益來(lái)選擇最好的數(shù)據(jù)集劃分方法
- 劃分?jǐn)?shù)據(jù)集
- 創(chuàng)建分支節(jié)點(diǎn)
- 對(duì)每個(gè)分支進(jìn)行判定類別相同。相同停止劃分,不同則按照上述方法進(jìn)行劃分。
python代碼實(shí)現(xiàn)
利用上面例子創(chuàng)建數(shù)據(jù)集
def createDataSet():
dataSet = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
labels = ['no sufacing', 'flippers']
return dataSet, labels
計(jì)算信息墑,對(duì)應(yīng)第一個(gè)公式
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
# 為分類創(chuàng)建字典
labelCounts = {}
for featVec in dataSet:
currentLabel = featVec[-1]
if currentLabel not in labelCounts.keys():
labelCounts.setdefault(currentLabel, 0)
labelCounts[currentLabel] += 1
# 計(jì)算香農(nóng)墑
shannonEnt = 0.0
for key in labelCounts:
prob = float(labelCounts[key]) / numEntries
shannonEnt += prob * math.log2(1 / prob)
return shannonEnt
計(jì)算最大信息增益(公式2), 劃分?jǐn)?shù)據(jù)集。
# 定義按照某個(gè)特征進(jìn)行劃分的函數(shù) splitDataSet
# 輸入三個(gè)變量(帶劃分?jǐn)?shù)據(jù)集, 特征,分類值)
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 #返回不含劃分特征的子集
# 定義按照最大信息增益劃分?jǐn)?shù)據(jù)的函數(shù)
def chooseBestFeatureToSplit(dataSet):
numFeature = len(dataSet[0]) - 1
print(numFeature)
baseEntropy = calcShannonEnt(dataSet)
bestInforGain = 0
bestFeature = -1
for i in range(numFeature):
featList = [number[i] for number in dataSet] #得到某個(gè)特征下所有值
uniqualVals = set(featList) #set無(wú)重復(fù)的屬性特征值
newEntrogy = 0
#求和
for value in uniqualVals:
subDataSet = splitDataSet(dataSet, i, value)
prob = len(subDataSet) / float(len(dataSet)) #即p(t)
newEntrogy += prob * calcShannonEnt(subDataSet) #對(duì)各子集求香農(nóng)墑
infoGain = baseEntropy - newEntrogy #計(jì)算信息增益
print(infoGain)
# 最大信息增益
if infoGain > bestInforGain:
bestInforGain = infoGain
bestFeature = i
return bestFeature
簡(jiǎn)單測(cè)試:
if __name__ == '__main__':
dataSet, labels = createDataSet()
r = chooseBestFeatureToSplit(dataSet)
print(r)
# 輸出
# 2
# 0.41997309402197514
# 0.17095059445466865
# 0
如上,可以看到共有兩個(gè)屬性['no surfacing','flippers']和其信息增益,因此選擇較大的特征(下標(biāo)0)對(duì)數(shù)據(jù)集進(jìn)行劃分(見(jiàn)開(kāi)始圖),重復(fù)步驟,知道只剩下一個(gè)類別。
創(chuàng)建決策樹(shù)構(gòu)造函數(shù)
# 投票表決代碼
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote not in classCount.keys():
classCount.setdefault(vote, 0)
classCount[vote] += 1
sortedClassCount = sorted(classCount.items(), key=lambda i:i[1], reverse=True)
return sortedClassCount[0][0]
def createTree(dataSet, labels):
classList = [example[-1] for example in dataSet]
# print(dataSet)
# print(classList)
# 類別相同,停止劃分
if classList.count(classList[0]) == len(classList):
return classList[0]
# 判斷是否遍歷完所有的特征,是,返回個(gè)數(shù)最多的類別
if len(dataSet[0]) == 1:
return majorityCnt(classList)
#按照信息增益最高選擇分類特征屬性
bestFeat = chooseBestFeatureToSplit(dataSet) #分類編號(hào)
bestFeatLabel = labels[bestFeat] #該特征的label
myTree = {bestFeatLabel: {}}
del (labels[bestFeat]) #移除該label
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] #子集合
#構(gòu)建數(shù)據(jù)的子集合,并進(jìn)行遞歸
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
代碼里面有注視,嘗試去理解每一步的執(zhí)行,對(duì)決策樹(shù)有一個(gè)基本的了解。
if __name__ == '__main__':
dataSet, labels = createDataSet()
r = chooseBestFeatureToSplit(dataSet)
# print(r)
myTree = createTree(dataSet, labels)
print(myTree)
# --> {'no sufacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
可以看到輸出結(jié)果是一個(gè)嵌套的字典,手動(dòng)可以畫(huà)出決策樹(shù),與開(kāi)頭的圖相吻合。
將決策樹(shù)用于分類
構(gòu)建決策樹(shù)分類函數(shù):
def classify(inputTree, featLabels, testVec):
"""
:param inputTree: 決策樹(shù)
:param featLabels: 屬性特征標(biāo)簽
:param testVec: 測(cè)試數(shù)據(jù)
:return: 所屬分類
"""
firstStr = list(inputTree.keys())[0] #樹(shù)的第一個(gè)屬性
sendDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
classLabel = None
for key in sendDict.keys():
if testVec[featIndex] == key:
if type(sendDict[key]).__name__ == 'dict':
classLabel = classify(sendDict[key], featLabels, testVec)
else:
classLabel = sendDict[key]
return classLabel
可以看到函數(shù)分別根據(jù)屬性值對(duì)測(cè)試數(shù)據(jù)進(jìn)行一步一步分類,直至到葉子節(jié)點(diǎn),得到正確的分類。
另外,可以將決策樹(shù)進(jìn)行存儲(chǔ),與kNN不一樣的是,決策樹(shù)構(gòu)造好不用重復(fù)計(jì)算,下次可以直接使用.
def storeTree(inputTree,filename):
import pickle
fw=open(filename,'wb') #pickle默認(rèn)方式是二進(jìn)制,需要制定'wb'
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr=open(filename,'rb')#需要制定'rb',以byte形式讀取
return pickle.load(fr)
完整決策樹(shù)代碼請(qǐng)查看github:
github:decision_tree
總結(jié)
- 決策樹(shù): ID3, C4.5, CART
- 信息論:信息墑,信息增益
- python對(duì)象存儲(chǔ)