機器學習算法·決策樹和朴素貝葉斯算法
一、問題描述
1912年當時世界上體積最龐大,內部設施最豪華的客運輪船’泰坦尼克號’,擁有美譽‘永不沉沒’。然而在第一次下水穿越大西洋時,就在航行中撞上冰山,永遠沉沒海底。船上喪生者達到1500多人。假如我們穿越時空回到了過去,成為船上的一名普通乘客,那么我們有多大的存活率?試根據船上乘客的各種信息特征來預測一下生存率。
二、數據准備與預處理
數據可以通過Kggle網站獲取,分為測試集合訓練集。由於獲取的數據,測試集將結果與特征信息分開了。為了便於將統一對測試集合訓練集進行清洗,所以首先將原始測試集中的特征信息與結果放入到了同一個文件之中。數據樣式如下表一所示:
表1 泰坦尼克號乘客信息表
因為所給的數據之中Age,Embarked,Cabin三個數據集存在元素缺失,根據經驗,分別用平均值和眾數來填充Age特征,並以20歲為一個年齡階段來划分年齡段和Embarked特征。Cabin缺失數據較多,根據Cabin的有無將該特征分為兩大類。
三、數據的可視化分析
基於以上對原始數據的初步清洗之后,開始對數據進行可視化分析,並以此來選出有效特征。一般來說,乘客的姓名和船票號碼對於乘客的生存率是沒有影響的。因為票價與乘客的乘船距離和船艙等級都有關系,因此不作為本預測模的特征。下面通過Python里面的pandas庫,對剩余的各個數據特征進行逐一分析。
圖 1 數據特征可視化分析
由以上圖表可以看出來船艙等級和性別,以及是否有船艙對於生存率有較大影響。同時其他特征對於生存狀況也有不同程度的影響。所以采用這些特征作為決策樹的決策特征。
四、決策樹算法原理及實現
4.1決策樹算法基本原理
決策樹基本原理很簡單,就是通過對一個擁有許多特征的數據集按照某種方式來進行划分,在該種划分模式下得到結果的准確性最高。在樹中的每一個結點是對對象屬性的一種判斷條件,支點表示符合結點的對象,葉子節點表示的是分類結果。構造決策樹的過程也就是將數據集進行划分的過程,使得無序的數據變得更加有序。描述數據的混亂程度,可以用信息熵或者稱為香農熵的量來定量衡量。當數據越混亂時,信息熵越大。信息熵可以用下列公式進行計算:
其中xi表示一種分類方式,p(xi)表示該分類的概率。
因此在進行特征選擇的時候,需要計算信息增益,也就是混亂度的減少量。信息增益越高,那么對這個特征進行划分也就是最好的方式。選擇信息增益最大的方式進行划分。算法的主要步驟如下:
Step1:對數據集每個特征進行划分,計算每種划分后的信息增益
Step2:選出信息增益最大的划分方式對所有特征進行划分
Step3:遞歸,直到所有特征划分完成。完成決策樹的構建
Step4:將生成的數進行可視化,在圖形窗口繪出決策樹
Step4:利用生成的決策樹對測試集進行預測
算法的主要部分代碼如圖2所示。繪出的決策樹和程序運行計算的准確率如下圖11.所示。從運行結果來看,預測的准確率較高。但是為了進一步提高算法的有效性,需要采取決策樹的剪枝處理。
圖 2決策樹算法的主要程序
圖 3未經剪枝的決策樹
圖 4未剪枝處理的決策樹運行結果
4.2決策樹預剪枝處理
決策樹在進行划分的時候,會划分出某些不必要的節點。在划分之后,決策樹預測的准確率的期望可能會降低。如果能在划分之前預先看一下是否會出現這樣的情況,那么對於算法的優化有很大的幫助。預剪枝的主要思想為:在划分結點時,計算划分之前和划分之后在訓練集上的准確率,如果沒有提高其在訓練集上的准確率則不進行划分。預剪枝的實現主要通過下面兩個函數。
判斷結點是否划分用如下方式表達:
if Testing(myTree,dataTest)<testingMajor(majorityCnt(classList),dataTest): return myTree
剪枝處理的結果如圖5所示:
圖 5剪枝處理之后的運行結果
圖 6剪枝后的決策樹
從運行的結果來看進行過剪枝之后的決策樹在形式上顯得更加簡潔,從准確率來看在算法的效果也更好。
五、朴素貝葉斯算法原理與實現
朴素貝葉斯算法是基於概率論,對各個特征出現進行了獨立假設的一個算法。假如有兩個事件A、B,如果已知A事件發生時B事件的發生概率P(B|A)以及A、B發生的概率P(A)、P(B),那么根據貝葉斯決策理論能計算出在B事件發生的條件下A事件的概率P(A|B).計算公式如下:
這就是貝葉斯全概率公式。假如數據集有n種不同的分類結果,要判斷一個樣本B的分類,只需要分別計算它在所有情況下發生的條件概率:
比較它們的大小,找出其中最大的那個概率對應的類別Amax作為該樣本的類別。對每個樣本都進行相同的分類,這樣對每個樣本都有最大概率准確判斷它的類別。下面是在上述問題的背景下的算法偽代碼:
Step1:找出樣本中出現的所有特征屬性featList,並計算每種類別的概率Pi
Step2:把樣本的特征向量轉化為以featList相同長度的只包含0、1的向量,其中1代表該 樣本中出現了featList中的該屬性;0代表沒有出現
Step3:計算每種類別的樣本每個特征出現的概率
Step4:根據每個樣本中的特征屬性出現的情況計算它是每種類別的概率選出其中最大的作 為該樣本的特征。
Step5:對數據集中所有樣本進行分類,計算預測的准確率
准備好數據集之后,對數據進行了測試,結果如圖7所示。
圖7 貝葉斯算法運行結果
從測試的結果來看效果並不太好,對訓練集和測試集進行互換之后結果相似。計算的准確率不高的原因,應該是在對數據集的使用采用了獨立性假設,而實際情況可能會出現兩種屬性想依賴的關系。目前尚未找到改進算法的方式,這個工作留給以后對概率有了更深的理解之后在研究。
六、總結
本次實驗使用了兩個問題的數據集對兩個機器學習的分類算法進行了測試和改進,從整體上來看它們都能夠達到了預期的目標。在算法思想方面,它們分別從混亂度和概率兩個完全不同的角度來對數據集進行分類。事實上在日常的生活之中,我們也會經常用到這兩種方法,應用到計算機上也是人類智慧的一個延伸。對於不同的問題需要采用不同的方法,也需要根據實際情況進行合理的改進。在決策樹的實現過程中采用了剪枝之后我們發現准確率有了很大的提高,決策樹也變得簡潔了許多,從而實際意義更大。
對於不同的算法它們共同的難點都在於特征的選取。但是在特征選取之后,即使是相同的數據集,不同算法在具體使用過程所需要考慮的問題也不相同。比如在決策樹的構建中對結點進行划分的過程,我們對不能完全划分的點采用投票處理,選概率大的作為樣本的標簽。而在朴素貝葉斯算法中,在計算全概率的時候使用對數加法代替了懲罰,從而使得計算更加方便。通過對算法的實例操作,不僅能夠提高對算法本身的理解,也能從總獲取許多思想,並應用到現實生活之中。
七、代碼
1.朴素貝葉斯算法
#本模塊用於貝葉斯算法的實現 from numpy import* from math import* import pandas as pd import numpy as np import os ###############___loadTxtDaat__############### def loadData(filePath):#准備數據,輸入文件 f=open(filePath)#打開文件 lines=f.readlines() trainData=[]#記錄文檔數組 trainLabel=[]#記錄類標簽 for line in lines: Line=line.strip('\n') data=Line.split('\t') trainLabel.append(data[-1]) trainData.append(data[:-1]) return trainData,trainLabel def creatVocabList(dataSet):#創建包含所有出現特征的數據集,需要將數字轉化為字符處理 vocabSet=set([])#創建一個空集 for document in dataSet: vocabSet=vocabSet|set(document)#創建兩個集合的並 return list(vocabSet) def setOfWords2Vec(vocabList,inputSet):#將文本轉化為數字列表 returnVec=[0]*len(vocabList)#創建一個其中所含元素都為0的向量 for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)]=1 return returnVec def bagOfWords2Vec(vocabList,inputSet):#袋裝模型,也記錄下每個詞出現的個數 returnVec=[0]*len(vocabList)#信件一個長度為len(vocabList)的0列表 for word in inputSet: if word in vocabList: returnVec[vocabList.index[word]]+=1 return returnVec ###############_創建有大於2中類別的數據__######################## def trainNB(trainMatrix,trainCategory):#輸出特征概率數組,每種類別出現的概率 Class=set(trainCategory)#存儲所有類別 Class=[data for data in Class] classNum=len(Class)#類別的個數 numTrainDoc=len(trainMatrix)#樣本集的個數 numWord=len(trainMatrix[0])#特征的個數 count=array([0]*classNum)#建立一個空列表存儲所有類別出現次數的數據 for i in range(classNum):#計算每個類別出現的個數 for j in range(len(trainCategory)): if trainCategory[j]==Class[i]: count[i]+=1 pClass=count/numTrainDoc#計算每種類別的概率 pNum=[ones(numWord) for i in range(classNum)]#為每種類別創建一個保存特征數目的向量 pDenom=[2 for i in range(classNum)]#為每個類別創建一個記錄元素總數的向量 for k in range(numTrainDoc): for i in range(classNum): if trainCategory[k]==Class[i]: pNum[i]+=trainMatrix[k] pDenom+=sum(trainMatrix[k]) pVect=[[] for i in range(classNum)] for i in range(classNum):#計算每個特征出現的概率 pVect[i]=vectorize(log)(pNum[i]/pDenom[i]) return pVect,pClass,Class ########################__classify__################# def classifyNB(vec2Calssify,pVec,pClass,Class): p=[0]*len(pClass) for i in range(len(pClass)): p[i]=sum(vec2Calssify*pVec[i])+log(pClass[i]) idx=p.index(max(p)) return Class[idx] #################__test__########################### def testingNB(trainData,trainLabel,testData,testLabel):#貝葉斯測函數,輸出測試的准確率 myVocabList=creatVocabList(trainData) trainMat=[] for daat in trainData: trainMat.append(setOfWords2Vec(myVocabList,daat)) pVect,pClass,Class=trainNB(array(trainMat),array(trainLabel)) count=0 for i in range(len(testData)): thisDoc=array(setOfWords2Vec(myVocabList,testData[i])) label=classifyNB(thisDoc,pVect,pClass,Class) if label==testLabel[i]: count+=1 corRate=100*count/len(testData) print('測試的准確率為:%.2f%%'%float(corRate)) return corRate ########################__getCsvData__############################### def get_data(filename):#清洗數據,獲取可以使用的數據 dataSet=pd.read_csv(filename) mean=dataSet['Age'].mean()#用平均值代替空缺的年齡 dataSet=dataSet.fillna({'Cabin':'Lost','Age':mean,'Embarked':'S'})#處理完Cabin,Embarked dataSet['Age']=(dataSet['Age']/20).apply(np.round)+1 label=dataSet['Survived'].values.tolist() dataSet=dataSet.drop(['Name','PassengerId','Fare','Ticket','Survived','SibSp','Parch'],axis=1)#刪除不需要的列 dataSet.ix[dataSet['Cabin']!='Lost','Cabin']='Have'#修改數據 vecData=dataSet.values.tolist() return vecData,label def main(): trainFile=r'E:\PythonData\tatanic\train.csv' testFile=r'E:\PythonData\tatanic\test.csv' trainData,trainLabel=get_data(trainFile) testData,testLabel=get_data(testFile) testingNB(trainData,trainLabel,testData,testLabel) if __name__=='__main__': main()
2.決策樹算法
#dataAnalize import pandas as pd import matplotlib.pyplot as plt import numpy as np dataSet=pd.read_csv('E:\\PythonData\\tatanic\\train.csv') mean=dataSet['Age'].mean()#用平均值代替空缺的年齡 dataSet=dataSet.fillna({'Cabin':'Lost','Age':mean,'Embarked':'S'})#處理完Cabin,Embarked dataSet['Age']=(dataSet['Age']/20).apply(np.round)+1#年齡按20歲分成不同階段 dataSet.ix[dataSet['Cabin']!='Lost','Cabin']='Have' #1、總體分析 #print(dataSet.describe())#觀察樣本總體數據情況 #2、船艙等級對乘客生存狀況的影響 s_Pclass=pd.pivot_table(dataSet,index='Pclass',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Pclass['mean'].plot(kind='Bar') plt.title('創倉等級對生存狀況的影響') #3.性別對乘客生存狀況的影響 s_Sex=pd.pivot_table(dataSet,index='Sex',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Sex['mean'].plot(kind='Bar') plt.title('性別對生存狀況的影響') #4、年齡對生存狀況的影響 s_Age=pd.pivot_table(dataSet,index='Age',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Age['mean'].plot(kind='Bar') plt.title('年齡對生存狀況的影響') #5、兄弟姐妹個數對生存狀況的影響 s_SibSp=pd.pivot_table(dataSet,index='SibSp',values='Survived',aggfunc=[np.sum,len,np.mean]) s_SibSp['mean'].plot(kind='Bar') plt.title('兄弟姐妹對生存狀況的影響') #6、父母同行對生存狀況的影響 s_Parch=pd.pivot_table(dataSet,index='Parch',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Parch['mean'].plot(kind='Bar') plt.title('父母同行對生存狀況的影響') #7、登陸口對生存狀況的影響 s_Embarked=pd.pivot_table(dataSet,index='Embarked',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Embarked['mean'].plot(kind='Bar') plt.title('乘客登陸口對生存狀況的影響') #8、是否有船艙號對生存狀況的影響 s_Cabin=pd.pivot_table(dataSet,index='Cabin',values='Survived',aggfunc=[np.sum,len,np.mean]) s_Cabin['mean'].plot(kind='Bar') plt.title('是否有船艙號對生存狀況的影響') plt.show()
#decidingTree ''' 本例我們依然使用tatanic數據集用來訓練決策樹,這是一個原始模型,沒有對決策樹進行剪枝操作。 第一步對數據進行整體分析,選取特征。經分析,發現可以利用的特征有:Sex,Age,Embarked,Cabin,Parch,Sibsp ,Pclass.需要剔除的有PassangerId,Fare,Name,Ticket。所選特征中需要清洗的數據有Age,Cabin,Parch,Embarked 運算結果:准確率為85%左右 ''' ######__導入庫__############# import operator import pandas as pd from numpy import* import numpy as np from pandas import Series,DataFrame import pickle from treePlotter import* from math import log import time import copy #############################__代碼區__############################### #########__為構建樹准備__######### #計算數據集的香農熵,香農熵越小說明數據集的混亂度越小 def calcShannonEnt(dataSet):#計算香農熵的函數 numEntries=len(dataSet)#計算數據集中實例的總數 labelCounts={}#創建一個字典 for featVec in dataSet:#對於dataSet中的每個數據 currentLabel=featVec[-1]#currentLabel儲存當前該數據的標簽 if currentLabel not in labelCounts.keys():#如果標簽不在labelCout字典中 labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 shannonEnt=0.0 for key in labelCounts: prob=float(labelCounts[key])/numEntries shannonEnt-=prob*log(prob,2) return shannonEnt #數據類型如下所示 def creatDataSet(): dataSet=[[1,1,'yes'], [1,1,'yes'], [1,0,'no'], [0,1,'no'], [0,1,'no']] labels=['no surfacing','flippers'] return dataSet,labels #多數據集按照第axis中的value屬性進行划分 def splitDataSet(dataSet,axis,value): retDataSet=[] for featVec in dataSet: if featVec[axis]==value: #下面兩行用來確定出去featVec[axis]之外的其他元素。 reducedFeatVec=featVec[:axis]#表示featVec[0],[1]...[axis-1] reducedFeatVec.extend(featVec[axis+1:])#表示a[axis+1],[axis+2]...[-1] retDataSet.append(reducedFeatVec) return retDataSet #選出最好的划分方式,使得划分后的數據集的香農熵最低,也就是混亂度最低 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]#用於存放第i列特征 uniqueVals=set(featList)#set用於創建唯一的集合類型如:set(1,1,1)=1 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 #當所有特征都遍歷完成之后還不能完全確定時,就進行投票選出最概然標簽 def majorityCnt(classList):#在葉子節點時根據投票,選擇票數最多的作為該分類的標簽 classCount={} for vote in classList: if vote not in classCount.keys():classCount[vote]=0 classCount[vote]+=1 sortedClassCount=sorted(classCount.items(),\ key=operator.itemgetter(1),reverse=True)#排序 return sortedClassCount[0][0] ##################__創建樹__############### ''' def creatTree(dataSet,labels): Labels=labels[:]#此處應當將lables淺復制一下,避免對原數據進行了修改 classList=[example[-1] for example in dataSet]#classLIst存放數據中的所有類('yes','no'...) 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:#對於該特征中的每一個屬性 subLabels=Labels[:]#下面進行決策樹的遞歸構建 myTree[bestFeatLabel][value]=creatTree(splitDataSet(dataSet,bestFeat,value),subLabels) return myTree def storeTree(inputTree,filename):# fw=open(filename,'wb')#注意此處應該要把打開方式變為'wb',因為下面pickle默認以二進制方式打開 pickle.dump(inputTree,fw) fw.clo def grabTree(filename): fr=open(filename,'rb')#此處也應該改為'rb'以二進制方式打開 return pickle.load(fr) ''' ###############__構建決策樹分類器__############# def classify(inputTree,featLabels,testVec):#輸入構造好的決策樹 firstStr=list(inputTree.keys())[0]#第一層 secondDict=inputTree[firstStr]#第二層 featIndex=featLabels.index(firstStr)#特征值得索引 for key in list(secondDict.keys()): if testVec[featIndex]==key: if type(secondDict[key]).__name__=='dict': global classLabel#注意局部變量與全局變量的關系,否則會報錯 classLabel=classify(secondDict[key],featLabels,testVec) else: classLabel=secondDict[key] return classLabel ###############__獲取數據並進行預測__################## def get_data(filename):#清洗數據,獲取可以使用的數據 dataSet=pd.read_csv(filename) mean=dataSet['Age'].mean()#用平均值代替空缺的年齡 dataSet=dataSet.fillna({'Cabin':'Lost','Age':mean,'Embarked':'S'})#處理完Cabin,Embarked dataSet['Age']=(dataSet['Age']/20).apply(np.round)+1 dataSet['Survived2']=dataSet['Survived'] dataSet=dataSet.drop(['Name','PassengerId','Fare','Ticket','Survived','SibSp','Parch','Cabin'],axis=1)#刪除不需要的列 #dataSet.ix[dataSet['Cabin']!='Lost','Cabin']='Have' returnData=dataSet.values.tolist() return returnData def test(test_data,inputTree,featLabels): errors=0.0 for i in range(len(test_data)): classResult=classify(inputTree,featLabels,test_data[i][:-1]) if classResult!=test_data[i][-1]: errors+=1 return errors def predict(test_data,inputTree,featLabels):#參數為待測數據,決策樹,特征標簽 classCount=0.0#用於記錄正確的次數 lenOfDatas=len(test_data)#測試集的個數 for i in range(lenOfDatas):#統計測試成功的個數 classResult=classify(inputTree,featLabels,test_data[i][:-1]) if classResult==test_data[i][-1]: classCount+=1 corRate=float(100*classCount/lenOfDatas)#計算准確率 corRate=round(corRate,2) return(corRate) #########################草稿################## def Testing(myTree,dataTest):#對樹進行測試 temp_labels=['Pclass','Sex','Age','Embarked'] error=0.0 for i in range(len(dataTest)): result=classify(myTree,temp_labels,dataTest[i][:-1]) if dataTest[i][-1]!=result: error+=1 #print('tree%f'%error) return float(error) def testingMajor(major,dataTest):#測試 error=0.0 for i in range(len(dataTest)): if major!=dataTest[i][-1]: error+=1 #print('major%f'%error) return float(error) #創建決策樹 def creatTree(dataSet,labels,dataTest): Labels=copy.deepcopy(labels)#此處應當將lables深復制一下,避免對原數據進行了修改 classList=[example[-1] for example in dataSet]#classLIst存放數據中的所有類('yes','no'...) 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:#對於該特征中的每一個屬性 subLabels=Labels[:]#下面進行決策樹的遞歸構建 myTree[bestFeatLabel][value]=creatTree(splitDataSet(dataSet,bestFeat,value),subLabels,dataTest) if Testing(myTree,dataTest)<testingMajor(majorityCnt(classList),dataTest): return myTree return majorityCnt(classList) #return myTree ########################__主函數__########################### def main(): train_filename='E:\\PythonData\\tatanic\\test.csv' test_filename='E:\\PythonData\\tatanic\\train.csv' #feat_labels=['Pclass','Sex','Age','SibSp','Parch','Cabin','Embarked'] feat_labels=['Pclass','Sex','Age','Embarked']#特征標簽 train_datas=get_data(train_filename)#訓練集 test_datas=get_data(test_filename)#測試集 tatanicTree=creatTree(test_datas,feat_labels,train_datas)#創建樹 #print(tatanicTree)#輸出構造好的決策樹 createPlot(tatanicTree)#繪出決策樹的圖 corRate=predict(train_datas,tatanicTree,feat_labels)#計算准確率 print('This decision-making tree\'s correct Rate is:%.2f%%'%corRate) ###########################__程序運行__############################ if __name__=='__main__': print('程序開始運行...') t1=time.time() main() t2=time.time() print('程序運行的時間為:%.2fs'%(t2-t1))
#treePlotter #本模塊主要任務為繪制決策樹,讓決策樹直觀顯示出來 import matplotlib.pyplot as plt #這里是對繪制是圖形屬性的一些定義,可以不用管,主要是后面的算法 decisionNode = dict(boxstyle="sawtooth", fc="0.8") leafNode = dict(boxstyle="round4", fc="0.8") arrow_args = dict(arrowstyle="<-") #這是遞歸計算樹的葉子節點個數,比較簡單 def getNumLeafs(myTree): numLeafs = 0 firstStr =list(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 =list(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 #這個是用來一注釋形式繪制節點和箭頭線,可以不用管 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 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 =list(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) #no ticks 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() #這個是用來創建數據集即決策樹 def retrieveTree(i): listOfTrees =[{'no surfacing': {0:{'flippers': {0: 'no', 1: 'yes'}}, 1: {'flippers': {0: 'no', 1: 'yes'}}, 2: {'flippers': {0: 'no', 1: 'yes'}}}}, {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}} ] return listOfTrees[i]