機器學習算法( 3、決策樹)


  本節使用的算法稱爲ID3,另外一個決策樹構造算法CART之後講解。算法

1、概述 

  咱們常用決策樹處理分類問題,它的過程相似二十個問題的遊戲:參與遊戲的一方在腦海裏想某個事物,其餘參與者向他提出問題,只容許提20個問 題,問題的答案也只能用對或錯回答。問問題的人經過推斷分解,逐步縮小帶猜想事物的範圍。數據結構

  如圖1所示的流程圖就是一個決策樹,長方形表明判斷模塊(decision block),橢圓形表明終止模塊(terminating block),表示已經得出結論,能夠終止運行。從判斷模塊引出的左右箭頭稱做分支(branch),它能夠到達另外一個判斷模塊或終止模塊。app

  圖 1構造了一個假象的郵件分類系統,它首先檢測發送郵件域名地址。若是地址爲myEmployer.com,則將其放在分類"無聊時須要閱讀的郵件"中。如 果郵件不是來自這個域名,則檢查內容是否包括單詞曲棍球,若是包含則將郵件歸類到"須要及時處理的朋友郵件",不然將郵件歸類到"無須閱讀的垃圾郵件"。函數

2、優缺點

  • 優勢:計算複雜度不高,輸出結果易於理解,對中間值的缺失不敏感,能夠處理不相關特徵數據。
  • 缺點:可能會產生過分匹配問題。
  • 適用數據類型:離散型和連續型

3、數學公式

  若是待分類的數據集可能劃分在多個分類之中,則符號Xi定義爲:x學習

  

  其中p(xi)是選擇該分類的機率。測試

  

  其中:n = 數據集分類數。this

  例如:數據集的分類爲 lables=[A,B,C,B,A,B] 則P(A)=2/6=0.3333,P(B)=3/6=0.5,P(C)=1/6=0.277,spa

  數據集的 H=-P(A)*log2P(A)+(-P(B)*log2P(B))+(-P(C)*log2P(C))3d

4、樹的構造

  在構造決策樹時,咱們須要解決的第一個問題就是,當前數據集上哪一個特徵在劃分數據分類時起決定性做用。爲了找到決定性的特徵,劃分出最好的結果,咱們必須評估每一個特徵。咱們假設已經根據必定的方法選取了待劃分的特徵,則原始數據集將根據這個特徵被劃分爲幾個數據子集。這數據子集會分佈在決策點(關鍵 特徵)的全部分支上。若是某個分支下的數據屬於同一類型,則無需進一步對數據集進行分割。若是數據子集內的數據不屬於同一類型,則須要遞歸地重複劃分數據 子集的過程,直到每一個數據子集內的數據類型相同如何劃分子集的算法和劃分原始數據集的方法相同code

建立分支的過程用僞代碼表示以下:

  檢測數據集中的每一個子項是否屬於同一類型:
  If Yes return 類標籤
  Else
    尋找劃分數據集的最好特徵
    劃分數據集
    建立分支節點
      for 每一個劃分的子集:
        遞歸調用本算法並添加返回結果到分支節點中(這是個遞歸)
    return 分支節點

  決策樹的通常流程:

  1. 收集數據:可使用任何方法。
  2. 準備數據:樹構造算法只適用於標稱數據,所以數值型數據必須離散化。
  3. 分析數據:可使用任何方法,構造樹完成以後,咱們應該檢查圖形是否符合預期。
  4. 訓練算法:構造樹的數據結構。
  5. 測試算法:使用經驗樹計算錯誤率。
  6. 使用算法:此步驟能夠適用於任何監督學習算法,而使用決策樹能夠更好地理解數據的內在含義。

  一些決策樹算法使用二分法劃分數據,本書將使用ID3算法劃分數據集,該算法處理如何劃分數據集,什麼時候中止劃分數據集。每次劃分數據集咱們只選取一個特徵屬性,那麼應該選擇哪一個特徵做爲劃分的參考屬性呢?

  表1的數據包含5個海洋動物,特徵包括:不浮出水面是否能夠生存,以及是否有腳噗。咱們能夠將這些動物分紅兩類:魚類和非魚類。

表1 海洋生物數據

  不浮出水面是否能夠生存 是否有腳蹼 屬於魚類
1
2
3
4
5

5、信息增益

  劃分數據集的大原則是:將無序的數據變得更加有序。咱們可使用多種方法劃分數據集,可是每種方法都有各自的優缺點。組織雜亂無章數據的一種方法就是使用信息論度量信息,信息論是量化處理信息的分支科學。咱們能夠在劃分數據以前或以後使用信息論量化度量信息的內容。

  在劃分數據集以前以後信息發生的變化成爲信息增益咱們能夠計算每一個特徵劃分數據集得到的信息增益,得到信息增益最高的特徵就是最好的選擇。

  集合信息的度量方式成爲香農熵或者簡稱爲熵。

  爲了計算熵,咱們須要計算全部類型全部可能值包含的信息的指望值,經過下面的公式獲得:

   其中n是分類的數目。

  下面給出計算信息熵的Python函數,建立名爲trees.py文件,添加以下代碼:

 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     #change to discrete values
 9     return dataSet, labels
10 def calcShannonEnt(dataSet):
11     numEntries = len(dataSet)  #獲取數據行數   numEntries = 5 12     labelCounts = {}
13     for featVec in dataSet: #the the number of unique elements and their occurance
14         currentLabel = featVec[-1]
15         if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
16         labelCounts[currentLabel] += 1
17     shannonEnt = 0.0
18     print("labelCounts=",labelCounts)   # labelCounts= {'yes': 2, 'no': 3} 19     for key in labelCounts:
20         prob = float(labelCounts[key])/numEntries  # prob 爲每一個值出現的機率
21         shannonEnt -= prob * log(prob,2) # 數學公式,計算 
22     print("shannonEnt=",shannonEnt)
23     return shannonEnt

  測試代碼:

1 >>> d,l=trees.createDataSet()
2 >>> d
3 [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
4 >>> l
5 ['no surfacing', 'flippers']
6 >>> c=trees.calcShannonEnt(d)
7 labelCounts= {'yes': 2, 'no': 3}
8 shannonEnt= 0.9709505944546686
9 >>> 

  calcShannonEnt:返回整個數據集的

  上面測試代碼數據集的=0.9709505944546686

   值越高,則混合的數據也越多,獲得 以後,咱們就能夠按最大信息增益的方法劃分數據集。

 

6、劃分數據集

   上面咱們學習瞭如何度量數據集的無序程序,分類算法除了須要測量信息,還須要劃分數據集,度量劃分數據集的,以便判斷當前是否正確地劃分了數據集。

   咱們將對每一個特徵劃分數據集的結果計算一次信息熵,而後判斷按照哪一個特徵劃分數據集市最好的劃分方法。

   按照給定的特徵劃分數據集:

1 def splitDataSet(dataSet, axis, value):#查找數據集 dataSet 第 axis 列值== value 的元素,再排除第 axis 列的數據,組成一個新的數據集
2     retDataSet = []
3     for featVec in dataSet:
4         if featVec[axis] == value:
5             reducedFeatVec = featVec[:axis]     #chop out axis used for splitting
6             reducedFeatVec.extend(featVec[axis+1:])
7             retDataSet.append(reducedFeatVec)
8     return retDataSet

  該函數使用了三個輸入參數:帶劃分的數據集、劃分數據集的特徵(數據集第幾列)、須要返回的特徵的值(按哪一個值劃分)。函數先選取數據集中第axis個特徵值爲value的數據,從這部分數據中去除第axis個特徵,並返回。

測試這個函數,效果以下:

1 >>> myDat, labels = trees.createDataSet()
2 >>> myDat
3 [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
4 >>> trees.splitDataSet(myDat, 0, 1) #查找第0列值==1的元素,再排除第0列的數據,組成一個新的數據集 5 [[1, 'yes'], [1, 'yes'], [0, 'no']]
6 >>> trees.splitDataSet(myDat, 0, 0)
7 [[1, 'no'], [1, 'no']]

  接下來咱們將遍歷整個數據集,循環計算香農熵和splitDataSet()函數,找到最好的特徵劃分方式。熵計算將會告訴咱們如何劃分數據集是最好的數據組織方式。

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

 1 def chooseBestFeatureToSplit(dataSet):
 2     numFeatures = len(dataSet[0]) - 1      # 這裏的dataSet 最後一列是分類 numFeatures=2;咱們按2列數據進行劃分
 3     baseEntropy = calcShannonEnt(dataSet)   # 計算出整個 數據數據集的 熵
 4     bestInfoGain = 0.0; bestFeature = -1
 5     for i in range(numFeatures):        # 循環每一列特徵 
 6         featList = [example[i] for example in dataSet]# 建立一個新的 列表,存放數據集第 i 列的數據
 7         uniqueVals = set(featList)       # 使用集合,把數據去重。。。。。。。。。。
 8         newEntropy = 0.0 #  如下是計算 每一列 的 熵 求某列 最大的 熵
 9         for value in uniqueVals:# 循環 第 i 列 的特徵值 
10             subDataSet = splitDataSet(dataSet, i, value) # 劃分數據集。。。。
11             prob = len(subDataSet)/float(len(dataSet))  # 子數據集所佔的比例。。。。
12             newEntropy += prob * calcShannonEnt(subDataSet)      # 子數據集的  * 比例。。。。
13         infoGain = baseEntropy - newEntropy     #calculate the info gain; ie reduction in entropy
14         if (infoGain > bestInfoGain):       #compare this to the best gain so far
15             bestInfoGain = infoGain         #if better than current best, set to best
16             bestFeature = i
17     return bestFeature                      #返回按某列劃分數據集的最大  
  • 本函數使用變量bestInfoGain和bestFeature記錄最好的信息增益和對應的特徵;
  • numFeatures記錄特徵維數,依次遍歷各個特徵,計算該特徵值的集合(uniqueVals);
  • 遍歷該特徵計算使用該特徵劃分的熵(newEntropy),據此計算新的信息增益(infoGain);
  • 比較infoGain和bestInfoGain記錄信息增益的最大值和對應特徵;
  • 最終返回最大的信息增益對應特徵的索引。

  測試上面代碼的實際輸出結果:

 1 >>> myData, labels = trees.createDataSet()
 2 >>> trees.chooseBestFeatureToSplit(myData)
 3 labelCounts= {'yes': 2, 'no': 3}
 4 shannonEnt= 0.9709505944546686
 5 labelCounts= {'no': 2}
 6 shannonEnt= 0.0
 7 labelCounts= {'yes': 2, 'no': 1}
 8 shannonEnt= 0.9182958340544896
 9 labelCounts= {'no': 1}
10 shannonEnt= 0.0
11 labelCounts= {'yes': 2, 'no': 2}
12 shannonEnt= 1.0
13 0

 

7、遞歸構建決策樹

  構建決策樹的算法流程以下:

  1. 獲得原始數據集,
  2. 基於最好的屬性值劃分數據集,因爲特徵值可能多於兩個,所以可能存在大於兩個分支的數據集劃分。
  3. 第一次劃分以後,數據將被向下傳遞到樹分支的下一個節點,在這個節點上,咱們能夠再次劃分數據。咱們能夠採用遞歸的原則處理數據集。
  4. 遞歸結束的條件是,程序遍歷完全部劃分數據集的屬性,或者每一個分支下的全部實例都具備相同的分類。

   

  添加下面的程序代碼:

 1 def majorityCnt(classList):# 返回 出現次數最多的類別 ,,相似於K-近鄰算法中 返回前K中類別出現最屢次數的。
 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]
 8 
 9 def createTree(dataSet,labels):
10     classList = [example[-1] for example in dataSet]  # 獲取數據集的全部類別
11     if classList.count(classList[0]) == len(classList): 
12         return classList[0]# 若是數據集的全部類別 都相同,則不須要劃分
13     if len(dataSet[0]) == 1: # 使用完了全部特徵,仍然不能將數據劃分 到某個類別上的話,返回出現次數最多的類別
14         return majorityCnt(classList)
15     bestFeat = chooseBestFeatureToSplit(dataSet)   # 獲取數據集中 按哪一列進行劃分。。。。。
16     bestFeatLabel = labels[bestFeat]  # bestFeatLabel = 列 描述
17     myTree = {bestFeatLabel:{}} # 建立一個字典
18     del(labels[bestFeat]) # 刪除已計算過的列
19     featValues = [example[bestFeat] for example in dataSet]
20     uniqueVals = set(featValues) # 獲取某列 全部不重複值
21     for value in uniqueVals:
22         subLabels = labels[:]       #copy all of labels, so trees don't mess up existing labels
23         myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) # 遞歸
24     return myTree                            
25  

  majorityCnt函數統計classList列表中每一個類型標籤出現頻率,返回出現次數最多的分類名稱。

  createTree函數使用兩個輸入參數:數據集dataSet和標籤列表labels。標籤列表包含了數據集中全部特徵的標籤,算法自己並不需 要這個變量,可是爲了給出數據明確的含義,咱們將它做爲一個輸入參數提供。上述代碼首先建立了名爲classList的列表變量,其中包含了數據集的全部 類標籤。列表變量classList包含了數據集的全部類標籤。遞歸函數的第一個中止條件是全部類標籤徹底相同,則直接返回該類標籤。遞歸函數的第二個停 止條件是使用完了全部特徵,仍然不能將數據集劃分紅僅包含惟一類別的分組。這裏使用majorityCnt函數挑選出現次數最多的類別做爲返回值。

  下一步程序開始建立樹,這裏直接使用Python的字典類型存儲樹的信息。字典變量myTree存儲樹的全部信息。當前數據集選取的最好特徵存儲在變量bestFeat中,獲得列表中包含的全部屬性值。

最後代碼遍歷當前選擇特徵包含的全部屬性值,在每一個數據集劃分上遞歸待用函數createTree(),獲得的返回值將被插入到字典變量myTree中,所以函數終止執行時,字典中將會嵌套不少表明葉子節點信息的字典數據。

注意其中的subLabels = labels[:]複製了類標籤,由於在遞歸調用createTree函數中會改變標籤列表的值。

測試這些函數:

 1 >>> myDat, labels = trees.createDataSet()
 2 >>> myTree = trees.createTree(myDat, labels)
 3 >>> myTree
 4 {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}

  變量myTree包含了不少表明樹結構信息的嵌套字典,從左邊開始,第一個關鍵字no surfacing 是第一個劃分數據集特徵的名稱,該關鍵字的值也是一個字典。

8、測試算法:使用決策樹執行分類,以及決策樹的存儲

  依靠訓練數據構造了決策樹以後,咱們能夠將它用於實際數據的分類。在執行數據分類時,須要決策樹以及用於決策樹的標籤向量。而後,程序比較測試數據與決策樹上的數值,遞歸執行該過程直到進入葉子結點;最後將測試數據定義爲葉子結點所屬的類型。

使用決策樹分類的函數:

 1 def classify(inputTree,featLabels,testVec):
 2     firstStr = inputTree.keys()[0]
 3     secondDict = inputTree[firstStr]
 4     featIndex = featLabels.index(firstStr)
 5     key = testVec[featIndex]
 6     valueOfFeat = secondDict[key]
 7     if isinstance(valueOfFeat, dict): 
 8         classLabel = classify(valueOfFeat, featLabels, testVec)
 9     else: classLabel = valueOfFeat
10     return classLabel

測試上面的分類函數:

>>> myDat, labels = trees.createDataSet()
>>> myTree = trees.createTree(myDat, labels[:])
>>> trees.classify(myTree, labels, [1, 0])
'no'
>>> trees.classify(myTree, labels, [1, 1])
'yes'

  可使用Python模塊pickle序列化對象,參見下面的程序。序列化對象能夠在磁盤上保存對象,並在須要的時候讀取出來。

 1 def storeTree(inputTree, filename):
 2     import pickle
 3     fw = open(filename, 'w')
 4     pickle.dump(inputTree, fw)
 5     fw.close()
 6  
 7 def grabTree(filename):
 8     import pickle
 9     fr = open(filename)
10     return pickle.load(fr)

9、示例:使用決策樹預測隱形眼鏡類型

>>> fr = open('lenses.txt')
>>> lensens = [inst.strip().split('\t') for inst in fr.readlines()]
>>> lensensLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
>>> lensesTree = trees.createTree(lensens,lensensLabels)
>>> lensensTree

  執行結果:{'tearRate': {'normal': {'astigmatic': {'yes': {'prescript': {'hyper': {'age': {'pre': 'no lenses', 'presbyopic': 'no lenses', 'young': 'hard'}}, 'myope': 'hard'}}, 'no': {'age': {'pre': 'soft', 'presbyopic': {'prescript': {'hyper': 'soft', 'myope': 'no lenses'}}, 'young': 'soft'}}}}, 'reduced': 'no lenses'}}

10、總結一下

  1. 計算整個數據集的
  2. 根據第1處計算出來的 再計算數據集按哪一列劃分最爲合適(計算數據集每一列的 ,根據全部列計算出來的 獲取最佳列),設此處計算出最佳列爲 I
  3. 獲取數據集第 I 列全部不重複值的集合,設此處集合爲 M
  4.  for v in M 循環集合 M
  5. 根據 I 列 和 v 值 劃分數據集
  6. 再遞歸運算
相關文章
相關標籤/搜索