一、简介
(1)本章主要使用字典来存储决策树,但字典的可读性太低。后面将用matplotlib将其形象化。
优点:计算复杂度不高、输出结果易于理解、对中间值的缺失不敏感、可以处理不相关特征数据
缺点:可能会产生过度匹配问题
(2)#创建决策树的伪代码函数createBranch()如下:
检查数据集中的每个子项是否属于同一个分类
if so return 类标签;
else
寻找划分数据集的最好特征
划分数据集
创建分支结点
for 每个划分的子集
调用createBranch并增加返回结果到分支结点中
return 分支结点
二、构造决策树
一步一步将上述伪代码转为Python代码
1 划分数据集
划分数据集的原则是:将无序的数据变得更加有序。在这里,用“信息增益(information gain)”来衡量数据的有序程度。信息增益越大,则数据变得更有序。
数据假定为:
第一列为feature1,第二列为feature2,第三列为label
信息增益=原始数据集的熵之和-新数据集的熵之和
那么什么是熵(entropy)呢?(这边只介绍公式,具体内容大家有兴趣的自行了解)熵来源于信息论之父克劳德·香农。对于指定label的数据集,其熵的计算公式为:
e=-pi*log(2,pi)
其中,pi表示该label("yes","no"或“maybe”)的数量占总样本数量的比例。
如果我们要计算整个数据集的熵,则:
H=-(p1*log(2,p1)+p2*log(2,p2)+...+pn*log(2,pn))
其中,pn为第n个label占总数的比。
(1)计算给定数据集的香农熵
#计算给定数据集的香农熵 from math import log def calcShannonEnt(dataSet): numEntries=len(dataSet) labelCounts={} #定义一个统计label的字典,每个Label都能且只能被存放1次 #统计各个label的数量,放在字典里 for featVec in dataSet: currentLabel=featVec[-1] #取最后一列(label列) if currentLabel not in labelCounts.keys(): #如果取出的这个label不在字典里,就在下面加入labelCounts中,并将其次数初始化为0 labelCounts[currentLabel]=0 labelCounts[currentLabel]+=1 #为当前取出的label的次数加1 shannonEnt=0.0 #遍历每个label,分别计算每个label对应的熵并累加起来 for key in labelCounts: prop=float(labelCounts[key])/numEntries shannonEnt-=prop*log(prop,2) return shannonEnt
(2)上一步实现了对某一特定数据集熵的计算,现在来学习怎样按照给定的feature划分出一个新的数据集。
#按照给定特征划分数据集 #axis:划分数据集的特征值的维度(哪个feature) #value:维度axis上的返回值(0,1或者3) def splitDataSet(dataSet,axis,value): retDataSet=[] #创建返回的list #遍历dataSet中的每个样本 for featVec in dataSet: if featVec[axis]==value: #抽取:将符合要求的值去掉axis列数据(去除该feature)后添加到retDataSet集上 reducedFeatVec=featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) #extend:把一个序列(sequence)的内容添加到列表中 retDataSet.append(reducedFeatVec) #append:向列表中添加一个对象 return retDataSet
(3)现在,我们学会了计算指定数据集的香农熵和按照指定feature划分数据集。现在,将这两个结合起来,我们就知道怎么选择最好的数据集划分方式了:
#选择最好的数据集划分方式 #返回可获得最大信息增益的那个feature的维度 def chooseBestFeatureToSplit(dataSet): numFeatures=len(dataSet[0])-1 #计算feature的数量(减1:减去最后一列标签) baseEntropy=calcShannonEnt(dataSet) #计算了数据集的原始香农熵 bestInfoGain=0.0 #初始化最佳的信息增益位0.0 bestFeature=-1 #初始化最好的feature的维度为-1 #遍历每列feature for i in range(numFeatures): featList=[example[i] for example in dataSet] #遍历dataSet的每一行,取其第i个feature(第i列)上的属性值放在featList上 uniqueVals=set(featList) #set():创建featList上“无序不重复”的元素集 newEntropy=0.0 #遍历i列feature中的所有可能属性值(不重复) #计算第i列feature的信息增益 for value in uniqueVals: #计算i列所有划分方式的信息熵之和 subDataSet=splitDataSet(dataSet,i,value) prob=len(subDataSet)/float(len(dataSet)) newEntropy+=prob*calcShannonEnt(subDataSet) #计算信息增益 infoGain=baseEntropy-newEntropy #比较所有特征中的信息增益,如果当前信息增益大于bestInfoGain,则更新这个bestInfoGain if(infoGain>bestInfoGain): bestInfoGain=infoGain bestFeature=i return bestFeature
2 递归构造决策树
回顾第一部分,我们已经学习了从数据集构建决策树算法所需要的子功能模块:将原始数据集基于最好的feature划分。第一次划分后,数据集将被分散传递到树分支的子节点,在这个节点上,我们可以再次划分数据(当feature不止1个时)。因此,我们可以采用递归的原则处理数据集。
递归结束的条件是:①每个分支下的所有的所有实例都具有相同的分类;②程序遍历完所有划分数据集的feature
第二个结束条件使得算法可以终止。但是如果程序遍历完所有feature后,某些feature中的属性对应的label仍不是唯一的,此时我们需要决定如何定义该叶子节点。
(1)在这种情况下,通常采用“多数表决”的方法决定该叶子节点的分类:
#多数表决:返回出现频率最高的那个标签 def majorityCnt(classList): classCount={} for vote in classList: if vote not in classCount.keys: classCount[vote]=0 classCount[vote]+=1 #按照classCount中value值从大到小排列 sortedClassCount=sorted(classCount.items(), key=operator.itemgetter(1), #获取对象第1个域的值(第0个域:key,第1个域:value) reverse=True) return sortedClassCount[0][0]
(2)现在,可以真正地构建树了(一棵存储在字典中的树)
#feature:feature的名称 def createTree(dataSet,feature): classList=[example[-1] for example in dataSet] #取出dataSet的label集 #若classList中所有的标签都和classList[0]的一致(类别完全相同),则停止继续划分 if classList.count(classList[0])==len(classList): return classList[0] #若使用完了所有特征,仍不能将数据集划分成仅包含唯一label的分组 #则返回出现次数最多的类别作为标签 if len(dataSet[0])==1: return majorityCnt(classList) bestFeat=chooseBestFeatureToSplit(dataSet) #找出最适合划分数据集的feature的维度 bestFeatFeature=feature[bestFeat] #找出对应的feature名称 myTree={bestFeatFeature:{}} #决策树的key为当前的bestFeatFeature del(feature[bestFeat]) #删除当前这列feature featValues=[example[bestFeat] for example in dataSet] #遍历这列feature的每个属性值,取出放在featValues上 uniqueVals=set(featValues) #创建featValues上的无序不重复元素集 #遍历当前feature上的每个属性 for value in uniqueVals: subFeature=feature[:] #对每个属性的子节点调用createTree进行下一步划分 myTree[bestFeatFeature][value]=createTree(splitDataSet(dataSet,bestFeat,value),subFeature) return myTree