决策树遵循“分而治之”策略,是一种树形结构,其中每个内部结点表示在一个属性上的测试,每个分支代表一个测试输出,每个叶结点代表一种类别,目的是产生一颗泛化能力强,即处理未见示例能力强的决策树。
优点:可以自学习
缺点:过拟合、泛化能力弱,生成的树不一定全局最优
划分选择:决策树学习的关键是如何划分属性,使得结点的“纯度”越来越高,具体的可按照三种划分标准。
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)