決策樹遵循“分而治之”策略,是一種樹形結構,其中每個內部結點表示在一個屬性上的測試,每個分支代表一個測試輸出,每個葉結點代表一種類別,目的是產生一顆泛化能力強,即處理未見示例能力強的決策樹。
優點:可以自學習
缺點:過擬合、泛化能力弱,生成的樹不一定全局最優
划分選擇:決策樹學習的關鍵是如何划分屬性,使得結點的“純度”越來越高,具體的可按照三種划分標准。
1.信息增益(ID3決策樹學習算法):表示得知特征A的信息而使得類X的信息的不確定性減少的程度。信息增益越大,意味着使用屬性來進行划分所獲得的純度提升越大。
信息熵 = 經驗熵H(D) – 經驗條件熵(H(D|A))
2.增益率(C4.5決策樹算法):信息增益可能對數目較多的屬性有偏好,為減少這種偏好使用增益率。C4.5算法是先從划分屬性中找出信息增益高於平均水平的屬性,然后在選擇增益率最高的。
3.基尼指數(CART決策樹算法):Gini(D)反映了從數據集中隨機抽取兩個樣本,其類別標記不一致的概率,Gini(D)越小,數據集D的純度越高。在選擇划分屬性時,我們選擇基尼指數最小的屬性作為最優划分屬性。
下面是未剪枝C4.5python實現的代碼:
1 import math 2 import operator 3 import xlrd 4 #功能:導入數據表 5 #功能:計算熵 6 def calcShannonEnt (dataSet): 7 num = len(dataSet)#實例的個數 8 labelCounts = {}#類標簽 9 for featVec in dataSet: 10 currentLabel = featVec[-1]#最后一列的數值 11 if currentLabel not in labelCounts.keys():#如果在labelCounts中沒出現 12 labelCounts[currentLabel] = 0#就把currentLabel鍵加入labelCounts中,值為0 13 labelCounts[currentLabel] += 1#labelCounts對應的值就加一 14 #計算香農熵H(D) 15 ShannonEnt = 0.0 16 for key in labelCounts: 17 prob = float(labelCounts[key]) / num#計算p(Xi)概率 18 ShannonEnt -= prob * math.log(prob, 2) 19 return ShannonEnt 20 21 #功能:按照給定特征划分數據集 22 #輸入:數據集、划分數據集的特征、需要返回的特征的值 23 #返回:划分后的數據集 24 def splitDataSet(dataSet, axis, value): 25 newdataSet = [] 26 for featVec in dataSet: 27 if featVec[axis] == value: 28 reducedFeatVec = featVec[:axis] 29 reducedFeatVec.extend(featVec[axis+1:]) 30 newdataSet.append(reducedFeatVec) 31 return newdataSet 32 33 #功能:選取最好的數據集划分方式 34 #返回:最佳特征下標(增益率最大) 35 def chooseBestFeatureTosplit(dataSet): 36 numFeatures = len(dataSet[0]) - 1#特征個數 37 baseEntropy = calcShannonEnt(dataSet)#原始香農熵H(D) 38 bestInfoGainrate = 0.0; bestFeature = -1#信息增益和最好的特征 39 #遍歷特征 40 for i in range(numFeatures): 41 featureSet = set([example[i] for example in dataSet])#第i個特征取值集合 42 newEntropy = 0.0 43 splitinfo = 0.0 44 for value in featureSet: 45 subDataSet = splitDataSet(dataSet, i, value) 46 prob = len(subDataSet)/float(len(dataSet)) 47 newEntropy += prob * calcShannonEnt(subDataSet)#經驗條件熵H(D|A) 48 splitinfo -= prob * math.log(prob,2)#經驗熵HA(D) 49 #當概率為1或者0時(因為經驗熵要做被除數): 50 if not splitinfo: 51 splitinfo = -0.99 * math.log(0.99,2) - 0.01 * math.log(0.01,2) 52 infoGain = baseEntropy - newEntropy#信息增益=H(D)-H(D|A) 53 infoGainrate = float(infoGain) / float(splitinfo)#增益率=信息增益/經驗熵 54 if infoGainrate > bestInfoGainrate: 55 bestInfoGainrate = infoGainrate 56 bestFeature = i 57 return bestFeature 58 59 #功能:多數表決決定葉子結點 60 #使用分類名稱的列表,創建鍵值為classList中唯一值的數據字典,字典對象存儲了classList每個類標簽出現的頻率 61 #返回:出現次數最多的分類名稱 62 def majorityCnt(classList): 63 classCount = {} 64 for vote in classList: 65 if vote not in classCount.keys(): 66 classCount[vote] = 0 67 classCount[vote] += 1 68 sortedClassCount = sorted(ClassCount.iteritems(),key = operator.itemgetter(1), reverse = True) 69 return sortedClassCount[0][0] 70 71 #功能:創建樹 72 #輸入數據集和類標簽 73 #返回字典樹 74 def createTree(dataSet, labels): 75 classList = [example[-1] for example in dataSet]#數據集的所有類標簽 76 if classList.count(classList[0]) == len(classList):#停止條件是所有的類標簽完全相同 77 return classList[0] 78 if len(dataSet[0]) == 1:#當使用完了所有特征,還不能將數據集划分成僅包含唯一類別的分組 79 return majorityCnt(classList)#挑選出出現次數最多的類別作為返回值 80 #開始創建樹 81 bestFeat = chooseBestFeatureTosplit(dataSet)#當前數據集選取的最好特征 82 bestFeatLabel = labels[bestFeat] 83 myTree = {bestFeatLabel:{}} 84 #得到列表包含的所有屬性值 85 #賦值當前特征標簽列表,防止改變原始列表的內容 86 subLabels = labels[:] 87 #刪除屬性列表中當前分類數據集特征 88 del(subLabels[bestFeat]) 89 #獲取數據集中最優特征所在列 90 featValues = [example[bestFeat] for example in dataSet] 91 #采用set集合性質,獲取特征的所有的唯一取值 92 uniqueVals = set(featValues) 93 for value in uniqueVals: 94 myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels) 95 return myTree 96 97 ''' 功能:決策樹分類函數 98 思路: 99 執行數據分類時,使用決策樹以及用於構造決策樹的標簽向量。 100 比較測試數據與決策樹上的數值 101 遞歸執行該過程直到進入葉子結點 102 將測試數據定義為葉子結點所屬的類型 103 ''' 104 def classify(inputTree, featLabels, testVec): 105 #將標簽字符串轉換為索引 106 firstSides = list(inputTree.keys()) 107 firstStr = firstSides[0] 108 secondDict = inputTree[firstStr] 109 featIndex = featLabels.index(firstStr) 110 #----------------------------------- 111 for key in secondDict.keys(): 112 if testVec[featIndex] == key: 113 if type(secondDict[key]).__name__ =='dict': 114 classLabel = classify(secondDict[key], featLabels, testVec) 115 else: classLabel = secondDict[key] 116 return classLabel 117 118 #功能:后剪枝 119 def 120 121 #功能:觀察決策樹的分類率 122 def getAccuracy(dataSet,testDatas,testLabels,myTree): 123 count = 0#分類正確的數量 124 print('數據量:',len(testDatas)) 125 print('屬性數量:',len(testLabels)) 126 num = 0#分類正確的數量 127 for testData in testDatas: 128 i = 0 129 a = classify(myTree, testLabels,testData) 130 if a == dataSet[i][-1]: 131 count += 1 132 i += 1 133 print("分類正確的數量是: ",count) 134 accuracy = count / len(testDatas) 135 print("決策樹分類器的正確率為:",accuracy)