用Python開始機器學習(2:決策樹分類算法)

從這一章開始進入正式的算法學習。

首先我們學習經典而有效的分類算法:決策樹分類算法

1、決策樹算法

決策樹用樹形結構對樣本的屬性進行分類,是最直觀的分類算法,而且也可以用於迴歸。不過對於一些特殊的邏輯分類會有困難。典型的如異或(XOR)邏輯,決策樹並不擅長解決此類問題。

決策樹的構建不是唯一的,遺憾的是最優決策樹的構建屬於NP問題。因此如何構建一棵好的決策樹是研究的重點。

J. Ross Quinlan在1975提出將信息熵的概念引入決策樹的構建,這就是鼎鼎大名的ID3算法。後續的C4.5, C5.0, CART等都是該方法的改進。

熵就是“無序,混亂”的程度。剛接觸這個概念可能會有些迷惑。想快速瞭解如何用信息熵增益劃分屬性,可以參考這位兄弟的文章:http://blog.csdn.net/alvine008/article/details/37760639

如果還不理解,請看下面這個例子。

假設要構建這麼一個自動選好蘋果的決策樹,簡單起見,我只讓他學習下面這4個樣本:

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. 樣本    紅     大      好蘋果  
  2. 0       1      1         1  
  3. 1       1      0         1  
  4. 2       0      1         0  
  5. 3       0      0         0  
樣本中有2個屬性,A0表示是否紅蘋果。A1表示是否大蘋果。

那麼這個樣本在分類前的信息熵就是S = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

信息熵爲1表示當前處於最混亂,最無序的狀態。

本例僅2個屬性。那麼很自然一共就只可能有2棵決策樹,如下圖所示:


顯然左邊先使用A0(紅色)做劃分依據的決策樹要優於右邊用A1(大小)做劃分依據的決策樹。

當然這是直覺的認知。定量的考察,則需要計算每種劃分情況的信息熵增益。

先選A0作劃分,各子節點信息熵計算如下:

0,1葉子節點有2個正例,0個負例。信息熵爲:e1 = -(2/2 * log(2/2) + 0/2 * log(0/2)) = 0。

2,3葉子節點有0個正例,2個負例。信息熵爲:e2 = -(0/2 * log(0/2) + 2/2 * log(2/2)) = 0。

因此選擇A0劃分後的信息熵爲每個子節點的信息熵所佔比重的加權和:E = e1*2/4 + e2*2/4 = 0。

選擇A0做劃分的信息熵增益G(S, A0)=S - E = 1 - 0 = 1.

事實上,決策樹葉子節點表示已經都屬於相同類別,因此信息熵一定爲0。


同樣的,如果先選A1作劃分,各子節點信息熵計算如下:

0,2子節點有1個正例,1個負例。信息熵爲:e1 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

1,3子節點有1個正例,1個負例。信息熵爲:e2 = -(1/2 * log(1/2) + 1/2 * log(1/2)) = 1。

因此選擇A1劃分後的信息熵爲每個子節點的信息熵所佔比重的加權和:E = e1*2/4 + e2*2/4 = 1。也就是說分了跟沒分一樣!

選擇A1做劃分的信息熵增益G(S, A1)=S - E = 1 - 1 = 0.

因此,每次劃分之前,我們只需要計算出信息熵增益最大的那種劃分即可。

2、數據集

爲方便講解與理解,我們使用如下一個極其簡單的測試數據集:

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. 1.5 50 thin  
  2. 1.5 60 fat  
  3. 1.6 40 thin  
  4. 1.6 60 fat  
  5. 1.7 60 thin  
  6. 1.7 80 fat  
  7. 1.8 60 thin  
  8. 1.8 90 fat  
  9. 1.9 70 thin  
  10. 1.9 80 fat  
這個數據一共有10個樣本,每個樣本有2個屬性,分別爲身高和體重,第三列爲類別標籤,表示“胖”或“瘦”。該數據保存在1.txt中。

我們的任務就是訓練一個決策樹分類器,輸入身高和體重,分類器能給出這個人是胖子還是瘦子。

(數據是作者主觀臆斷,具有一定邏輯性,但請無視其合理性)

決策樹對於“是非”的二值邏輯的分枝相當自然。而在本數據集中,身高與體重是連續值怎麼辦呢?

雖然麻煩一點,不過這也不是問題,只需要找到將這些連續值劃分爲不同區間的中間點,就轉換成了二值邏輯問題。

本例決策樹的任務是找到身高、體重中的一些臨界值,按照大於或者小於這些臨界值的邏輯將其樣本兩兩分類,自頂向下構建決策樹。

使用Python機器學習庫,實現起來相當簡單和優雅。


3、Python實現

Python代碼實現如下:

