決策樹分類算法

1、決策樹原理

決策樹是用樣本的屬性做爲結點,用屬性的取值做爲分支的樹結構。 
決策樹的根結點是全部樣本中信息量最大的屬性。樹的中間結點是該結點爲根的子樹所包含的樣本子集中信息量最大的屬性。決策樹的葉結點是樣本的類別值。決策樹是一種知識表示形式,它是對全部樣本數據的高度歸納決策樹能準確地識別全部樣本的類別,也能有效地識別新樣本的類別。
python

 

決策樹算法ID3的基本思想:算法

首先找出最有判別力的屬性,把樣例分紅多個子集,每一個子集又選擇最有判別力的屬性進行劃分,一直進行到全部子集僅包含同一類型的數據爲止。最後獲得一棵決策樹。app

J.R.Quinlan的工做主要是引進了信息論中的信息增益,他將其稱爲信息增益(information gain),做爲屬性判別能力的度量,設計了構造決策樹的遞歸算法。機器學習

 

舉例子比較容易理解:函數

對於氣候分類問題,屬性爲:學習

天氣(A1) 取值爲: 晴,多雲,雨測試

氣溫(A2) 取值爲: 冷 ,適中,熱ui

溼度(A3) 取值爲: 高 ,正常spa

風 (A4) 取值爲: 有風, 無風設計

 

每一個樣例屬於不一樣的類別,此例僅有兩個類別,分別爲P,N。P類和N類的樣例分別稱爲正例和反例。將一些已知的正例和反例放在一塊兒便獲得訓練集。

由ID3算法得出一棵正確分類訓練集中每一個樣例的決策樹,見下圖。

決策樹葉子爲類別名,即P 或者N。其它結點由樣例的屬性組成,每一個屬性的不一樣取值對應一分枝。

若要對同樣例分類,從樹根開始進行測試,按屬性的取值分枝向下進入下層結點,對該結點進行測試,過程一直進行到葉結點,樣例被判爲屬於該葉結點所標記的類別。

現用圖來判一個具體例子,

某天早晨氣候描述爲:

天氣:多雲

氣溫:冷

溼度:正常

風: 無風

它屬於哪類氣候呢?-------------從圖中可判別該樣例的類別爲P類。

 

ID3就是要從表的訓練集構造圖這樣的決策樹。實際上,能正確分類訓練集的決策樹不止一棵。Quinlan的ID3算法能得出結點最少的決策樹。

ID3算法:

⒈ 對當前例子集合,計算各屬性的信息增益;

⒉ 選擇信息增益最大的屬性Ak

⒊ 把在Ak處取值相同的例子歸於同一子集,Ak取幾個值就得幾個子集;

⒋ 對既含正例又含反例的子集,遞歸調用建樹算法;

⒌ 若子集僅含正例或反例,對應分枝標上P或N,返回調用處。

通常只要涉及到樹的狀況,常常會要用到遞歸。

 

對於氣候分類問題進行具體計算有:

信息熵的計算: 其中S是樣例的集合, P(ui)是類別i出現機率:

|S|表示例子集S的總數,|ui|表示類別ui的例子數。對9個正例和5個反例有:

P(u1)=9/14

P(u2)=5/14

H(S)=(9/14)log(14/9)+(5/14)log(14/5)=0.94bit

 

信息增益的計算:

其中A是屬性,Value(A)是屬性A取值的集合,v是A的某一屬性值,Sv是S中A的值爲v的樣例集合,| Sv |爲Sv中所含樣例數。

以屬性A1爲例,根據信息增益的計算公式,屬性A1的信息增益爲

S=[9+,5-] //原樣例集中共有14個樣例,9個正例,5個反例

S晴=[2+,3-]//屬性A1取值晴的樣例共5個,2正,3反

S多雲=[4+,0-] //屬性A1取值多雲的樣例共4個,4正,0反

S雨=[3+,2-] //屬性A1取值晴的樣例共5個,3正,2反

 

3.結果爲

屬性A1的信息增益最大,因此被選爲根結點。

4.建決策樹的根和葉子

ID3算法將選擇信息增益最大的屬性天氣做爲樹根,在14個例子中對天氣的3個取值進行分枝,3 個分枝對應3 個子集,分別是:

其中S2中的例子全屬於P類,所以對應分枝標記爲P,其他兩個子集既含有正例又含有反例,將遞歸調用建樹算法。

5.遞歸建樹

分別對S1和S3子集遞歸調用ID3算法,在每一個子集中對各屬性求信息增益.

(1)對S1,溼度屬性信息增益最大,以它爲該分枝的根結點,再向下分枝。溼度取高的例子全爲N類,該分枝標記N。取值正常的例子全爲P類,該分枝標記P。

(2)對S3,風屬性信息增益最大,則以它爲該分枝根結點。再向下分枝,風取有風時全爲N類,該分枝標記N。取無風時全爲P類,該分枝標記P。

 

2、PYTHON實現決策樹算法分類

本代碼爲machine learning in action 第三章例子,親測無誤。

一、計算給定數據shangnon數據的函數:

 

 

