【機器學習筆記之二】決策樹的python實現


本文結構:

  1. 是什么?
  2. 有什么算法?
  3. 數學原理?
  4. 編碼實現算法?

1. 是什么?

簡單地理解,就是根據一些 feature 進行分類,每個節點提一個問題,通過判斷,將數據分為幾類,再繼續提問。這些問題是根據已有數據學習出來的,再投入新數據的時候,就可以根據這棵樹上的問題,將數據划分到合適的葉子上。



2. 有什么算法?

常用的幾種決策樹算法有ID3、C4.5、CART:

ID3:選擇信息熵增益最大的feature作為node,實現對數據的歸納分類。
C4.5:是ID3的一個改進,比ID3准確率高且快,可以處理連續值和有缺失值的feature。
CART:使用基尼指數的划分准則,通過在每個步驟最大限度降低不純潔度,CART能夠處理孤立點以及能夠對空缺值進行處理。


3. 數學原理?

ID3: Iterative Dichotomiser 3

參考


下面這個數據集,可以同時被上面兩顆樹表示,結果是一樣的,而我們更傾向於選擇簡單的樹。
那么怎樣做才能使得學習到的樹是最簡單的呢?


下面是 ID3( Iterative Dichotomiser 3 )的算法:


例如下面數據集,哪個是最好的 Attribute?


用熵Entropy來衡量:
E(S) 是數據集S的熵
i 指每個結果,即 No,Yes的概率


E越大意味着信息越混亂,我們的目標是要讓E最小。
E在0-1之間,如果P+的概率在0.5, 此時E最大,這時候說明信息對我們沒有明確的意義,對分類沒有幫助。


但是我們不僅僅想要變量的E最小,還想要這棵樹是 well organized。
所以用到 Gain:信息增益


意思是如果我后面要用這個變量的話,它的E會減少多少。


例如下面的數據集:


  1. 先計算四個feature的熵E,及其分支的熵,然后用Gain的公式計算信息增益。


  2. 再選擇Gain最大的特征是 outlook。

  3. 第一層選擇出來后,各個分支再繼續選擇下一層,計算Gain最大的,例如分支 sunny 的下一層節點是 humidity。


詳細的計算步驟可以參考這篇博文。


C4.5

參考

ID3有個局限是對於有大量數據的feature過於敏感,C4.5是它的一個改進,通過選擇最大的信息增益率 gain ratio 來選擇節點。而且它可以處理連續的和有缺失值的數據。


P’ (j/p) is the proportion of elements present at the position p, taking the value of j-th test.

例如 outlook 作為第一層節點后,它有 3 個分支,分別有 5,4,5 條數據,則 SplitInfo(5,4,5) = -5/14log(5,14)-4/14log(4,14)-5/14(5,14) ,其中 log(5,14) 即為 log2(5/14)。

下面是一個有連續值和缺失值的例子:


連續值

第一步計算 Gain,除了連續值的 humudity,其他步驟和前文一樣。

要計算 humudity 的 Gain 的話,先把所有值升序排列:
{65, 70, 70, 70, 75, 78, 80, 80, 80, 85, 90, 90, 95, 96}
然后把重復的去掉:
{65, 70, 75, 78, 80, 85, 90, 95, 96}
如下圖所示,按區間計算 Gain,然后選擇最大的 Gain (S, Humidity) = 0.102


因為 Gain(S, Outlook) = 0 .246,所以root還是outlook:


缺失值

處理有缺失值的數據時候,用下圖的公式:


例如 D12 是不知道的。

  1. 計算全集和 outlook 的 info,


  2. 其中幾個分支的熵如下,再計算出 outlook 的 Gain:


比較一下 ID3 和 C4.5 的准確率和時間:

accuracy :


execution time:



4. 編碼實現算法?

代碼可以看《機器學習實戰》這本書和這篇博客。

完整代碼可以在 github 上查看。

接下來以 C4.5 的代碼為例:

1. 定義數據:

 1 def createDataSet():
 2     dataSet = [[0, 0, 0, 0, 'N'], 
 3                [0, 0, 0, 1, 'N'], 
 4                [1, 0, 0, 0, 'Y'], 
 5                [2, 1, 0, 0, 'Y'], 
 6                [2, 2, 1, 0, 'Y'], 
 7                [2, 2, 1, 1, 'N'], 
 8                [1, 2, 1, 1, 'Y']]
 9     labels = ['outlook', 'temperature', 'humidity', 'windy']
