http://cn.akinator.com/ “神燈猜名人”這個遊戲很多人都玩過吧,問很多問題,然後逐步猜測你想的名人是誰。決策樹的工作原理與這個類似,輸入一系列數據,然後給出遊戲答案。決策樹也是最經常使用的數據挖掘算法。書上給了一個流程圖決策樹,很簡單易懂。
這裏,橢圓形就是判斷模塊,方塊就是終止模塊。kNN 方法也可以完成分類任務,但是缺點是無法給出數據的內在含義。決策樹的主要優勢就在於數據形式容易理解。
==============================================================================
決策樹
優點:計算複雜度不高,輸出結果容易理解,對中間值缺失不敏感,可以處理不相關特徵數據。
缺點:可能會產生過度匹配問題。
適用數據類型:數值型和標稱型。
僞代碼:
creatBranch():
if so return 類標籤:
else:
尋找劃分數據集的最好特徵
劃分數據集
創建分支節點
for 每個劃分的子集
調用函數 creatBranch() 並增加返回結果到分支節點中
return 分支節點
可以看出這是一個遞歸函數,在裏面直接調用了自己。
==============================================================================
決策樹的一般流程:
- 收集數據:可以使用任何方法
- 準備數據:樹構造算法只適用於標稱型數據,因此數值型數據必須離散化。
- 分析數據:可以使用任何方法,構造樹完成以後,我們應該檢查圖形是否符合預期。
- 訓練算法:構造樹的數據結構。
- 測試算法:使用經驗樹計算錯誤率。
- 使用算法:此步驟可以適用於任何監督學習算法,而使用決策樹可以更好地理解數據的內在含義。
一些決策樹算法採用二分法,我們不用這種方法。我們可能會遇到更多的選項,比如四個,然後創立四個不同分支。本書將使用 ID3 算法劃分數據集。
==============================================================================
信息增益:
劃分數據集的大原則是:將無序的數據變得更加有序。
在劃分數據集之前之後信息發生的變化稱爲信息增益。
集合信息的度量方式稱爲香農熵或者簡稱爲熵。熵定義爲信息的期望值。公式略過不表。
給一段代碼,計算給定數據集的熵:
# -*- 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
for key in labelCounts:
prob = float(labelCounts[key])/numEntries
shannonEnt -= prob *log(prob, 2) # 以 2 爲底求對數
return shannonEnt
然後自己利用 createDataSet() 函數來得到35頁表 3-1 的魚類鑑定數據集。
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
最後來運行一下試試,我們建立一個 run_trees.py
# -*- coding:utf-8 -*-
# run_trees.py
import trees
myDat,labels = trees.createDataSet()
print myDat
print trees.calcShannonEnt(myDat)
熵越多高,說明混合數據越多。這裏添加一個 “maybe” 分類,表示可能爲魚類。
測試:
# -*- coding:utf-8 -*-
import trees
myDat,labels = trees.createDataSet()
print myDat
print trees.calcShannonEnt(myDat)
print '*********************************'
myDat[0][2] = 'maybe' # 0 指的是dataSet第一個[],-1 指[]裏面倒數第一個元素
print myDat
print trees.calcShannonEnt(myDat)
結果:
====================================================================================================
3.1.2 劃分數據集
分類算法除了需要測量信息熵,還要劃分數據集。
我們對每個特徵劃分數據集的結果計算一次信息熵,然後判斷按照哪個特徵劃分數據集是最好的方式。
按照給定特徵劃分數據集,代碼接着 trees.py 寫:
def splitDataSet(dataSet, axis, value):
retDataSet= [] # 創建新的 list 對象
for featVec in dataSet:
if featVec[axis] == value:
# 抽取
reducedFeatVec = featVec[:axis]
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
注意 append() 和 extend() 的區別:
>>>a = [1,2,3]
>>>b = [4,5,6]
>>>a.append(b)
>>>a
[1,2,3,[4,5,6]]
>>>a = [1,2,3]
>>>a.extend(b)
>>>a
[1,2,3,4,5,6]
=============================================================================================
現在可以在前面的簡單樣本數據上測試函數 splitDataSet()
在 run_trees.py 裏面加些代碼:
# -*- coding:utf-8 -*-
# run_trees.py
import trees
myDat,labels = trees.createDataSet()
print '>>> myDat'
print myDat
print '>>> trees.calcShannonEnt(myDat)'
print trees.calcShannonEnt(myDat)
print '*********************************'
myDat[0][2] = 'maybe' # 0 指的是dataSet第一個[],-1 指[]裏面倒數第一個元素
print '>>> myDat'
print myDat
print '>>> trees.calcShannonEnt(myDat)'
print trees.calcShannonEnt(myDat)
print '*********************************'
reload(trees)
myDat,labels = trees.createDataSet()
print '>>> myDat'
print myDat
print '>>> trees.splitDataSet(myDat,0,1)'
print trees.splitDataSet(myDat,0,1)
print '>>> trees.splitDataSet(myDat,0,0)'
print trees.splitDataSet(myDat,0,0)
結果如下:
=======================================================================
接下來我們要遍歷整個數據集,循環計算香農熵和 splitDataSet() 函數,找到最好的特徵劃分方式。熵計算將會告訴我們如何劃分數據集是最好的組織方式。
加代碼:
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) # set 是一個集合
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
這段代碼就是選取特徵,劃分數據集,計算得出最好的劃分數據集的特徵。
在在 run_trees.py 裏面加些代碼:
print '*********************************'
reload(trees)
print '>>> myDat, labels = trees.createDataSet()'
myDat, labels = trees.createDataSet()
print '>>> trees.chooseBestFeatureToSplit(myDat)'
print trees.chooseBestFeatureToSplit(myDat)
print '>>> myDat'
print myDat
結果如下:
代碼的意義在於,告訴我們第0個特徵(不浮出水面是否可以生存)是最好的用於劃分數據集的特徵。
如果不相信這個結果,可以修改 calcShannonEnt(dataSet) 函數來測試不同特徵分組的輸出結果。
===============================================================================
3.1.3 遞歸構建決策樹
從數據集構造決策樹算法所需要的子功能模塊,原理如下:得到原始數據集,然後基於最好的屬性值劃分數據集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的數據集劃分。第一次劃分後,數據將被鄉下傳遞到樹分支的下一個節點,在這個節點上,我們可以再次劃分數據。我們可以採用遞歸的原則處理數據集。
在添加代碼前,在 trees.py 頂部加上一行代碼:
import operator
然後添加:
def majorityCnt(classList):
classCount = {} # 創建鍵值爲 classList 中唯一值的數據字典
for vote in classList:
if vote not in classCount.keys():classCount[vote] = 0
classCount[vote] += 1 # 儲存了 classList 中每個類標籤出現的頻率
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:
# 爲了保證每次調用函數 createTree() 時不改變原始列表類型,使用新變量 subLabels 代替原始列表
subLabels = labels[:] # 這行代碼複製了類標籤,並將其存儲在新列表變量 subLabels 中
myTree[bestFeatLabel][value] = createTree(splitDataSet\
(dataSet,bestFeat,value),subLabels)
return myTree
下一步開始創建樹,使用 字典 類型來保存樹的信息,當然也可以聲明特殊的數據類型儲存樹,但是這裏沒有必要。
當前數據集選取的最好特徵存儲在變量 bestFeat 中,得到列表包含的所有屬性值。
現在運行代碼,在 run_trees.py 裏面添加:
print '*********************************'
reload(trees)
print '>>> myDat, labels = trees.createDataSet()'
myDat, labels = trees.createDataSet()
print '>>> myTree = trees. createTree(myDat, labels)'
myTree = trees. createTree(myDat, labels)
print '>>> myTree'
print myTree
結果:變量 myTree 包含了很多代表樹結構信息的嵌套字典。