[python] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. # -*- coding: utf-8 -*-  
  2. import numpy as np  
  3. import scipy as sp  
  4. from sklearn import tree  
  5. from sklearn.metrics import precision_recall_curve  
  6. from sklearn.metrics import classification_report  
  7. from sklearn.cross_validation import train_test_split  
  8.   
  9.   
  10. ''''' 數據讀入 '''  
  11. data   = []  
  12. labels = []  
  13. with open("data\\1.txt") as ifile:  
  14.         for line in ifile:  
  15.             tokens = line.strip().split(' ')  
  16.             data.append([float(tk) for tk in tokens[:-1]])  
  17.             labels.append(tokens[-1])  
  18. x = np.array(data)  
  19. labels = np.array(labels)  
  20. y = np.zeros(labels.shape)  
  21.   
  22.   
  23. ''''' 標籤轉換爲0/1 '''  
  24. y[labels=='fat']=1  
  25.   
  26. ''''' 拆分訓練數據與測試數據 '''  
  27. x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.2)  
  28.   
  29. ''''' 使用信息熵作爲劃分標準,對決策樹進行訓練 '''  
  30. clf = tree.DecisionTreeClassifier(criterion='entropy')  
  31. print(clf)  
  32. clf.fit(x_train, y_train)  
  33.   
  34. ''''' 把決策樹結構寫入文件 '''  
  35. with open("tree.dot"'w') as f:  
  36.     f = tree.export_graphviz(clf, out_file=f)  
  37.       
  38. ''''' 係數反映每個特徵的影響力。越大表示該特徵在分類中起到的作用越大 '''  
  39. print(clf.feature_importances_)  
  40.   
  41. '''''測試結果的打印'''  
  42. answer = clf.predict(x_train)  
  43. print(x_train)  
  44. print(answer)  
  45. print(y_train)  
  46. print(np.mean( answer == y_train))  
  47.   
  48. '''''準確率與召回率'''  
  49. precision, recall, thresholds = precision_recall_curve(y_train, clf.predict(x_train))  
  50. answer = clf.predict_proba(x)[:,1]  
  51. print(classification_report(y, answer, target_names = ['thin''fat']))  

輸出結果類似如下所示:

[ 0.2488562  0.7511438]
array([[  1.6,  60. ],
       [  1.7,  60. ],
       [  1.9,  80. ],
       [  1.5,  50. ],
       [  1.6,  40. ],
       [  1.7,  80. ],
       [  1.8,  90. ],
       [  1.5,  60. ]])
array([ 1.,  0.,  1.,  0.,  0.,  1.,  1.,  1.])
array([ 1.,  0.,  1.,  0.,  0.,  1.,  1.,  1.])
1.0

             precision    recall  f1-score   support
       thin       0.83      1.00      0.91         5
        fat        1.00      0.80      0.89         5

avg / total       1.00      1.00      1.00         8

array([ 0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.,  0.,  0.])
array([ 0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.])

可以看到,對訓練過的數據做測試,準確率是100%。但是最後將所有數據進行測試,會出現1個測試樣本分類錯誤。

說明本例的決策樹對訓練集的規則吸收的很好,但是預測性稍微差點。

這裏有3點需要說明,這在以後的機器學習中都會用到。

1、拆分訓練數據與測試數據

這樣做是爲了方便做交叉檢驗。交叉檢驗是爲了充分測試分類器的穩定性。

代碼中的0.2表示隨機取20%的數據作爲測試用。其餘80%用於訓練決策樹。

也就是說10個樣本中隨機取8個訓練。本文數據集小,這裏的目的是可以看到由於取的訓練數據隨機,每次構建的決策樹都不一樣。

2、特徵的不同影響因子。

樣本的不同特徵對分類的影響權重差異會很大。分類結束後看看每個樣本對分類的影響度也是很重要的。

在本例中,身高的權重爲0.25,體重爲0.75,可以看到重量的重要性遠遠高於身高。對於胖瘦的判定而言,這也是相當符合邏輯的。

3、準確率與召回率

這2個值是評判分類準確率的一個重要標準。比如代碼的最後將所有10個樣本輸入分類器進行測試的結果:

測試結果:array([ 0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.,  0.,  0.])
真實結果:array([ 0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.,  0.,  1.])

分爲thin的準確率爲0.83。是因爲分類器分出了6個thin,其中正確的有5個,因此分爲thin的準確率爲5/6=0.83。

分爲thin的召回率爲1.00。是因爲數據集中共有5個thin,而分類器把他們都分對了(雖然把一個fat分成了thin!),召回率5/5=1。

分爲fat的準確率爲1.00。不再贅述。

分爲fat的召回率爲0.80。是因爲數據集中共有5個fat,而分類器只分出了4個(把一個fat分成了thin!),召回率4/5=0.80。

很多時候,尤其是數據分類難度較大的情況,準確率與召回率往往是矛盾的。你可能需要根據你的需要找到最佳的一個平衡點。

比如本例中,你的目標是儘可能保證找出來的胖子是真胖子(準確率),還是保證儘可能找到更多的胖子(召回率)。


代碼還把決策樹的結構寫入了tree.dot中。打開該文件,很容易畫出決策樹,還可以看到決策樹的更多分類信息。

本文的tree.dot如下所示:

[plain] view plain copy
 在CODE上查看代碼片派生到我的代碼片
  1. digraph Tree {  
  2. 0 [label="X[1] <= 55.0000\nentropy = 0.954434002925\nsamples = 8", shape="box"] ;  
  3. 1 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 2.  0.]", shape="box"] ;  
  4. 0 -> 1 ;  
  5. 2 [label="X[1] <= 70.0000\nentropy = 0.650022421648\nsamples = 6", shape="box"] ;  
  6. 0 -> 2 ;  
  7. 3 [label="X[0] <= 1.6500\nentropy = 0.918295834054\nsamples = 3", shape="box"] ;  
  8. 2 -> 3 ;  
  9. 4 [label="entropy = 0.0000\nsamples = 2\nvalue = [ 0.  2.]", shape="box"] ;  
  10. 3 -> 4 ;  
  11. 5 [label="entropy = 0.0000\nsamples = 1\nvalue = [ 1.  0.]", shape="box"] ;  
  12. 3 -> 5 ;  
  13. 6 [label="entropy = 0.0000\nsamples = 3\nvalue = [ 0.  3.]", shape="box"] ;  
  14. 2 -> 6 ;  
  15. }  
根據這個信息,決策樹應該長的如下這個樣子:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章