10     return dataSet, labels

2. 計算熵:

 1 def calcShannonEnt(dataSet):
 2     numEntries = len(dataSet)
 3     labelCounts = {}
 4     for featVec in dataSet:
 5         currentLabel = featVec[-1]
 6         if currentLabel not in labelCounts.keys():
 7             labelCounts[currentLabel] = 0
 8         labelCounts[currentLabel] += 1      # 數每一類各多少個, {'Y': 4, 'N': 3}
 9     shannonEnt = 0.0
10     for key in labelCounts:
11         prob = float(labelCounts[key])/numEntries
12         shannonEnt -= prob * log(prob, 2)
13     return shannonEnt

3. 選擇最大的gain ratio對應的feature:

 1 def chooseBestFeatureToSplit(dataSet):
 2     numFeatures = len(dataSet[0]) - 1                 #feature個數
 3     baseEntropy = calcShannonEnt(dataSet)             #整個dataset的熵
 4     bestInfoGainRatio = 0.0
 5     bestFeature = -1
 6     for i in range(numFeatures):
 7         featList = [example[i] for example in dataSet]  #每個feature的list
 8         uniqueVals = set(featList)                      #每個list的唯一值集合                 
 9         newEntropy = 0.0
10         splitInfo = 0.0
11         for value in uniqueVals:
12             subDataSet = splitDataSet(dataSet, i, value)  #每個唯一值對應的剩余feature的組成子集
13             prob = len(subDataSet)/float(len(dataSet))
14             newEntropy += prob * calcShannonEnt(subDataSet)
15             splitInfo += -prob * log(prob, 2)
16         infoGain = baseEntropy - newEntropy              #這個feature的infoGain
17         if (splitInfo == 0): # fix the overflow bug
18             continue
19         infoGainRatio = infoGain / splitInfo             #這個feature的infoGainRatio      
20         if (infoGainRatio > bestInfoGainRatio):          #選擇最大的gain ratio
21             bestInfoGainRatio = infoGainRatio
22             bestFeature = i                              #選擇最大的gain ratio對應的feature
23     return bestFeature

4. 划分數據,為下一層計算准備:

1 def splitDataSet(dataSet, axis, value):
2     retDataSet = []
3     for featVec in dataSet:
4         if featVec[axis] == value:                      #只看當第i列的值=value時的item
5             reduceFeatVec = featVec[:axis]              #featVec的第i列給除去
6             reduceFeatVec.extend(featVec[axis+1:])
7             retDataSet.append(reduceFeatVec)            
8     return retDataSet

5. 多重字典構建樹:

 1 def createTree(dataSet, labels):
 2     classList = [example[-1] for example in dataSet]         # ['N', 'N', 'Y', 'Y', 'Y', 'N', 'Y']
 3     if classList.count(classList[0]) == len(classList):
 4         # classList所有元素都相等,即類別完全相同,停止划分
 5         return classList[0]                                  #splitDataSet(dataSet, 0, 0)此時全是N,返回N
 6     if len(dataSet[0]) == 1:                                 #[0, 0, 0, 0, 'N'] 
 7         # 遍歷完所有特征時返回出現次數最多的
 8         return majorityCnt(classList)
 9     bestFeat = chooseBestFeatureToSplit(dataSet)             #0-> 2   
10         # 選擇最大的gain ratio對應的feature
11     bestFeatLabel = labels[bestFeat]                         #outlook -> windy     
12     myTree = {bestFeatLabel:{}}                   
13         #多重字典構建樹{'outlook': {0: 'N'
14     del(labels[bestFeat])                                    #['temperature', 'humidity', 'windy'] -> ['temperature', 'humidity']        
15     featValues = [example[bestFeat] for example in dataSet]  #[0, 0, 1, 2, 2, 2, 1]     
16     uniqueVals = set(featValues)
17     for value in uniqueVals:
18         subLabels = labels[:]                                #['temperature', 'humidity', 'windy']
19         myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels)
20             # 划分數據,為下一層計算准備
21     return myTree

 

6. 可視化決策樹的結果:

dataSet, labels = createDataSet()
labels_tmp = labels[:]
desicionTree = createTree(dataSet, labels_tmp)
treePlotter.createPlot(desicionTree)

 

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM