【資料科學系統學習】機器學習演算法 # 西瓜書學習記錄 [10] 決策樹實踐
阿新 • • 發佈:2018-12-10
本篇內容為《機器學習實戰》第 3 章決策樹部分程式清單。所用程式碼為 python3。
決策樹優點:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特徵資料。缺點:可能會產生過度匹配問題。適用資料型別:數值型和標稱型
在構造決策樹時,我們需要解決的第一個問題就是,當前資料集上哪個特徵在劃分資料分類時起決定性作用。為了找到決定性的特徵,劃分出最好的結果,我們必須評估每個特徵。完成測試之後,原始資料集就被劃分為幾個資料子集。這些資料子集會分佈在第一個決策點的所有分支上。如果某個分支下的資料屬於同一型別,則無需進一步對資料集進行分割。如果資料子集內的資料不屬於同一型別,則需要重複劃分資料子集的過程。劃分資料子集的演算法和劃分原始資料集的方法相同,直到所有具有相同型別的資料均在一個數據子集內。
建立分支的虛擬碼函式createBranch()
如下所示:
檢測資料集中的每個子項是否屬於同一分類:
If so return 類標籤
Else
尋找劃分資料集的最好特徵
劃分資料集
建立分支節點
for 每個劃分的子集
調整函式createBranch()並增加返回結果到分支節點中
return 分支節點
下面我們採用量化的方法來判定如何劃分資料,我們以下圖所示的資料集為例:
程式清單 3-1 計算給定資料集的夏農熵
''' Created on Sep 16, 2018 @author: yufei ''' # coding=utf-8 """ 計算給定資料的夏農熵 """ from math import log 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 # 以 2 為底求對數 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob, 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
在 python 提示符下,執行程式碼並得到結果:
>>> import trees
>>> myDat, labels = trees.createDataSet()
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.calcShannonEnt(myDat)
0.9709505944546686
程式清單 3-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()
,在 python 提示符下,執行程式碼並得到結果:
>>> myDat, labels = trees.createDataSet()
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
>>> trees.splitDataSet(myDat, 0, 0)
[[1, 'no'], [1, 'no']]
程式清單 3-3 選擇最好的資料集劃分方式
"""
函式功能:選擇特徵,劃分資料集,計算得出最好的劃分資料集的特徵
資料集需滿足:
1、資料是一種由列表元素組成的列表,且所有的列表元素都要具有相同的資料長度
2、資料的最後一列或每個例項的最後一個元素是當前例項的類別標籤
"""
def chooseBestFeatureToSplit(dataSet):
# 判定當前資料集包含多少特徵屬性
numFeatures = len(dataSet[0]) - 1
# 計算整個資料集的原始夏農熵,即最初的無序度量值
baseEntropy = calcShannonEnt(dataSet)
bestInfoGain = 0.0
bestFeatures = -1
# 遍歷資料集中的所有特徵
for i in range(numFeatures):
# 建立唯一的分類標籤列表,將資料集中所有第 i 個特徵值寫入這個 list 中
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
bestFeatures = i
return bestFeatures
在 python 提示符下,執行程式碼並得到結果:
>>> myDat, labels = trees.createDataSet()
>>> trees.chooseBestFeatureToSplit(myDat)
0
>>> myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
程式碼執行結果告訴我們,第 0 個特徵是最好的用於劃分資料集的特徵。也就是說第一個特徵是 1 的放在一個組,第一個特徵是 0 的放在另一個組。因為這個資料集比較簡單,我們直接觀察可以看到第一種劃分更好地處理了相關資料。
下面我們會介紹如何將上述實現的函式功能放在一起,構建決策樹。
程式清單 3-4 建立樹的函式程式碼
"""
使用分類名稱的列表,建立資料字典
返回出現次數最多的分類名稱
"""
import operator
def majorityCnt(classList):
classCount = {}
for vote in classList:
if vote in classList:
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 的列表變數,包含了資料集的所有類標籤
classList = [example[-1] for example in dataSet]
# 遞迴函式的第一個停止條件:所有類標籤完全相同,則直接返回該類標籤
if classList.count(classList[0]) == len(classList):
return classList[0]
# 遞迴函式的第二個停止條件:使用完所有特徵,仍然不能將資料集劃分成僅包含唯一類別的分組
# 由於無法簡單地返回唯一的類標籤,這裡遍歷完所有特徵時使用 majorityCnt 函式返回出現次數最多的類別
if len(dataSet[0]) == 1:
return majorityCnt(classList)
# 當前資料集選取的最好特徵儲存在變數 bestFeat 中,得到列表包含的所有屬性值
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
# 字典變數 myTree 儲存了樹的所有資訊
myTree = {bestFeatLabel:{}}
del(labels[bestFeat])
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
# 遍歷當前選擇特徵包含的所有屬性值
for value in uniqueVals:
# 複製類標籤,將其儲存在新列表變數 subLabels 中
# 在python語言中,函式引數是列表型別時,引數是按照引用方式傳遞的
# 為了保證每次呼叫函式 createTree 時不改變原始列表的內容
subLabels = labels[:]
# 在每個資料集劃分上遞迴的呼叫函式 createTree()
# 得到的返回值被插入字典變數 myTree 中
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
return myTree
在 python 提示符下,執行程式碼並得到結果:
>>> myDat, labels = trees.createDataSet()
>>> myTree = trees.createTree(myDat, labels)
>>> myTree
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
最後得到的變數myTree
包含了很多代表樹結構資訊的巢狀字典。這棵樹包含了 3 個葉子節點以及 2 個判斷節點,形狀如下圖所示:
不足之處,歡迎指正。