一、前言
本篇討論決策樹的原理和決策樹構(gòu)建的準(zhǔn)備工作,機(jī)器學(xué)習(xí)決策樹的原理,以及如何選擇最優(yōu)特征作為分類特征,決策樹構(gòu)建,決策樹可視化,使用決策樹進(jìn)行分類預(yù)測,決策樹的存儲和讀取以及sklearn實戰(zhàn)之預(yù)測隱形眼睛類型。
本文出現(xiàn)的所有代碼,均可在github上下載,歡迎Follow、Star:Github地址:https://github.com/yaoguangju/machine_learning
二、決策樹的基礎(chǔ)
1、決策樹是什么
決策樹是什么?決策樹(decision tree)是一種基本的分類與回歸方法。舉個通俗易懂的例子,如下圖所示的流程圖就是一個決策樹,長方形代表判斷模塊(decision block),橢圓形成代表終止模塊(terminating block),表示已經(jīng)得出結(jié)論,可以終止運行。從判斷模塊引出的左右箭頭稱作為分支(branch),它可以達(dá)到另一個判斷模塊或者終止模塊。我們還可以這樣理解,分類決策樹模型是一種描述對實例進(jìn)行分類的樹形結(jié)構(gòu)。決策樹由結(jié)點(node)和有向邊(directed edge)組成。結(jié)點有兩種類型:內(nèi)部結(jié)點(internal node)和葉結(jié)點(leaf node)。內(nèi)部結(jié)點表示一個特征或?qū)傩裕~結(jié)點表示一個類。蒙圈沒??
如下圖所示的決策樹,長方形和橢圓形都是結(jié)點。長方形的結(jié)點屬于內(nèi)部結(jié)點,橢圓形的結(jié)點屬于葉結(jié)點,從結(jié)點引出的左右箭頭就是有向邊。而最上面的結(jié)點就是決策樹的根結(jié)點(root node)。這樣,結(jié)點說法就與模塊說法對應(yīng)上了。
我們回到這個流程圖,對,你沒看錯,這就是一個假想的相親對象分類系統(tǒng)。它首先檢測相親對方是否有房。如果有房,則對于這個相親對象可以考慮進(jìn)一步接觸。如果沒有房,則觀察相親對象是否有上進(jìn)心,如果沒有,直接Say Goodbye,此時可以說:”你人很好,但是我們不合適?!比绻?,同樣也值得認(rèn)真考慮。
不過這只是個簡單的相親對象分類系統(tǒng),只是做了簡單的分類。真實情況可能要復(fù)雜得多,考慮因素也可以是五花八門。脾氣好嗎?會做飯嗎?愿意做家務(wù)嗎?家里幾個孩子?父母是干什么的?等等各種因素。
我們可以把決策樹看成一個if-then規(guī)則的集合,將決策樹轉(zhuǎn)換成if-then規(guī)則的過程是這樣的:由決策樹的根結(jié)點(root node)到葉結(jié)點(leaf node)的每一條路徑構(gòu)建一條規(guī)則;路徑上內(nèi)部結(jié)點的特征對應(yīng)著規(guī)則的條件,而葉結(jié)點的類對應(yīng)著規(guī)則的結(jié)論。決策樹的路徑或其對應(yīng)的if-then規(guī)則集合具有一個重要的性質(zhì):互斥并且完備。這就是說,每一個實例都被一條路徑或一條規(guī)則所覆蓋,而且只被一條路徑或一條規(guī)則所覆蓋。這里所覆蓋是指實例的特征與路徑上的特征一致或?qū)嵗凉M足規(guī)則的條件。
使用決策樹做預(yù)測需要以下過程:
收集數(shù)據(jù):可以使用任何方法。比如想構(gòu)建一個相親系統(tǒng),我們可以從媒婆那里,或者通過采訪相親對象獲取數(shù)據(jù)。根據(jù)他們考慮的因素和最終的選擇結(jié)果,就可以得到一些供我們利用的數(shù)據(jù)了。
準(zhǔn)備數(shù)據(jù):收集完的數(shù)據(jù),我們要進(jìn)行整理,將這些所有收集的信息按照一定規(guī)則整理出來,并排版,方便我們進(jìn)行后續(xù)處理。
分析數(shù)據(jù):可以使用任何方法,決策樹構(gòu)造完成之后,我們可以檢查決策樹圖形是否符合預(yù)期。
訓(xùn)練算法:這個過程也就是構(gòu)造決策樹,同樣也可以說是決策樹學(xué)習(xí),就是構(gòu)造一個決策樹的數(shù)據(jù)結(jié)構(gòu)。
測試算法:使用經(jīng)驗樹計算錯誤率。當(dāng)錯誤率達(dá)到了可接收范圍,這個決策樹就可以投放使用了。
使用算法:此步驟可以使用適用于任何監(jiān)督學(xué)習(xí)算法,而使用決策樹可以更好地理解數(shù)據(jù)的內(nèi)在含義。
2、決策樹的構(gòu)建的準(zhǔn)備工作
使用決策樹做預(yù)測的每一步驟都很重要,數(shù)據(jù)收集不到位,將會導(dǎo)致沒有足夠的特征讓我們構(gòu)建錯誤率低的決策樹。數(shù)據(jù)特征充足,但是不知道用哪些特征好,將會導(dǎo)致無法構(gòu)建出分類效果好的決策樹模型。從算法方面看,決策樹的構(gòu)建是我們的核心內(nèi)容。
決策樹要如何構(gòu)建呢?通常,這一過程可以概括為3個步驟:特征選擇、決策樹的生成和決策樹的修剪。
特征選擇
特征選擇在于選取對訓(xùn)練數(shù)據(jù)具有分類能力的特征。這樣可以提高決策樹學(xué)習(xí)的效率,如果利用一個特征進(jìn)行分類的結(jié)果與隨機(jī)分類的結(jié)果沒有很大差別,則稱這個特征是沒有分類能力的。經(jīng)驗上扔掉這樣的特征對決策樹學(xué)習(xí)的精度影響不大。通常特征選擇的標(biāo)準(zhǔn)是信息增益(information gain)或信息增益比,為了簡單,本文使用信息增益作為選擇特征的標(biāo)準(zhǔn)。那么,什么是信息增益?在講解信息增益之前,讓我們看一組實例,貸款申請樣本數(shù)據(jù)表。
希望通過所給的訓(xùn)練數(shù)據(jù)學(xué)習(xí)一個貸款申請的決策樹,用于對未來的貸款申請進(jìn)行分類,即當(dāng)新的客戶提出貸款申請時,根據(jù)申請人的特征利用決策樹決定是否批準(zhǔn)貸款申請。
特征選擇就是決定用哪個特征來劃分特征空間。比如,我們通過上述數(shù)據(jù)表得到兩個可能的決策樹,分別由兩個不同特征的根結(jié)點構(gòu)成。
圖(a)所示的根結(jié)點的特征是年齡,有3個取值,對應(yīng)于不同的取值有不同的子結(jié)點。圖(b)所示的根節(jié)點的特征是工作,有2個取值,對應(yīng)于不同的取值有不同的子結(jié)點。兩個決策樹都可以從此延續(xù)下去。問題是:究竟選擇哪個特征更好些?這就要求確定選擇特征的準(zhǔn)則。直觀上,如果一個特征具有更好的分類能力,或者說,按照這一特征將訓(xùn)練數(shù)據(jù)集分割成子集,使得各個子集在當(dāng)前條件下有最好的分類,那么就更應(yīng)該選擇這個特征。信息增益就能夠很好地表示這一直觀的準(zhǔn)則。
什么是信息增益呢?在劃分?jǐn)?shù)據(jù)集之后信息發(fā)生的變化稱為信息增益,知道如何計算信息增益,我們就可以計算每個特征值劃分?jǐn)?shù)據(jù)集獲得的信息增益,獲得信息增益最高的特征就是最好的選擇。
(1)香農(nóng)熵
在可以評測哪個數(shù)據(jù)劃分方式是最好的數(shù)據(jù)劃分之前,我們必須學(xué)習(xí)如何計算信息增益。集合信息的度量方式成為香農(nóng)熵或者簡稱為熵(entropy),這個名字來源于信息論之父克勞德·香農(nóng)。
如果看不明白什么是信息增益和熵,請不要著急,因為他們自誕生的那一天起,就注定會令世人十分費解??藙诘隆は戕r(nóng)寫完信息論之后,約翰·馮·諾依曼建議使用”熵”這個術(shù)語,因為大家都不知道它是什么意思。
熵定義為信息的期望值。在信息論與概率統(tǒng)計中,熵是表示隨機(jī)變量不確定性的度量。如果待分類的事物可能劃分在多個分類之中,則符號xi的信息定義為 :
其中p(xi)是選擇該分類的概率。有人可能會問,信息為啥這樣定義???答曰:前輩得出的結(jié)論。這就跟1+1等于2一樣,記住并且會用即可。上述式中的對數(shù)以2為底,也可以e為底(自然對數(shù))。
通過上式,我們可以得到所有類別的信息。為了計算熵,我們需要計算所有類別所有可能值包含的信息期望值(數(shù)學(xué)期望),通過下面的公式得到:
其中n是分類的數(shù)目。熵越大,隨機(jī)變量的不確定性就越大。
當(dāng)熵中的概率由數(shù)據(jù)估計(特別是最大似然估計)得到時,所對應(yīng)的熵稱為經(jīng)驗熵(empirical entropy)。什么叫由數(shù)據(jù)估計?比如有10個數(shù)據(jù),一共有兩個類別,A類和B類。其中有7個數(shù)據(jù)屬于A類,則該A類的概率即為十分之七。其中有3個數(shù)據(jù)屬于B類,則該B類的概率即為十分之三。淺顯的解釋就是,這概率是我們根據(jù)數(shù)據(jù)數(shù)出來的。我們定義貸款申請樣本數(shù)據(jù)表中的數(shù)據(jù)為訓(xùn)練數(shù)據(jù)集D,則訓(xùn)練數(shù)據(jù)集D的經(jīng)驗熵為H(D),|D|表示其樣本容量,及樣本個數(shù)。設(shè)有K個類Ck, = 1,2,3,…,K,|Ck|為屬于類Ck的樣本個數(shù),因此經(jīng)驗熵公式就可以寫為 :
根據(jù)此公式計算經(jīng)驗熵H(D),分析貸款申請樣本數(shù)據(jù)表中的數(shù)據(jù)。最終分類結(jié)果只有兩類,即放貸和不放貸。根據(jù)表中的數(shù)據(jù)統(tǒng)計可知,在15個數(shù)據(jù)中,9個數(shù)據(jù)的結(jié)果為放貸,6個數(shù)據(jù)的結(jié)果為不放貸。所以數(shù)據(jù)集D的經(jīng)驗熵H(D)為:
經(jīng)過計算可知,數(shù)據(jù)集D的經(jīng)驗熵H(D)的值為0.971。
(2)編寫代碼計算經(jīng)驗熵
在編寫代碼之前,我們先對數(shù)據(jù)集進(jìn)行屬性標(biāo)注。
年齡:0代表青年,1代表中年,2代表老年;
有工作:0代表否,1代表是;
有自己的房子:0代表否,1代表是;
信貸情況:0代表一般,1代表好,2代表非常好;
類別(是否給貸款):no代表否,yes代表是。
確定這些之后,我們就可以創(chuàng)建數(shù)據(jù)集,并計算經(jīng)驗熵了,代碼編寫如下:
代碼運行結(jié)果如下圖所示,代碼是先打印訓(xùn)練數(shù)據(jù)集,然后打印計算的經(jīng)驗熵H(D),程序計算的結(jié)果與我們統(tǒng)計計算的結(jié)果是一致的,程序沒有問題。
(3) 信息增益
在上面,我們已經(jīng)說過,如何選擇特征,需要看信息增益。也就是說,信息增益是相對于特征而言的,信息增益越大,特征對最終的分類結(jié)果影響也就越大,我們就應(yīng)該選擇對最終分類結(jié)果影響最大的那個特征作為我們的分類特征。
在講解信息增益定義之前,我們還需要明確一個概念,條件熵。
熵我們知道是什么,條件熵又是個什么鬼?條件熵H(Y|X)表示在已知隨機(jī)變量X的條件下隨機(jī)變量Y的不確定性,隨機(jī)變量X給定的條件下隨機(jī)變量Y的條件熵(conditional entropy)H(Y|X),定義為X給定條件下Y的條件概率分布的熵對X的數(shù)學(xué)期望:
這里,
同理,當(dāng)條件熵中的概率由數(shù)據(jù)估計(特別是極大似然估計)得到時,所對應(yīng)的條件熵成為條件經(jīng)驗熵(empirical conditional entropy)。
明確了條件熵和經(jīng)驗條件熵的概念。接下來,讓我們說說信息增益。前面也提到了,信息增益是相對于特征而言的。所以,特征A對訓(xùn)練數(shù)據(jù)集D的信息增益g(D,A),定義為集合D的經(jīng)驗熵H(D)與特征A給定條件下D的經(jīng)驗條件熵H(D|A)之差,即:
一般地,熵H(D)與條件熵H(D|A)之差成為互信息(mutual information)。決策樹學(xué)習(xí)中的信息增益等價于訓(xùn)練數(shù)據(jù)集中類與特征的互信息。
設(shè)特征A有n個不同的取值{a1,a2,···,an},根據(jù)特征A的取值將D劃分為n個子集{D1,D2,···,Dn},|Di|為Di的樣本個數(shù)。記子集Di中屬于Ck的樣本的集合為Dik,即Dik = Di ∩ Ck,|Dik|為Dik的樣本個數(shù)。于是經(jīng)驗條件熵的公式可以些為:
說了這么多概念性的東西,沒有聽懂也沒有關(guān)系,舉幾個例子,再回來看一下概念,就懂了。
以貸款申請樣本數(shù)據(jù)表為例進(jìn)行說明??聪履挲g這一列的數(shù)據(jù),也就是特征A1,一共有三個類別,分別是:青年、中年和老年。我們只看年齡是青年的數(shù)據(jù),年齡是青年的數(shù)據(jù)一共有5個,所以年齡是青年的數(shù)據(jù)在訓(xùn)練數(shù)據(jù)集出現(xiàn)的概率是十五分之五,也就是三分之一。同理,年齡是中年和老年的數(shù)據(jù)在訓(xùn)練數(shù)據(jù)集出現(xiàn)的概率也都是三分之一。現(xiàn)在我們只看年齡是青年的數(shù)據(jù)的最終得到貸款的概率為五分之二,因為在五個數(shù)據(jù)中,只有兩個數(shù)據(jù)顯示拿到了最終的貸款,同理,年齡是中年和老年的數(shù)據(jù)最終得到貸款的概率分別為五分之三、五分之四。所以計算年齡的信息增益,過程如下:
同理,計算其余特征的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分別為:
最后,比較特征的信息增益,由于特征A3(有自己的房子)的信息增益值最大,所以選擇A3作為最優(yōu)特征。
(4) 編寫代碼計算信息增益
我們已經(jīng)學(xué)會了通過公式計算信息增益,接下來編寫代碼,計算信息增益。
#-*-coding:UTF-8-*-frommathimportlog"""函數(shù)說明:計算給定數(shù)據(jù)集的經(jīng)驗熵(香農(nóng)熵)Parameters:dataSet-數(shù)據(jù)集Returns:shannonEnt-經(jīng)驗熵(香農(nóng)熵)"""defcalcShannonEnt(dataSet):numEntires=len(dataSet)#返回數(shù)據(jù)集的行數(shù)labelCounts={}#保存每個標(biāo)簽(Label)出現(xiàn)次數(shù)的字典forfeatVecindataSet:#對每組特征向量進(jìn)行統(tǒng)計currentLabel=featVec[-1]#提取標(biāo)簽(Label)信息ifcurrentLabelnotinlabelCounts.keys():#如果標(biāo)簽(Label)沒有放入統(tǒng)計次數(shù)的字典,添加進(jìn)去labelCounts[currentLabel]=0labelCounts[currentLabel]+=1#Label計數(shù)shannonEnt=0.0#經(jīng)驗熵(香農(nóng)熵)forkeyinlabelCounts:#計算香農(nóng)熵prob=float(labelCounts[key])/numEntires#選擇該標(biāo)簽(Label)的概率shannonEnt-=prob*log(prob,2)#利用公式計算returnshannonEnt#返回經(jīng)驗熵(香農(nóng)熵)"""函數(shù)說明:創(chuàng)建測試數(shù)據(jù)集Parameters:無Returns:dataSet-數(shù)據(jù)集labels-特征標(biāo)簽"""defcreateDataSet():dataSet=[[0,0,0,0,'no'],#數(shù)據(jù)集[0,0,0,1,'no'],[0,1,0,1,'yes'],[0,1,1,0,'yes'],[0,0,0,0,'no'],[1,0,0,0,'no'],[1,0,0,1,'no'],[1,1,1,1,'yes'],[1,0,1,2,'yes'],[1,0,1,2,'yes'],[2,0,1,2,'yes'],[2,0,1,1,'yes'],[2,1,0,1,'yes'],[2,1,0,2,'yes'],[2,0,0,0,'no']]labels=['年齡','有工作','有自己的房子','信貸情況']#特征標(biāo)簽returndataSet,labels#返回數(shù)據(jù)集和分類屬性"""函數(shù)說明:選擇最優(yōu)特征Parameters:dataSet-數(shù)據(jù)集Returns:bestFeature-信息增益最大的(最優(yōu))特征的索引值"""defchooseBestFeatureToSplit(dataSet):numFeatures=len(dataSet[0])-1#特征數(shù)量baseEntropy=calcShannonEnt(dataSet)#計算數(shù)據(jù)集的香農(nóng)熵bestInfoGain=0.0#信息增益bestFeature=-1#最優(yōu)特征的索引值foriinrange(numFeatures):#遍歷所有特征#獲取dataSet的第i個所有特征featList=[example[i]forexampleindataSet]uniqueVals=set(featList)#創(chuàng)建set集合{},元素不可重復(fù)newEntropy=0.0#經(jīng)驗條件熵forvalueinuniqueVals:#計算信息增益subDataSet=splitDataSet(dataSet,i,value)#subDataSet劃分后的子集prob=len(subDataSet)/float(len(dataSet))#計算子集的概率newEntropy+=prob*calcShannonEnt(subDataSet)#根據(jù)公式計算經(jīng)驗條件熵infoGain=baseEntropy-newEntropy#信息增益print("第%d個特征的增益為%.3f"%(i,infoGain))#打印每個特征的信息增益if(infoGain>bestInfoGain):#計算信息增益bestInfoGain=infoGain#更新信息增益,找到最大的信息增益bestFeature=i#記錄信息增益最大的特征的索引值returnbestFeature#返回信息增益最大的特征的索引值if__name__=='__main__':dataSet,features=createDataSet()print("最優(yōu)特征索引值:"+str(chooseBestFeatureToSplit(dataSet)))
splitDataSet函數(shù)是用來選擇各個特征的子集的,比如選擇年齡(第0個特征)的青年(用0代表)的自己,我們可以調(diào)用splitDataSet(dataSet,0,0)這樣返回的子集就是年齡為青年的5個數(shù)據(jù)集。chooseBestFeatureToSplit是選擇選擇最優(yōu)特征的函數(shù)。運行代碼結(jié)果如下:
對比我們自己計算的結(jié)果,發(fā)現(xiàn)結(jié)果完全正確!最優(yōu)特征的索引值為2,也就是特征A3(有自己的房子)。
決策樹生成和修剪
我們已經(jīng)學(xué)習(xí)了從數(shù)據(jù)集構(gòu)造決策樹算法所需要的子功能模塊,包括經(jīng)驗熵的計算和最優(yōu)特征的選擇,其工作原理如下:得到原始數(shù)據(jù)集,然后基于最好的屬性值劃分?jǐn)?shù)據(jù)集,由于特征值可能多于兩個,因此可能存在大于兩個分支的數(shù)據(jù)集劃分。第一次劃分之后,數(shù)據(jù)集被向下傳遞到樹的分支的下一個結(jié)點。在這個結(jié)點上,我們可以再次劃分?jǐn)?shù)據(jù)。因此我們可以采用遞歸的原則處理數(shù)據(jù)集。
構(gòu)建決策樹的算法有很多,比如C4.5、ID3和CART,這些算法在運行時并不總是在每次劃分?jǐn)?shù)據(jù)分組時都會消耗特征。由于特征數(shù)目并不是每次劃分?jǐn)?shù)據(jù)分組時都減少,因此這些算法在實際使用時可能引起一定的問題。目前我們并不需要考慮這個問題,只需要在算法開始運行前計算列的數(shù)目,查看算法是否使用了所有屬性即可。
決策樹生成算法遞歸地產(chǎn)生決策樹,直到不能繼續(xù)下去未為止。這樣產(chǎn)生的樹往往對訓(xùn)練數(shù)據(jù)的分類很準(zhǔn)確,但對未知的測試數(shù)據(jù)的分類卻沒有那么準(zhǔn)確,即出現(xiàn)過擬合現(xiàn)象。過擬合的原因在于學(xué)習(xí)時過多地考慮如何提高對訓(xùn)練數(shù)據(jù)的正確分類,從而構(gòu)建出過于復(fù)雜的決策樹。解決這個問題的辦法是考慮決策樹的復(fù)雜度,對已生成的決策樹進(jìn)行簡化。
三、決策樹構(gòu)建
上篇文章也粗略提到過,構(gòu)建決策樹的算法有很多。篇幅原因,本篇文章只使用ID3算法構(gòu)建決策樹。
1、ID3算法
ID3算法的核心是在決策樹各個結(jié)點上對應(yīng)信息增益準(zhǔn)則選擇特征,遞歸地構(gòu)建決策樹。具體方法是:從根結(jié)點(root node)開始,對結(jié)點計算所有可能的特征的信息增益,選擇信息增益最大的特征作為結(jié)點的特征,由該特征的不同取值建立子節(jié)點;再對子結(jié)點遞歸地調(diào)用以上方法,構(gòu)建決策樹;直到所有特征的信息增益均很小或沒有特征可以選擇為止。最后得到一個決策樹。ID3相當(dāng)于用極大似然法進(jìn)行概率模型的選擇。
在使用ID3構(gòu)造決策樹之前,我們再分析下數(shù)據(jù)。
利用上篇文章求得的結(jié)果,由于特征A3(有自己的房子)的信息增益值最大,所以選擇特征A3作為根結(jié)點的特征。它將訓(xùn)練集D劃分為兩個子集D1(A3取值為”是”)和D2(A3取值為”否”)。由于D1只有同一類的樣本點,所以它成為一個葉結(jié)點,結(jié)點的類標(biāo)記為“是”。
對D2則需要從特征A1(年齡),A2(有工作)和A4(信貸情況)中選擇新的特征,計算各個特征的信息增益:
根據(jù)計算,選擇信息增益最大的特征A2(有工作)作為結(jié)點的特征。由于A2有兩個可能取值,從這一結(jié)點引出兩個子結(jié)點:一個對應(yīng)”是”(有工作)的子結(jié)點,包含3個樣本,它們屬于同一類,所以這是一個葉結(jié)點,類標(biāo)記為”是”;另一個是對應(yīng)”否”(無工作)的子結(jié)點,包含6個樣本,它們也屬于同一類,所以這也是一個葉結(jié)點,類標(biāo)記為”否”。
這樣就生成了一個決策樹,該決策樹只用了兩個特征(有兩個內(nèi)部結(jié)點),生成的決策樹如下圖所示。
這樣我們就使用ID3算法構(gòu)建出來了決策樹,接下來,讓我們看看如何進(jìn)行代實現(xiàn)。
2、編寫代碼構(gòu)建決策樹
我們使用字典存儲決策樹的結(jié)構(gòu),比如上小節(jié)我們分析出來的決策樹,用字典可以表示為:
{'有自己的房子':{0:{'有工作':{0:'no',1:'yes'}},1:'yes'}}
創(chuàng)建函數(shù)majorityCnt統(tǒng)計classList中出現(xiàn)此處最多的元素(類標(biāo)簽),創(chuàng)建函數(shù)createTree用來遞歸構(gòu)建決策樹。編寫代碼如下:
#-*-coding:UTF-8-*-frommathimportlogimportoperator"""函數(shù)說明:計算給定數(shù)據(jù)集的經(jīng)驗熵(香農(nóng)熵)Parameters:dataSet-數(shù)據(jù)集Returns:shannonEnt-經(jīng)驗熵(香農(nóng)熵)"""defcalcShannonEnt(dataSet):numEntires=len(dataSet)#返回數(shù)據(jù)集的行數(shù)labelCounts={}#保存每個標(biāo)簽(Label)出現(xiàn)次數(shù)的字典forfeatVecindataSet:#對每組特征向量進(jìn)行統(tǒng)計currentLabel=featVec[-1]#提取標(biāo)簽(Label)信息ifcurrentLabelnotinlabelCounts.keys():#如果標(biāo)簽(Label)沒有放入統(tǒng)計次數(shù)的字典,添加進(jìn)去labelCounts[currentLabel]=0labelCounts[currentLabel]+=1#Label計數(shù)shannonEnt=0.0#經(jīng)驗熵(香農(nóng)熵)forkeyinlabelCounts:#計算香農(nóng)熵prob=float(labelCounts[key])/numEntires#選擇該標(biāo)簽(Label)的概率shannonEnt-=prob*log(prob,2)#利用公式計算returnshannonEnt#返回經(jīng)驗熵(香農(nóng)熵)"""函數(shù)說明:創(chuàng)建測試數(shù)據(jù)集Parameters:無Returns:dataSet-數(shù)據(jù)集labels-特征標(biāo)簽"""defcreateDataSet():dataSet=[[0,0,0,0,'no'],#數(shù)據(jù)集[0,0,0,1,'no'],[0,1,0,1,'yes'],[0,1,1,0,'yes'],[0,0,0,0,'no'],[1,0,0,0,'no'],[1,0,0,1,'no'],[1,1,1,1,'yes'],[1,0,1,2,'yes'],[1,0,1,2,'yes'],[2,0,1,2,'yes'],[2,0,1,1,'yes'],[2,1,0,1,'yes'],[2,1,0,2,'yes'],[2,0,0,0,'no']]labels=['年齡','有工作','有自己的房子','信貸情況']#特征標(biāo)簽returndataSet,labels#返回數(shù)據(jù)集和分類屬性"""函數(shù)說明:按照給定特征劃分?jǐn)?shù)據(jù)集Parameters:dataSet-待劃分的數(shù)據(jù)集axis-劃分?jǐn)?shù)據(jù)集的特征value-需要返回的特征的值Returns:無"""defsplitDataSet(dataSet,axis,value):retDataSet=[]#創(chuàng)建返回的數(shù)據(jù)集列表forfeatVecindataSet:#遍歷數(shù)據(jù)集iffeatVec[axis]==value:reducedFeatVec=featVec[:axis]#去掉axis特征reducedFeatVec.extend(featVec[axis+1:])#將符合條件的添加到返回的數(shù)據(jù)集retDataSet.append(reducedFeatVec)returnretDataSet#返回劃分后的數(shù)據(jù)集"""函數(shù)說明:選擇最優(yōu)特征Parameters:dataSet-數(shù)據(jù)集Returns:bestFeature-信息增益最大的(最優(yōu))特征的索引值"""defchooseBestFeatureToSplit(dataSet):numFeatures=len(dataSet[0])-1#特征數(shù)量baseEntropy=calcShannonEnt(dataSet)#計算數(shù)據(jù)集的香農(nóng)熵bestInfoGain=0.0#信息增益bestFeature=-1#最優(yōu)特征的索引值foriinrange(numFeatures):#遍歷所有特征#獲取dataSet的第i個所有特征featList=[example[i]forexampleindataSet]uniqueVals=set(featList)#創(chuàng)建set集合{},元素不可重復(fù)newEntropy=0.0#經(jīng)驗條件熵forvalueinuniqueVals:#計算信息增益subDataSet=splitDataSet(dataSet,i,value)#subDataSet劃分后的子集prob=len(subDataSet)/float(len(dataSet))#計算子集的概率newEntropy+=prob*calcShannonEnt(subDataSet)#根據(jù)公式計算經(jīng)驗條件熵infoGain=baseEntropy-newEntropy#信息增益#print("第%d個特征的增益為%.3f"%(i,infoGain))#打印每個特征的信息增益if(infoGain>bestInfoGain):#計算信息增益bestInfoGain=infoGain#更新信息增益,找到最大的信息增益bestFeature=i#記錄信息增益最大的特征的索引值returnbestFeature#返回信息增益最大的特征的索引值"""函數(shù)說明:統(tǒng)計classList中出現(xiàn)此處最多的元素(類標(biāo)簽)Parameters:classList-類標(biāo)簽列表Returns:sortedClassCount[0][0]-出現(xiàn)此處最多的元素(類標(biāo)簽)"""defmajorityCnt(classList):classCount={}forvoteinclassList:#統(tǒng)計classList中每個元素出現(xiàn)的次數(shù)ifvotenotinclassCount.keys():classCount[vote]=0classCount[vote]+=1sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)#根據(jù)字典的值降序排序returnsortedClassCount[0][0]#返回classList中出現(xiàn)次數(shù)最多的元素"""函數(shù)說明:創(chuàng)建決策樹Parameters:dataSet-訓(xùn)練數(shù)據(jù)集labels-分類屬性標(biāo)簽featLabels-存儲選擇的最優(yōu)特征標(biāo)簽Returns:myTree-決策樹"""defcreateTree(dataSet,labels,featLabels):classList=[example[-1]forexampleindataSet]#取分類標(biāo)簽(是否放貸:yesorno)ifclassList.count(classList[0])==len(classList):#如果類別完全相同則停止繼續(xù)劃分returnclassList[0]iflen(dataSet[0])==1:#遍歷完所有特征時返回出現(xiàn)次數(shù)最多的類標(biāo)簽returnmajorityCnt(classList)bestFeat=chooseBestFeatureToSplit(dataSet)#選擇最優(yōu)特征bestFeatLabel=labels[bestFeat]#最優(yōu)特征的標(biāo)簽featLabels.append(bestFeatLabel)myTree={bestFeatLabel:{}}#根據(jù)最優(yōu)特征的標(biāo)簽生成樹del(labels[bestFeat])#刪除已經(jīng)使用特征標(biāo)簽featValues=[example[bestFeat]forexampleindataSet]#得到訓(xùn)練集中所有最優(yōu)特征的屬性值uniqueVals=set(featValues)#去掉重復(fù)的屬性值forvalueinuniqueVals:#遍歷特征,創(chuàng)建決策樹。myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),labels,featLabels)returnmyTreeif__name__=='__main__':dataSet,labels=createDataSet()featLabels=[]myTree=createTree(dataSet,labels,featLabels)print(myTree)
遞歸創(chuàng)建決策樹時,遞歸有兩個終止條件:第一個停止條件是所有的類標(biāo)簽完全相同,則直接返回該類標(biāo)簽;第二個停止條件是使用完了所有特征,仍然不能將數(shù)據(jù)劃分僅包含唯一類別的分組,即決策樹構(gòu)建失敗,特征不夠用。此時說明數(shù)據(jù)緯度不夠,由于第二個停止條件無法簡單地返回唯一的類標(biāo)簽,這里挑選出現(xiàn)數(shù)量最多的類別作為返回值。
運行上述代碼,我們可以看到如下結(jié)果:
可見,我們的決策樹已經(jīng)構(gòu)建完成了。這時候,有的朋友可能會說,這個決策樹看著好別扭,雖然這個能看懂,但是如果多點的結(jié)點,就不好看了。能直觀點嗎?完全沒有問題,我們可以使用強(qiáng)大的Matplotlib繪制決策樹。
3、決策樹可視化
這里代碼都是關(guān)于Matplotlib的,如果對于Matplotlib不了解的,可以先學(xué)習(xí)下,Matplotlib的內(nèi)容這里就不再累述。可視化需要用到的函數(shù):
getNumLeafs:獲取決策樹葉子結(jié)點的數(shù)目
getTreeDepth:獲取決策樹的層數(shù)
plotNode:繪制結(jié)點
plotMidText:標(biāo)注有向邊屬性值
plotTree:繪制決策樹
createPlot:創(chuàng)建繪制面板
對可視化決策樹的程序進(jìn)行了詳細(xì)的注釋,直接看代碼,調(diào)試查看即可。為了顯示中文,需要設(shè)置FontProperties,代碼編寫如下:
(點擊圖片查看大圖)
不出意外的話,我們就可以得到如下結(jié)果,可以看到?jīng)Q策樹繪制完成。plotNode函數(shù)的工作就是繪制各個結(jié)點,比如有自己的房子、有工作、yes、no,包括內(nèi)結(jié)點和葉子結(jié)點。plotMidText函數(shù)的工作就是繪制各個有向邊的屬性。
4、使用決策樹執(zhí)行分類
依靠訓(xùn)練數(shù)據(jù)構(gòu)造了決策樹之后,我們可以將它用于實際數(shù)據(jù)的分類。在執(zhí)行數(shù)據(jù)分類時,需要決策樹以及用于構(gòu)造樹的標(biāo)簽向量。然后,程序比較測試數(shù)據(jù)與決策樹上的數(shù)值,遞歸執(zhí)行該過程直到進(jìn)入葉子結(jié)點;最后將測試數(shù)據(jù)定義為葉子結(jié)點所屬的類型。在構(gòu)建決策樹的代碼,可以看到,有個featLabels參數(shù)。它是用來干什么的?它就是用來記錄各個分類結(jié)點的,在用決策樹做預(yù)測的時候,我們按順序輸入需要的分類結(jié)點的屬性值即可。舉個例子,比如我用上述已經(jīng)訓(xùn)練好的決策樹做分類,那么我只需要提供這個人是否有房子,是否有工作這兩個信息即可,無需提供冗余的信息。
用決策樹做分類的代碼很簡單,編寫代碼如下:
(點擊圖片,查看大圖)
這里只增加了classify函數(shù),用于決策樹分類。輸入測試數(shù)據(jù)[0,1],它代表沒有房子,但是有工作,分類結(jié)果如下所示:
看到這里,細(xì)心的朋友可能就會問了,每次做預(yù)測都要訓(xùn)練一次決策樹?這也太麻煩了吧?有什么好的解決嗎?
5、決策樹的存儲
構(gòu)造決策樹是很耗時的任務(wù),即使處理很小的數(shù)據(jù)集,如前面的樣本數(shù)據(jù),也要花費幾秒的時間,如果數(shù)據(jù)集很大,將會耗費很多計算時間。然而用創(chuàng)建好的決策樹解決分類問題,則可以很快完成。因此,為了節(jié)省計算時間,最好能夠在每次執(zhí)行分類時調(diào)用已經(jīng)構(gòu)造好的決策樹。為了解決這個問題,需要使用Python模塊pickle序列化對象。序列化對象可以在磁盤上保存對象,并在需要的時候讀取出來。
假設(shè)我們已經(jīng)得到?jīng)Q策樹
{'有自己的房子':{0:{'有工作':{0:'no',1:'yes'}},1:'yes'}}
使用pickle.dump存儲決策樹。
#-*-coding:UTF-8-*-importpickle"""函數(shù)說明:存儲決策樹Parameters:inputTree-已經(jīng)生成的決策樹filename-決策樹的存儲文件名Returns:無"""defstoreTree(inputTree,filename):withopen(filename,'wb')asfw:pickle.dump(inputTree,fw)if__name__=='__main__':myTree={'有自己的房子':{0:{'有工作':{0:'no',1:'yes'}},1:'yes'}}storeTree(myTree,'classifierStorage.txt')
運行代碼,在該Python文件的相同目錄下,會生成一個名為classifierStorage.txt的txt文件,這個文件二進(jìn)制存儲著我們的決策樹。我們可以使用VScode打開看下存儲結(jié)果。
看不懂?沒錯,因為這個是個二進(jìn)制存儲的文件,我們也無需看懂里面的內(nèi)容,會存儲,會用即可。那么問題來了。將決策樹存儲完這個二進(jìn)制文件,然后下次使用的話,怎么用呢?
很簡單使用pickle.load進(jìn)行載入即可,編寫代碼如下:
#-*-coding:UTF-8-*-importpickle"""函數(shù)說明:讀取決策樹Parameters:filename-決策樹的存儲文件名Returns:pickle.load(fr)-決策樹字典"""defgrabTree(filename):fr=open(filename,'rb')returnpickle.load(fr)if__name__=='__main__':myTree=grabTree('classifierStorage.txt')print(myTree)
如果在該Python文件的相同目錄下,有一個名為classifierStorage.txt的文件,那么我們就可以運行上述代碼,運行結(jié)果如下圖所示:
從上述結(jié)果中,我們可以看到,我們順利加載了存儲決策樹的二進(jìn)制文件。
四、Sklearn之使用決策樹預(yù)測隱形眼睛類型
1、實戰(zhàn)背景
進(jìn)入本文的正題:眼科醫(yī)生是如何判斷患者需要佩戴隱形眼鏡的類型的?一旦理解了決策樹的工作原理,我們甚至也可以幫助人們判斷需要佩戴的鏡片類型。
隱形眼鏡數(shù)據(jù)集是非常著名的數(shù)據(jù)集,它包含很多換著眼部狀態(tài)的觀察條件以及醫(yī)生推薦的隱形眼鏡類型。隱形眼鏡類型包括硬材質(zhì)(hard)、軟材質(zhì)(soft)以及不適合佩戴隱形眼鏡(no lenses)。數(shù)據(jù)來源與UCI數(shù)據(jù)庫,數(shù)據(jù)集下載地址:點擊進(jìn)入鏈接
一共有24組數(shù)據(jù),數(shù)據(jù)的Labels依次是age、prescript、astigmatic、tearRate、class,也就是第一列是年齡,第二列是癥狀,第三列是是否散光,第四列是眼淚數(shù)量,第五列是最終的分類標(biāo)簽。數(shù)據(jù)如下圖所示:
可以使用已經(jīng)寫好的Python程序構(gòu)建決策樹,不過出于繼續(xù)學(xué)習(xí)的目的,本文使用Sklearn實現(xiàn)。
2、使用Sklearn構(gòu)建決策樹
官方英文文檔地址:
http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html
sklearn.tree模塊提供了決策樹模型,用于解決分類問題和回歸問題。方法如下圖所示:
本次實戰(zhàn)內(nèi)容使用的是DecisionTreeClassifier和export_graphviz,前者用于決策樹構(gòu)建,后者用于決策樹可視化。
DecisionTreeClassifier構(gòu)建決策樹:
讓我們先看下DecisionTreeClassifier這個函數(shù),一共有12個參數(shù):
參數(shù)說明如下:
criterion:特征選擇標(biāo)準(zhǔn),可選參數(shù),默認(rèn)是gini,可以設(shè)置為entropy。gini是基尼不純度,是將來自集合的某種結(jié)果隨機(jī)應(yīng)用于某一數(shù)據(jù)項的預(yù)期誤差率,是一種基于統(tǒng)計的思想。entropy是香農(nóng)熵,也就是上篇文章講過的內(nèi)容,是一種基于信息論的思想。Sklearn把gini設(shè)為默認(rèn)參數(shù),應(yīng)該也是做了相應(yīng)的斟酌的,精度也許更高些?ID3算法使用的是entropy,CART算法使用的則是gini。
splitter:特征劃分點選擇標(biāo)準(zhǔn),可選參數(shù),默認(rèn)是best,可以設(shè)置為random。每個結(jié)點的選擇策略。best參數(shù)是根據(jù)算法選擇最佳的切分特征,例如gini、entropy。random隨機(jī)的在部分劃分點中找局部最優(yōu)的劃分點。默認(rèn)的”best”適合樣本量不大的時候,而如果樣本數(shù)據(jù)量非常大,此時決策樹構(gòu)建推薦”random”。
max_features:劃分時考慮的最大特征數(shù),可選參數(shù),默認(rèn)是None。尋找最佳切分時考慮的最大特征數(shù)(n_features為總共的特征數(shù)),有如下6種情況:
如果max_features是整型的數(shù),則考慮max_features個特征;
如果max_features是浮點型的數(shù),則考慮int(max_features * n_features)個特征;
如果max_features設(shè)為auto,那么max_features = sqrt(n_features);
如果max_features設(shè)為sqrt,那么max_featrues = sqrt(n_features),跟auto一樣;
如果max_features設(shè)為log2,那么max_features = log2(n_features);
如果max_features設(shè)為None,那么max_features = n_features,也就是所有特征都用。
一般來說,如果樣本特征數(shù)不多,比如小于50,我們用默認(rèn)的”None”就可以了,如果特征數(shù)非常多,我們可以靈活使用剛才描述的其他取值來控制劃分時考慮的最大特征數(shù),以控制決策樹的生成時間。
max_depth:決策樹最大深,可選參數(shù),默認(rèn)是None。這個參數(shù)是這是樹的層數(shù)的。層數(shù)的概念就是,比如在貸款的例子中,決策樹的層數(shù)是2層。如果這個參數(shù)設(shè)置為None,那么決策樹在建立子樹的時候不會限制子樹的深度。一般來說,數(shù)據(jù)少或者特征少的時候可以不管這個值?;蛘呷绻O(shè)置了min_samples_slipt參數(shù),那么直到少于- - - min_smaples_split個樣本為止。如果模型樣本量多,特征也多的情況下,推薦限制這個最大深度,具體的取值取決于數(shù)據(jù)的分布。常用的可以取值10-100之間。
min_samples_split:內(nèi)部節(jié)點再劃分所需最小樣本數(shù),可選參數(shù),默認(rèn)是2。這個值限制了子樹繼續(xù)劃分的條件。如果min_samples_split為整數(shù),那么在切分內(nèi)部結(jié)點的時候,min_samples_split作為最小的樣本數(shù),也就是說,如果樣本已經(jīng)少于min_samples_split個樣本,則停止繼續(xù)切分。如果min_samples_split為浮點數(shù),那么min_samples_split就是一個百分比,ceil(min_samples_split * n_samples),數(shù)是向上取整的。如果樣本量不大,不需要管這個值。如果樣本量數(shù)量級非常大,則推薦增大這個值。
min_samples_leaf:葉子節(jié)點最少樣本數(shù),可選參數(shù),默認(rèn)是1。這個值限制了葉子節(jié)點最少的樣本數(shù),如果某葉子節(jié)點數(shù)目小于樣本數(shù),則會和兄弟節(jié)點一起被剪枝。葉結(jié)點需要最少的樣本數(shù),也就是最后到葉結(jié)點,需要多少個樣本才能算一個葉結(jié)點。如果設(shè)置為1,哪怕這個類別只有1個樣本,決策樹也會構(gòu)建出來。如果min_samples_leaf是整數(shù),那么min_samples_leaf作為最小的樣本數(shù)。如果是浮點數(shù),那么min_samples_leaf就是一個百分比,同上,celi(min_samples_leaf * n_samples),數(shù)是向上取整的。如果樣本量不大,不需要管這個值。如果樣本量數(shù)量級非常大,則推薦增大這個值。
min_weight_fraction_leaf:葉子節(jié)點最小的樣本權(quán)重和,可選參數(shù),默認(rèn)是0。這個值限制了葉子節(jié)點所有樣本權(quán)重和的最小值,如果小于這個值,則會和兄弟節(jié)點一起被剪枝。一般來說,如果我們有較多樣本有缺失值,或者分類樹樣本的分布類別偏差很大,就會引入樣本權(quán)重,這時我們就要注意這個值了。
max_leaf_nodes:最大葉子節(jié)點數(shù),可選參數(shù),默認(rèn)是None。通過限制最大葉子節(jié)點數(shù),可以防止過擬合。如果加了限制,算法會建立在最大葉子節(jié)點數(shù)內(nèi)最優(yōu)的決策樹。如果特征不多,可以不考慮這個值,但是如果特征分成多的話,可以加以限制,具體的值可以通過交叉驗證得到。
class_weight:類別權(quán)重,可選參數(shù),默認(rèn)是None,也可以字典、字典列表、balanced。指定樣本各類別的的權(quán)重,主要是為了防止訓(xùn)練集某些類別的樣本過多,導(dǎo)致訓(xùn)練的決策樹過于偏向這些類別。類別的權(quán)重可以通過{class_label:weight}這樣的格式給出,這里可以自己指定各個樣本的權(quán)重,或者用balanced,如果使用balanced,則算法會自己計算權(quán)重,樣本量少的類別所對應(yīng)的樣本權(quán)重會高。當(dāng)然,如果你的樣本類別分布沒有明顯的偏倚,則可以不管這個參數(shù),選擇默認(rèn)的None。
random_state:可選參數(shù),默認(rèn)是None。隨機(jī)數(shù)種子。如果是證書,那么random_state會作為隨機(jī)數(shù)生成器的隨機(jī)數(shù)種子。隨機(jī)數(shù)種子,如果沒有設(shè)置隨機(jī)數(shù),隨機(jī)出來的數(shù)與當(dāng)前系統(tǒng)時間有關(guān),每個時刻都是不同的。如果設(shè)置了隨機(jī)數(shù)種子,那么相同隨機(jī)數(shù)種子,不同時刻產(chǎn)生的隨機(jī)數(shù)也是相同的。如果是RandomState instance,那么random_state是隨機(jī)數(shù)生成器。如果為None,則隨機(jī)數(shù)生成器使用np.random。
min_impurity_split:節(jié)點劃分最小不純度,可選參數(shù),默認(rèn)是1e-7。這是個閾值,這個值限制了決策樹的增長,如果某節(jié)點的不純度(基尼系數(shù),信息增益,均方差,絕對差)小于這個閾值,則該節(jié)點不再生成子節(jié)點。即為葉子節(jié)點 。
presort:數(shù)據(jù)是否預(yù)排序,可選參數(shù),默認(rèn)為False,這個值是布爾值,默認(rèn)是False不排序。一般來說,如果樣本量少或者限制了一個深度很小的決策樹,設(shè)置為true可以讓劃分點選擇更加快,決策樹建立的更加快。如果樣本量太大的話,反而沒有什么好處。問題是樣本量少的時候,我速度本來就不慢。所以這個值一般懶得理它就可以了。
除了這些參數(shù)要注意以外,其他在調(diào)參時的注意點有:
當(dāng)樣本數(shù)量少但是樣本特征非常多的時候,決策樹很容易過擬合,一般來說,樣本數(shù)比特征數(shù)多一些會比較容易建立健壯的模型
如果樣本數(shù)量少但是樣本特征非常多,在擬合決策樹模型前,推薦先做維度規(guī)約,比如主成分分析(PCA),特征選擇(Losso)或者獨立成分分析(ICA)。這樣特征的維度會大大減小。再來擬合決策樹模型效果會好。
推薦多用決策樹的可視化,同時先限制決策樹的深度,這樣可以先觀察下生成的決策樹里數(shù)據(jù)的初步擬合情況,然后再決定是否要增加深度。
在訓(xùn)練模型時,注意觀察樣本的類別情況(主要指分類樹),如果類別分布非常不均勻,就要考慮用class_weight來限制模型過于偏向樣本多的類別。
決策樹的數(shù)組使用的是numpy的float32類型,如果訓(xùn)練數(shù)據(jù)不是這樣的格式,算法會先做copy再運行。
如果輸入的樣本矩陣是稀疏的,推薦在擬合前調(diào)用csc_matrix稀疏化,在預(yù)測前調(diào)用csr_matrix稀疏化。
sklearn.tree.DecisionTreeClassifier()提供了一些方法供我們使用,如下圖所示:
了解到這些,我們就可以編寫代碼了。
注意一點,由于fit()函數(shù)不能接收string類型的數(shù)據(jù),通過打印的信息可以看到,數(shù)據(jù)都是string類型的。在使用fit()函數(shù)之前,我們需要對數(shù)據(jù)集進(jìn)行編碼,這里可以使用兩種方法:
LabelEncoder :將字符串轉(zhuǎn)換為增量值
OneHotEncoder:使用One-of-K算法將字符串轉(zhuǎn)換為整數(shù)
為了對string類型的數(shù)據(jù)序列化,需要先生成pandas數(shù)據(jù),這樣方便我們的序列化工作。這里我使用的方法是,原始數(shù)據(jù)->字典->pandas數(shù)據(jù),編寫代碼如下:
importpandasaspdif__name__=='__main__':withopen('lenses.txt','r')asfr:#加載文件lenses=[inst.strip().split(' ')forinstinfr.readlines()]#處理文件lenses_target=[]#提取每組數(shù)據(jù)的類別,保存在列表里foreachinlenses:lenses_target.append(each[-1])lensesLabels=['age','prescript','astigmatic','tearRate']#特征標(biāo)簽lenses_list=[]#保存lenses數(shù)據(jù)的臨時列表lenses_dict={}#保存lenses數(shù)據(jù)的字典,用于生成pandasforeach_labelinlensesLabels:#提取信息,生成字典foreachinlenses:lenses_list.append(each[lensesLabels.index(each_label)])lenses_dict[each_label]=lenses_listlenses_list=[]print(lenses_dict)#打印字典信息lenses_pd=pd.DataFrame(lenses_dict)#生成pandas.DataFrameprint(lenses_pd)
從運行結(jié)果可以看出,順利生成pandas數(shù)據(jù)。
接下來,將數(shù)據(jù)序列化,編寫代碼如下:
#-*-coding:UTF-8-*-importpandasaspdfromsklearn.preprocessingimportLabelEncoderimportpydotplusfromsklearn.externals.siximportStringIOif__name__=='__main__':withopen('lenses.txt','r')asfr:#加載文件lenses=[inst.strip().split(' ')forinstinfr.readlines()]#處理文件lenses_target=[]#提取每組數(shù)據(jù)的類別,保存在列表里foreachinlenses:lenses_target.append(each[-1])lensesLabels=['age','prescript','astigmatic','tearRate']#特征標(biāo)簽lenses_list=[]#保存lenses數(shù)據(jù)的臨時列表lenses_dict={}#保存lenses數(shù)據(jù)的字典,用于生成pandasforeach_labelinlensesLabels:#提取信息,生成字典foreachinlenses:lenses_list.append(each[lensesLabels.index(each_label)])lenses_dict[each_label]=lenses_listlenses_list=[]#print(lenses_dict)#打印字典信息lenses_pd=pd.DataFrame(lenses_dict)#生成pandas.DataFrameprint(lenses_pd)#打印pandas.DataFramele=LabelEncoder()#創(chuàng)建LabelEncoder()對象,用于序列化forcolinlenses_pd.columns:#為每一列序列化lenses_pd[col]=le.fit_transform(lenses_pd[col])print(lenses_pd)
從打印結(jié)果可以看到,我們已經(jīng)將數(shù)據(jù)順利序列化,接下來。我們就可以fit()數(shù)據(jù),構(gòu)建決策樹了。
3、使用Graphviz可視化決策樹
Graphviz的是AT&T Labs Research開發(fā)的圖形繪制工具,他可以很方便的用來繪制結(jié)構(gòu)化的圖形網(wǎng)絡(luò),支持多種格式輸出,生成圖片的質(zhì)量和速度都不錯。它的輸入是一個用dot語言編寫的繪圖腳本,通過對輸入腳本的解析,分析出其中的點,邊以及子圖,然后根據(jù)屬性進(jìn)行繪制。是使用Sklearn生成的決策樹就是dot格式的,因此我們可以直接利用Graphviz將決策樹可視化。
在講解編寫代碼之前,我們需要安裝兩樣?xùn)|西,即pydotplus和Grphviz。
(1)安裝Pydotplus
pydotplus可以在CMD窗口中,直接使用指令安裝:
pipinstallpydotplus
(2)安裝Graphviz
Graphviz不能使用pip進(jìn)行安裝,我們需要手動安裝,下載地址:http://www.graphviz.org/Home.php
下載好安裝包,進(jìn)行安裝,安裝完畢之后,需要設(shè)置Graphviz的環(huán)境變量。
首先,按快捷鍵win+r,在出現(xiàn)的運行對話框中輸入sysdm.cpl,點擊確定,出現(xiàn)如下對話框:
選擇高級->環(huán)境變量。在系統(tǒng)變量的Path變量中,添加Graphviz的環(huán)境變量,比如Graphviz安裝在了D盤的根目錄,則添加:D:Graphvizin;
添加好環(huán)境變量之后,我們就可以正常使用Graphviz了。
(3)編寫代碼
代碼如下,可視化部分的代碼不難,都是有套路的,直接填參數(shù)就好,詳細(xì)內(nèi)容可以查看官方教程:http://scikit-learn.org/stable/modules/tree.html#tree
運行代碼,在該python文件保存的相同目錄下,會生成一個名為tree的PDF文件,打開文件,我們就可以看到?jīng)Q策樹的可視化效果圖了。
確定好決策樹之后,我們就可以做預(yù)測了??梢愿鶕?jù)自己的眼睛情況和年齡等特征,看一看自己適合何種材質(zhì)的隱形眼鏡。使用如下代碼就可以看到預(yù)測結(jié)果:
print(clf.predict([[1,1,1,0]]))#預(yù)測
代碼簡單,官方手冊都有,就不全貼出來了。
五、總結(jié)
決策樹的一些優(yōu)點:
易于理解和解釋。決策樹可以可視化。
幾乎不需要數(shù)據(jù)預(yù)處理。其他方法經(jīng)常需要數(shù)據(jù)標(biāo)準(zhǔn)化,創(chuàng)建虛擬變量和刪除缺失值。決策樹還不支持缺失值。
使用樹的花費(例如預(yù)測數(shù)據(jù))是訓(xùn)練數(shù)據(jù)點(data points)數(shù)量的對數(shù)。
可以同時處理數(shù)值變量和分類變量。其他方法大都適用于分析一種變量的集合。
可以處理多值輸出變量問題。
使用白盒模型。如果一個情況被觀察到,使用邏輯判斷容易表示這種規(guī)則。相反,如果是黑盒模型(例如人工神經(jīng)網(wǎng)絡(luò)),結(jié)果會非常難解釋。
即使對真實模型來說,假設(shè)無效的情況下,也可以較好的適用。
決策樹的一些缺點:
決策樹學(xué)習(xí)可能創(chuàng)建一個過于復(fù)雜的樹,并不能很好的預(yù)測數(shù)據(jù)。也就是過擬合。修剪機(jī)制(現(xiàn)在不支持),設(shè)置一個葉子節(jié)點需要的最小樣本數(shù)量,或者數(shù)的最大深度,可以避免過擬合。
決策樹可能是不穩(wěn)定的,因為即使非常小的變異,可能會產(chǎn)生一顆完全不同的樹。這個問題通過decision trees with an ensemble來緩解。
概念難以學(xué)習(xí),因為決策樹沒有很好的解釋他們,例如,XOR, parity or multiplexer problems。
如果某些分類占優(yōu)勢,決策樹將會創(chuàng)建一棵有偏差的樹。因此,建議在訓(xùn)練之前,先抽樣使樣本均衡。
-
機(jī)器學(xué)習(xí)
+關(guān)注
關(guān)注
66文章
8349瀏覽量
132312 -
決策樹
+關(guān)注
關(guān)注
2文章
96瀏覽量
13534
原文標(biāo)題:機(jī)器學(xué)習(xí)決策樹算法實戰(zhàn)——理論+詳細(xì)的Python3代碼實現(xiàn)
文章出處:【微信號:rgznai100,微信公眾號:rgznai100】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論