決策樹是一種常見的機器學習算法,也是很容易理解的。顧名思義,它是基於樹結構進行決策的。
如下圖所示:
正方形表示 判斷模塊 , 橢圓形表示 終止模塊,一棵決策樹包含一個根結點、若干個內部結點和若干個葉結點,葉節點表示決策結果。
1. 決策樹的構造
構造決策樹,需要解決的第一個問題就是,當前數據集上 哪個特徵 在劃分數據分類時其決定性作用(就是選擇哪個特徵進行劃分)
創建分支的僞代碼createBranch()如下所示:
這是一個 遞歸 函數。
決策數算法的流程如下:
- 收集數據:可使用任何方法。
- 準備數據:樹構造算法只適用標稱型數據,因此數據必須 離散化。
- 分析數據:可使用任何方法,構造樹完成之後,我們應該檢查圖形是否符合預期。
- 訓練算法:構造樹的數據結構。
- 測試算法:使用經驗樹計算錯誤率。
- 使用算法:此步驟可以適用任何監督學習算法。
1.1 信息增益
爲了進行數據的劃分,即選擇通過什麼特徵進行劃分,採取的一個原則爲:將無序的數據變得有序,即希望決策樹的分支節點所包含的樣本儘可能屬於同一類別,即結點的 “純度” 越來越高。
- 信息:符號 的信息定義爲 ,其中 是選擇該分類的概率。
- 信息熵:熵定義爲信息的期望值,它是度量樣本集合純度最常用的一種指標,公式爲 其中n爲分類的數目。
- 信息增益:當我們選擇一個特徵 進行劃分後,會產生 個分支節點,其中第 個分支結點包含 中所有在屬性 上取值爲 的樣本,記爲 。信息增益定義爲:,信息增益是越大越好。
舉例如下:
首先我們創建一個數據集:
#創建數據集
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
shannoEnt = 0.0
for key in labelCounts:
#計算每個類別所佔樣本集合的比例
prob = float(labelCounts[key])/numEntries
#求信息熵
shannoEnt -= prob * log(prob, 2)
return shannoEnt
最後求的結果就是
1.2 劃分數據集
#按照給定特徵劃分數據集
#參數:待劃分的數據集、劃分數據集的特徵、特徵的返回值
def splitDataSet(dataSet, axis, value):
# 創建新的list對象
retDataSet = []
# 遍歷數據集的每一行
for featVec in dataSet:
if featVec[axis] == value:
# 去掉axis特徵
reducedFeatVec = featVec[:axis]
# 將符合條件的添加到返回的數據集
# extend() 函數用於在列表末尾一次性追加另一個序列中的多個值(用新列表擴展原來的列表)。
reducedFeatVec.extend(featVec[axis+1:])
# 列表中嵌套列表
retDataSet.append(reducedFeatVec)
return retDataSet
選擇最好的數據集劃分方法, 代碼如下:
#選擇最好的數據集劃分方式
def chooseBestFeatureToSplit(dataSet):
#算出特徵的數目
numFeatures = len(dataSet[0]) - 1
#print(numFeatures)
#求得所有樣本的信息熵
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
表示第0個特徵爲最佳劃分屬性。
1.3 遞歸構建決策樹
當我們選擇好劃分的屬性之後,數據將被分支開來,被向下傳遞到樹分支的下一個結點,在這個結點上,再次劃分數據,這樣就是 遞歸 的一個過程。
遞歸結束的條件爲:
-
程序處理完所有劃分數據集的屬性,這樣類標籤卻不是唯一的,那麼就採用“少數服從多數”原則進行類別的確定。
-
每個分支下的所有實例都有相同的分類(這樣就沒必要在劃分了),這樣就得到了一個葉子結點,類別就是這個葉子結點上所有數據相同的類別。
“少數服從多數” 代碼如下:
#統計classList中出現次數最多的元素(類標籤)
def majorityCnt(classList):
classCount = {}
# 統計classList中每個元素出現的次數
for vote in classList:
if vote not in classCount.keys():
classCount[vote] = 0
classCount[vote] += 1
# 根據字典的值降序排序
# operator.itemgetter(1)獲取對象的第1列的值
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回出現次數最多的元素
return sortedClassCount[0][0]
創建樹的函數代碼如下:
#創建樹
def createTree(dataSet, labels, featLabels):
# 取分類標籤
classList = [example[-1] for example in dataSet]
# 如果類別完全相同則停止繼續劃分
if classList.count(classList[0]) == len(classList):
#print(classList)
return classList[0]
# 遍歷完所有特徵時得不到唯一類別的分組,返回出現次數最多的類標籤
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 選擇最優特徵
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
featLabels.append(bestFeatLabel)
# 根據標籤生成樹
myTree = {bestFeatLabel:{}}
# 刪除已經使用的特徵標籤
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
#遞歸構建決策樹
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)
return myTree
運行後,生成一棵如下的樹:
1.4 使用決策樹進行分類
#使用決策樹的分類函數
def classify(inputTree, featLabels, testVec):
#獲取根節點
firstStr = next(iter(inputTree))
#獲取子樹
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