原文連結:www.cnblogs.com/fydeblog/p/…
前言
這篇notebook是關於機器學習監督學習中的決策樹演算法,內容包括決策樹演算法的構造過程,使用matplotlib庫繪製樹形圖以及使用決策樹預測隱形眼睛型別. 作業系統:ubuntu14.04(win也ok) 執行環境:anaconda-python2.7-jupyter notebook 參考書籍:機器學習實戰和原始碼 notebook writer ----方陽
注意事項:在這裡說一句,預設環境python2.7的notebook,用python3.6的會出問題,還有我的目錄可能跟你們的不一樣,你們自己跑的時候記得改目錄,我會把notebook和程式碼以及資料集放到結尾的百度雲盤,方便你們下載!
決策樹原理:不斷通過資料集的特徵來劃分資料集,直到遍歷所有劃分資料集的屬性,或每個分支下的例項都具有相同的分類,決策樹演算法停止執行。
決策樹的優缺點及適用型別:
- 優點 :計算複雜度不高, 輸出結果易於理解,對中間值的缺失不敏感,可以處理不相關特徵資料。
- 缺點 :可能會產生過度匹配問題。
適用資料型別:數值型和標稱型。
先舉一個小例子,讓你瞭解決策樹是幹嘛的,簡單來說,決策樹演算法就是一種基於特徵的分類器,拿郵件來說吧,試想一下,郵件的型別有很多種,有需要及時處理的郵件,無聊時觀看的郵件,垃圾郵件等等,我們需要去區分這些,比如根據郵件中出現裡你的名字還有你朋友的名字,這些特徵就會就可以將郵件分成兩類,需要及時處理的郵件和其他郵件,這時候在分類其他郵件,例如郵件中出現buy,money等特徵,說明這是垃圾推廣檔案,又可以將其他檔案分成無聊是觀看的郵件和垃圾郵件了。
1.決策樹的構造
1.1 資訊增益
試想一下,一個資料集是有多個特徵的,我們該從那個特徵開始劃分呢,什麼樣的劃分方式會是最好的?
我們知道劃分資料集的大原則是將無序的資料變得更加有序,這樣才能分類得更加清楚,這裡就提出了一種概念,叫做資訊增益,它的定義是在劃分資料集之前之後資訊發生的變化,變化越大,證明劃分得越好,所以在劃分資料集的時候,獲得增益最高的特徵就是最好的選擇。
這裡又會扯到另一個概念,資訊理論中的熵,它是集合資訊的度量方式,熵變化越大,資訊增益也就越大。資訊增益是熵的減少或者是資料無序度的減少.
一個符號x在資訊理論中的資訊定義是 l(x)= -log(p(x)) ,這裡都是以2為底,不再複述。
則熵的計算公式是 H =-∑p(xi)log(p(xi)) (i=1,2,..n)
下面開始實現給定資料集,計算熵。
參考程式碼:
from math import log #we use log function to calculate the entropy
import operator
複製程式碼
def calcShannonEnt(dataSet):
numEntries = len(dataSet)
labelCounts = {}
for featVec in dataSet: #the the number of unique elements and their occurance
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) #log base 2
return shannonEnt
複製程式碼
程式思路:
- 首先計算資料集中例項的總數,由於程式碼中多次用到這個值,為了提高程式碼效率,我們顯式地宣告一個變數儲存例項總數。
- 然後 ,建立一個資料字典labelCounts,它的鍵值是最後一列(分類的結果)的數值.如果當前鍵值不存在,則擴充套件字典並將當前鍵值加入字典。每個鍵值都記錄了當前類別出現的次數。
- 最後 , 使用所有類標籤的發生頻率計算類別出現的概率。我們將用這個概率計算夏農熵。
讓我們來測試一下,先自己定義一個資料集。
下表的資料包含 5 個海洋動物,特徵包括:不浮出水面是否可以生存,以及是否有腳蹼。我們可以將這些動物分成兩類: 魚類和非魚類。
根據上面的表格,我們可以定義一個createDataSet函式。
參考程式碼如下:
def createDataSet():
dataSet = [[1, 1, 'yes'],
[1, 1, 'yes'],
[1, 0, 'no'],
[0, 1, 'no'],
[0, 1, 'no']]
labels = ['no surfacing','flippers']
#change to discrete values
return dataSet, labels
複製程式碼
把所有的程式碼都放在trees.py中(以下在jupyter)
cd /home/fangyang/桌面/machinelearninginaction/Ch03
/home/fangyang/桌面/machinelearninginaction/Ch03
import trees
myDat, labels = trees.createDataSet()
myDat #old data set
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
labels
['no surfacing', 'flippers']
trees.calcShannonEnt(myDat) #calculate the entropy
0.9709505944546686
myDat[0][-1]='maybe' #change the result ,and look again the entropy
myDat #new data set
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
trees.calcShannonEnt(myDat) # the new entropy
1.3709505944546687
我們可以看到當結果分類改變,熵也發生裡變化,主要是因為最後的結果發生裡改變,相應的概率也發生了改變,根據公式,熵也會改變。
1.2 劃分資料集
前面已經得到了如何去求資訊熵的函式,但我們的劃分是以哪個特徵劃分的呢,不知道,所以我們還要寫一個以給定特徵劃分資料集的函式。
參考程式碼如下:
def splitDataSet(dataSet, axis, value):
retDataSet = []
for featVec in dataSet:
if featVec[axis] == value:
reducedFeatVec = featVec[:axis] #chop out axis used for splitting
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
return retDataSet
複製程式碼
函式的三個輸人蔘數:待劃分的資料集(dataSet)、劃分資料集的特徵(axis)、特徵的返回值(value)。輸出是劃分後的資料集(retDataSet)。
小知識:python語言在函式中傳遞的是列表的引用 ,在函式內部對列表物件的修改, 將會影響該列表物件的整個生存週期。為了消除這個不良影響 ,我們需要在函式的開始宣告一個新列表物件。 因為該函式程式碼在同一資料集上被呼叫多次,為了不修改原始資料集,建立一個新的列表物件retDataSet。
這個函式也挺簡單的,根據axis的值所指的物件來進行劃分資料集,比如axis=0,就按照第一個特徵來劃分,featVec[:axis]就是空,下面經過一個extend函式,將featVec[axis+1:]後面的數存到reduceFeatVec中,然後通過append函式以列表的形式存到retDataSet中。
這裡說一下entend和append函式的功能,舉個例子吧。
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]
可見append函式是直接將b的原型匯入a中,extend是將b中的元素匯入到a中
下面再來測試一下
myDat, labels = trees.createDataSet() #initialization
myDat
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
trees.splitDataSet(myDat,0,1) #choose the first character to split the dataset
[[1, 'yes'], [1, 'yes'], [0, 'no']]
trees.splitDataSet(myDat,0,0)# change the value ,look the difference of previous results
[[1, 'no'], [1, 'no']]
好了,我們知道了怎樣以某個特徵劃分資料集了,但我們需要的是最好的資料集劃分方式,所以要結合前面兩個函式,計算以每個特徵為劃分方式,相應最後的資訊熵,我們要找到最大資訊熵,它所對應的特徵就是我們要找的最好劃分方式。所以有了函式chooseBestFeatureToSpilt 參考程式碼如下:
def chooseBestFeatureToSplit(dataSet):
numFeatures = len(dataSet[0]) - 1 #the last column is used for the labels
baseEntropy = calcShannonEnt(dataSet) #calculate the original entropy
bestInfoGain = 0.0; bestFeature = -1
for i in range(numFeatures): #iterate over all the features
featList = [example[i] for example in dataSet]#create a list of all the examples of this feature
uniqueVals = set(featList) #get a set of unique values
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 #calculate the info gain; ie reduction in entropy
if (infoGain > bestInfoGain): #compare this to the best gain so far
bestInfoGain = infoGain #if better than current best, set to best
bestFeature = i
return bestFeature #returns an integer
複製程式碼
這個函式就是把前面兩個函式整合起來了,先算出特徵的數目,由於最後一個是標籤,不算特徵,所以以資料集長度來求特徵數時,要減1。然後求原始的資訊熵,是為了跟新的資訊熵,進行比較,選出變化最大所對應的特徵。這裡有一個雙重迴圈,外迴圈是按特徵標號進行迴圈的,下標從小到大,featList是特徵標號對應下的每個樣本的值,是一個列表,而uniqueVals是基於這個特徵的所有可能的值的集合,內迴圈做的是以特徵集合中的每一個元素作為劃分,最後求得這個特徵下的平均資訊熵,然後原始的資訊熵進行比較,得出資訊增益,最後的if語句是要找到最大資訊增益,並得到最大資訊增益所對應的特徵的標號。
現在來測試測試
import trees
myDat, labels = trees.createDataSet()
trees.chooseBestFeatureToSplit(myDat) #return the index of best character to split
複製程式碼
0
1.3 遞迴構建決策樹
好了,到現在,我們已經知道如何基於最好的屬性值去劃分資料集了,現在進行下一步,如何去構造決策樹
決策樹的實現原理:得到原始資料集, 然後基於最好的屬性值劃分資料集,由於特徵值可能多於兩個,因此可能存在大於兩個分支的資料集劃分。第一次劃分之後, 資料將被向下傳遞到樹分支的下一個節點, 在這個節點上 ,我們可以再次劃分資料。因此我們可以採用遞迴的原則處理資料集。
遞迴結束的條件是:程式遍歷完所有劃分資料集的屬性, 或者每個分支下的所有例項都具有相同的分類。
這裡先構造一個majorityCnt函式,它的作用是返回出現次數最多的分類名稱,後面會用到
def majorityCnt(classList):
classCount={}
for vote in classList:
if vote not in classCount.keys(): classCount[vote] = 0
classCount[vote] += 1
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
複製程式碼
這個函式在實戰一中的一個函式是一樣的,複述一遍,classCount定義為儲存字典,每當,由於後面加了1,所以每次出現鍵值就加1,就可以就算出鍵值出現的次數裡。最後通過sorted函式將classCount字典分解為列表,sorted函式的第二個引數匯入了運算子模組的itemgetter方法,按照第二個元素的次序(即數字)進行排序,由於此處reverse=True,是逆序,所以按照從大到小的次序排列。
讓我們來測試一下
import numpy as np
classList = np.array(myDat).T[-1]
複製程式碼
classList
複製程式碼
array(['yes', 'yes', 'no', 'no', 'no'], dtype='|S21')
majorityCnt(classList) #the number of 'no' is 3, 'yes' is 2,so return 'no'
複製程式碼
‘no’ 接下來是建立決策樹函式
程式碼如下:
def createTree(dataSet,labels):
classList = [example[-1] for example in dataSet]
if classList.count(classList[0]) == len(classList):
return classList[0]#stop splitting when all of the classes are equal
if len(dataSet[0]) == 1: #stop splitting when there are no more features in dataSet
return majorityCnt(classList)
bestFeat = chooseBestFeatureToSplit(dataSet)
bestFeatLabel = labels[bestFeat]
myTree = {bestFeatLabel:{}}
del(labels[bestFeat]) #delete the best feature , so it can find the next best feature
featValues = [example[bestFeat] for example in dataSet]
uniqueVals = set(featValues)
for value in uniqueVals:
subLabels = labels[:] #copy all of labels, so trees don't mess up existing labels
myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
return myTree
複製程式碼
前面兩個if語句是判斷分類是否結束,當所有的類都相等時,也就是屬於同一類時,結束再分類,又或特徵全部已經分類完成了,只剩下最後的class,也結束分類。這是判斷遞迴結束的兩個條件。一般開始的時候是不會執行這兩步的,先選最好的特徵,使用 chooseBestFeatureToSplit函式得到最好的特徵,然後進行分類,這裡建立了一個大字典myTree,它將決策樹的整個架構全包含進去,這個等會在測試的時候說,然後對資料集進行劃分,用splitDataSet函式,就可以得到劃分後新的資料集,然後再進行createTrees函式,直到遞迴結束。
來測試一下
myTree = trees.createTree(myDat,labels)
複製程式碼
myTree
複製程式碼
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
再來說說上面沒詳細說明的大字典,myTree是特徵是‘no surfacing’,根據這個分類,得到兩個分支‘0’和‘1‘,‘0’分支由於全是同一類就遞迴結束裡,‘1’分支不滿足遞迴結束條件,繼續進行分類,它又會生成它自己的字典,又會分成兩個分支,並且這兩個分支滿足遞迴結束的條件,所以返回‘no surfacing’上的‘1’分支是一個字典。這種巢狀的字典正是決策樹演算法的結果,我們可以使用它和Matplotlib來進行畫決策
1.4 使用決策樹執行分類
這個就是將測試合成一個函式,定義為classify函式
參考程式碼如下:
def classify(inputTree,featLabels,testVec):
firstStr = inputTree.keys()[0]
secondDict = inputTree[firstStr]
featIndex = featLabels.index(firstStr)
key = testVec[featIndex]
valueOfFeat = secondDict[key]
if isinstance(valueOfFeat, dict):
classLabel = classify(valueOfFeat, featLabels, testVec)
else: classLabel = valueOfFeat
return classLabel
複製程式碼
這個函式就是一個根據決策樹來判斷新的測試向量是那種型別,這也是一個遞迴函式,拿上面決策樹的結果來說吧。
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},這是就是我們的inputTree,首先通過函式的第一句話得到它的第一個bestFeat,也就是‘no surfacing’,賦給了firstStr,secondDict就是‘no surfacing’的值,也就是 {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}},然後用index函式找到firstStr的標號,結果應該是0,根據下標,把測試向量的值賦給key,然後找到對應secondDict中的值,這裡有一個isinstance函式,功能是第一個引數的型別等於後面引數的型別,則返回true,否則返回false,testVec列表第一位是1,則valueOfFeat的值是 {0: 'no', 1: 'yes'},是dict,則遞迴呼叫這個函式,再進行classify,知道不是字典,也就最後的結果了,其實就是將決策樹過一遍,找到對應的labels罷了。
這裡有一個小知識點,在jupyter notebook中,顯示綠色的函式,可以通過下面查詢它的功能,例如
isinstance? #run it , you will see a below window which is used to introduce this function
複製程式碼
讓我們來測試測試
trees.classify(myTree,labels,[1,0])
複製程式碼
‘no’
trees.classify(myTree,labels,[1,1])
複製程式碼
‘yes'
1.5 決策樹的儲存
構造決策樹是很耗時的任務,即使處理很小的資料集, 如前面的樣本資料, 也要花費幾秒的時間 ,如果資料集很大,將會耗費很多計算時間。然而用建立好的決策樹解決分類問題,可以很快完成。因此 ,為了節省計算時間,最好能夠在每次執行分類時呼叫巳經構造好的決策樹。
解決方案:使用pickle模組儲存決策樹
參考程式碼:
def storeTree(inputTree,filename):
import pickle
fw = open(filename,'w')
pickle.dump(inputTree,fw)
fw.close()
def grabTree(filename):
import pickle
fr = open(filename)
return pickle.load(fr)
複製程式碼
就是將決策樹寫到檔案中,用的時候在取出來,測試一下就明白了
trees.storeTree(myTree,'classifierStorage.txt') #run it ,store the tree
複製程式碼
trees.grabTree('classifierStorage.txt')
複製程式碼
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}} 決策樹的構造部分結束了,下面介紹怎樣繪製決策樹
2. 使用Matplotlib註解繪製樹形圖
前面我們看到決策樹最後輸出是一個大字典,非常醜陋,我們想讓它更有層次感,更加清晰,最好是圖形狀的,於是,我們要Matplotlib去畫決策樹。
2.1 Matplotlib註解
Matplotlib提供了一個註解工具annotations,它可以在資料圖形上新增文字註釋。
建立一個treePlotter.py檔案來儲存畫圖的相關函式.
首先是使用文字註解繪製樹節點,參考程式碼如下:
import matplotlib.pyplot as plt
decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle="<-")
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',\
xytext=centerPt, textcoords='axes fraction',\
va="center", ha="center", bbox=nodeType, arrowprops=arrow_args )
def createPlot1():
fig = plt.figure(1, facecolor='white')
fig.clf()
createPlot.ax1 = plt.subplot(111, frameon=False) #ticks for demo puropses
plotNode('a decision node', (0.5, 0.1), (0.1, 0.5), decisionNode)
plotNode('a leaf node', (0.8, 0.1), (0.3, 0.8), leafNode)
plt.show()
複製程式碼
前面三行是定義文字框和箭頭格式,decisionNode是鋸齒形方框,文字框的大小是0.8,leafNode是4邊環繞型,跟矩形類似,大小也是4,arrow_args是指箭頭,我們在後面結果是會看到這些東西,這些資料以字典型別儲存。第一個plotNode函式的功能是繪製帶箭頭的註解,輸入引數分別是文字框的內容,文字框的中心座標,父結點座標和文字框的型別,這些都是通過一個createPlot.ax1.annotate函式實現的,create.ax1是一個全域性變數,這個函式不多將,會用就行了。第二個函式createPlot就是生出圖形,也沒什麼東西,函式第一行是生成影象的畫框,橫縱座標最大值都是1,顏色是白色,下一個是清屏,下一個就是分圖,111中第一個1是行數,第二個是列數,第三個是第幾個圖,這裡就一個圖,跟matlab中的一樣,matplotlib裡面的函式都是和matlab差不多。
來測試一下吧
reset -f #clear all the module and data
複製程式碼
cd 桌面/machinelearninginaction/Ch03
複製程式碼
/home/fangyang/桌面/machinelearninginaction/Ch03
import treePlotter
import matplotlib.pyplot as plt
複製程式碼
treePlotter.createPlot1()
複製程式碼
2.2 構造註解樹
繪製一棵完整的樹需要一些技巧。我們雖然有 x 、y 座標,但是如何放置所有的樹節點卻是個問題,我們必須知道有多少個葉節點,以便可以正確確定x軸的長度;我們還需要知道樹有多少層,以便可以正確確定y軸的高度。這裡定義了兩個新函式getNumLeafs()和getTreeDepth(),以求葉節點的數目和樹的層數。
參考程式碼:
def getNumLeafs(myTree):
numLeafs = 0
firstStr = myTree.keys()[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
numLeafs += getNumLeafs(secondDict[key])
else: numLeafs +=1
return numLeafs
def getTreeDepth(myTree):
maxDepth = 0
firstStr = myTree.keys()[0]
secondDict = myTree[firstStr]
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
thisDepth = 1 + getTreeDepth(secondDict[key])
else: thisDepth = 1
if thisDepth > maxDepth: maxDepth = thisDepth
return maxDepth
複製程式碼
我們可以看到兩個方法有點似曾相識,沒錯,我們在進行決策樹分類測試時,用的跟這個幾乎一樣,分類測試中的isinstance函式換了一種方式去判斷,遞迴依然在,不過是每遞迴依次,高度增加1,葉子數同樣是檢測是否為字典,不是字典則增加相應的分支。
這裡還寫了一個函式retrieveTree,它的作用是預先儲存的樹資訊,避免了每次測試程式碼時都要從資料中建立樹的麻煩
參考程式碼如下
def retrieveTree(i):
listOfTrees =[{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
{'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
]
return listOfTrees[i]
複製程式碼
這個沒什麼好說的,就是把決策樹的結果存在一個函式中,方便呼叫,跟前面的儲存決策樹差不多。
有了前面這些基礎後,我們就可以來畫樹了。
參考程式碼如下:
def plotMidText(cntrPt, parentPt, txtString):
xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
def plotTree(myTree, parentPt, nodeTxt):#if the first key tells you what feat was split on
numLeafs = getNumLeafs(myTree) #this determines the x width of this tree
depth = getTreeDepth(myTree)
firstStr = myTree.keys()[0] #the text label for this node should be this
cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
plotMidText(cntrPt, parentPt, nodeTxt)
plotNode(firstStr, cntrPt, parentPt, decisionNode)
secondDict = myTree[firstStr]
plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
for key in secondDict.keys():
if type(secondDict[key]).__name__=='dict':#test to see if the nodes are dictonaires, if not they are leaf nodes
plotTree(secondDict[key],cntrPt,str(key)) #recursion
else: #it's a leaf node print the leaf node
plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
#if you do get a dictonary you know it's a tree, and the first element will be another dict
def createPlot(inTree):
fig = plt.figure(1, facecolor='white')
fig.clf()
axprops = dict(xticks=[], yticks=[])
createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)
plotTree.totalW = float(getNumLeafs(inTree))
plotTree.totalD = float(getTreeDepth(inTree))
plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
plotTree(inTree, (0.5,1.0), '')
plt.show()
複製程式碼
第一個函式是在父子節點中填充文字資訊,函式中是將父子節點的橫縱座標相加除以2,上面寫得有一點點不一樣,但原理是一樣的,然後還是在這個中間座標的基礎上新增文字,還是用的是 createPlot.ax1這個全域性變數,使用它的成員函式text來新增文字,裡面是它的一些引數。
第二個函式是關鍵,它呼叫前面我們說過的函式,用樹的寬度用於計算放置判斷節點的位置 ,主要的計算原則是將它放在所有葉子節點的中間,而不僅僅是它子節點的中間,根據高度就可以平分座標系了,用座標系的最大值除以高度,就是每層的高度。這個plotTree函式也是個遞迴函式,每次都是呼叫,畫出一層,知道所有的分支都不是字典後,才算畫完。每次檢測出是葉子,就記錄下它的座標,並寫出葉子的資訊和父子節點間的資訊。plotTree.xOff和plotTree.yOff是用來追蹤已經繪製的節點位置,以及放置下一個節點的恰當位置。
第三個函式我們之前介紹介紹過一個類似,這個函式呼叫了plotTree函式,最後輸出樹狀圖,這裡只說兩點,一點是全域性變數plotTree.totalW儲存樹的寬度 ,全 局變數plotTree.totalD儲存樹的深度,還有一點是plotTree.xOff和plotTree.yOff是在這個函式這裡初始化的。
最後我們來測試一下
cd 桌面/machinelearninginaction/Ch03
複製程式碼
/home/fangyang/桌面/machinelearninginaction/Ch03
import treePlotter
myTree = treePlotter.retrieveTree(0)
treePlotter.createPlot(myTree)
複製程式碼
改變標籤,重新繪製圖形
myTree['no surfacing'][3] = 'maybe'
treePlotter.createPlot(myTree)
複製程式碼
至此,用matplotlib畫決策樹到此結束。
3 使用決策樹預測眼睛型別
隱形眼鏡資料集是非常著名的資料集 , 它包含很多患者眼部狀況的觀察條件以及醫生推薦的隱形眼鏡型別 。隱形眼鏡型別包括硬材質 、軟材質以及不適合佩戴 隱形眼鏡 。資料來源於UCI資料庫 ,為了更容易顯示資料 , 將資料儲存在原始碼下載路徑的文字檔案中。
進行測試
import trees
lensesTree = trees.createTree(lenses,lensesLabels)
fr = open('lenses.txt')
lensesTree = trees.createTree(lenses,lensesLabels)
lenses = [inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels = ['age' , 'prescript' , 'astigmatic','tearRate']
lensesTree = trees.createTree(lenses,lensesLabels)
複製程式碼
lensesTree
{'tearRate': {'normal': {'astigmatic': {'no': {'age': {'pre': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft'}}, 'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses', 'presbyopic': 'no lenses', 'young': 'hard'}}, 'myope': 'hard'}}}}, 'reduced': 'no lenses'}}
這樣看,非常亂,看不出什麼名堂,畫出決策樹樹狀圖看看
treePlotter.createPlot(lensesTree)
這就非常清楚了,但還是有一個問題,決策樹非常好地匹配了實驗資料,然而這些匹配選項可能太多了,我們將這種問題稱之為過度匹配(overfitting),為了減少過度匹配問題,我們可以裁剪決策樹,去掉一些不必要的葉子節點。如果葉子節點只能增加少許資訊, 則可以刪除該節點, 將它並人到其他葉子節點中,這個將在後面討論吧!
結尾
這篇notebook寫了兩天多,接近三天,好累,希望這篇關於決策樹的部落格能夠幫助到你,如果發現錯誤,還望不吝指教,謝謝!
相關文章和視訊推薦
圓方圓學院彙集 Python + AI 名師,打造精品的 Python + AI 技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。
公開課地址:ke.qq.com/course/3627…
加入python學習討論群 78486745 ,獲取資料,和廣大群友一起學習。