[python] view plaincopy

  1. def calcShannonEnt(dataSet):

  2. #calculate the shannon value

  3. numEntries = len(dataSet)

  4. labelCounts = {}

  5. for featVec in dataSet: #create the dictionary for all of the data

  6. currentLabel = featVec[-1]

  7. if currentLabel not in labelCounts.keys():

  8. labelCounts[currentLabel] = 0

  9. labelCounts[currentLabel] += 1

  10. shannonEnt = 0.0

  11. for key in labelCounts:

  12. prob = float(labelCounts[key])/numEntries

  13. shannonEnt -= prob*log(prob,2) #get the log value

  14. return shannonEnt


2. 建立數據的函數

 

 

[python] view plaincopy

  1. def createDataSet():

  2. dataSet = [[1,1,'yes'],

  3. [1,1, 'yes'],

  4. [1,0,'no'],

  5. [0,1,'no'],

  6. [0,1,'no']]

  7. labels = ['no surfacing','flippers']

  8. return dataSet, labels


3.劃分數據集,按照給定的特徵劃分數據集

 

 

[python] view plaincopy

  1. def splitDataSet(dataSet, axis, value):

  2. retDataSet = []

  3. for featVec in dataSet:

  4. if featVec[axis] == value: #abstract the fature

  5. reducedFeatVec = featVec[:axis]

  6. reducedFeatVec.extend(featVec[axis+1:])

  7. retDataSet.append(reducedFeatVec)

  8. return retDataSet

 

 

4.選擇最好的數據集劃分方式

 

[python] view plaincopy

  1. def chooseBestFeatureToSplit(dataSet):

  2. numFeatures = len(dataSet[0])-1

  3. baseEntropy = calcShannonEnt(dataSet)

  4. bestInfoGain = 0.0; bestFeature = -1

  5. for i in range(numFeatures):

  6. featList = [example[i] for example in dataSet]

  7. uniqueVals = set(featList)

  8. newEntropy = 0.0

  9. for value in uniqueVals:

  10. subDataSet = splitDataSet(dataSet, i , value)

  11. prob = len(subDataSet)/float(len(dataSet))

  12. newEntropy +=prob * calcShannonEnt(subDataSet)

  13. infoGain = baseEntropy - newEntropy

  14. if(infoGain > bestInfoGain):

  15. bestInfoGain = infoGain

  16. bestFeature = i

  17. return bestFeature


5.遞歸建立樹

 

用於找出出現次數最多的分類名稱的函數

 

[python] view plaincopy

  1. def majorityCnt(classList):

  2. classCount = {}

  3. for vote in classList:

  4. if vote not in classCount.keys(): classCount[vote] = 0

  5. classCount[vote] += 1

  6. sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)

  7. return sortedClassCount[0][0]


用於建立樹的函數代碼

 

[python] view plaincopy

  1. def createTree(dataSet, labels):

  2. classList = [example[-1] for example in dataSet]

  3. # the type is the same, so stop classify

  4. if classList.count(classList[0]) == len(classList):

  5. return classList[0]

  6. # traversal all the features and choose the most frequent feature

  7. if (len(dataSet[0]) == 1):

  8. return majorityCnt(classList)

  9. bestFeat = chooseBestFeatureToSplit(dataSet)

  10. bestFeatLabel = labels[bestFeat]

  11. myTree = {bestFeatLabel:{}}

  12. del(labels[bestFeat])

  13. #get the list which attain the whole properties

  14. featValues = [example[bestFeat] for example in dataSet]

  15. uniqueVals = set(featValues)

  16. for value in uniqueVals:

  17. subLabels = labels[:]

  18. myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)

  19. return myTree


而後是在python 名利提示符號輸入以下命令:

 

 

[python] view plaincopy

  1. myDat, labels = trees.createDataSet()

  2. myTree = trees.createTree(myDat,labels)

  3. print myTree


結果是:

 

{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}


6.實用決策樹進行分類的函數

 

[python] view plaincop

  1. def classify(inputTree, featLabels, testVec):

  2. firstStr = inputTree.keys()[0]

  3. secondDict = inputTree[firstStr]

  4. featIndex = featLabels.index(firstStr)

  5. for key in secondDict.keys():

  6. if testVec[featIndex] == key:

  7. if type(secondDict[key]).__name__ == 'dict':

  8. classLabel = classify(secondDict[key], featLabels, testVec)

  9. else: classLabel = secondDict[key]

  10. return classLabel


在Python命令提示符,輸入:

 

[python] view plaincopy

  1. trees.classify(myTree,labels,[1,0])


獲得結果:

'no'

Congratulation. Oh yeah. You did it.!!!

 

 

如下爲補充學習筆記

一、決策樹算法

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

 

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

 

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

 

請看下面這個例子。

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

 

[plain] view plaincopy

  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.

 

所以,每次劃分以前,咱們只須要計算出信息熵增益最大的那種劃分便可。

 

二、數據集

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

 

[plain] view plaincopy

  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的機器學習庫,實現起來至關簡單和優雅。

 

三、Python實現

Python代碼實現以下:

 

[python] view plaincopy

  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點須要說明,這在之後的機器學習中都會用到。

 

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

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

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

也就是說10個樣本中隨機取8個訓練。本文數據集小,這裏的目的是能夠看到因爲取的訓練數據隨機,每次構建的決策樹都不同。

 

二、特徵的不一樣影響因子。

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

在本例中,身高的權重爲0.25,體重爲0.75,能夠看到重量的重要性遠遠高於身高。對於胖瘦的斷定而言,這也是至關符合邏輯的。

 

三、準確率與召回率

這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 plaincopy

  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. }

相關文章
相關標籤/搜索