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