决策树
优点:计算复杂度不高,输出结果易于理解,对中间值的确实不敏感,可以处理不相关特征数据
缺点:可能会产生过度匹配问题。
使用数据类型:数值型和标称型。
在构造决策树的时候,我们需要解决的第一个问题就是,当前数据集上那个特征在划分数据的时候起到了决定性作用?为了找到决定性的特征,划分作出最好的结果。我们必须评估完每个特征。完成测试之后原始数据集就被划分位几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于统一类型,则当前无需阅读的垃圾邮件已经正确的划分数据分类,无需进一步对数据进行分割。如果不属于统一类型则需要重复划分数据子集的过程。如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。
检测数据集中的每个子项是否属于同一分类: if so return 类标签; else 寻找划分数据集的最好特征 划分数据集 创建分支节点 for 每个划分的子集 调用函数createBranch并增加返回结果到分支节点中 return 分支节点
上面的伪代码是一个递归函数,在倒数第二行直接调用了他自己。后面我们将上面的伪代码转换成Python代码,这里我们需要进一步了解算法是如何划分数据集的。
决策树的一般流程:
1。收集数据:可以使用任何方法。
2。准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
3。分析数据:可以使用任何方法,构造树完成之后,我们应该检查图型是否符合预期。
4。训练算法:构造树的数据结构
5。测试算法:使用经验树来计算错误率。
6。使用算法:此算法可以用于任何监督学习算法,而是用决策树可以更好的理解数据的内在含义。
一些决策树算法采用二分法划分数据,本书不采用这种方法。如果根据某个属性划分数据将会产生四个可能的值,我们将把数据划分成四块,并创建成四个不同的分支。本书将使用 ID3 算法划分数据集,概算法处理如何划分数据集,何时停止划分数据集。每次划分数据集时我们之选择了一个特征属性如果训练集中存在20个特征,第一次我们选择那个特征作为划分的参考属性呢?(--当然是大分类下划分小分类了。--)
现在假设特征包括:不浮出水面是否可以生存,是否有脚蹼。我们可以将这些动物分成两类,鱼类和非鱼类。我们现在想决定依据哪一个特征来划分数据。在回答这个问题之前,我们必须采用量化的办法想一下。
信息增益
划分数据集的大原则是:将无序的数据集变得更加有序。我们可以使用多种方法划分数据集,但是每种方法都有自己的优缺点。组织杂乱无章的数据的一种方法就是使用信息论度量信息,信息论是量化处理信息的分支科学。我们可以在划分数据前后使用信息论量化度量信息的内容。
在划分数据集之前之后信息发生的变化称为信息增益,直到如何计算信息增益我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的就是最好的选择。
在评测那种数据划分方式是最好的数据划分之前,我们必须知道如何计算信息增益,集合信息的度量方式称为香农熵或者简称为熵,这个名字的起源来自于信息论之父 克劳德-香农 。
熵定义为信息的期望值,在明晰这个概念之前,我们必须知道信息的定义。如果待分类的事物可以划分在多个分类之中,则符号xi的信息定义为:其中p(xi)是选择该分类的概率。为了计算熵,我们需要计算所有类别所有可能包含的信息期望值,通过下面的公式得到:其中n是分类的数目。
from math import log def calcShannonEnt(dataSet): numEntries = len(dataSet) # dataSet的长度(此处为5) labelCounts = {} #创建一个labelCounts 集合 for featVec in dataSet: #将其中的每一个元素都遍历一边 currentLabel = featVec[-1] #取出dataSet中最后一列的值 if currentLabel not in labelCounts.keys(): #如果labelCount中没有该关键字的话。 labelCounts[currentLabel] = 0 #加上该关键字然后对应的值设置为 0 labelCounts[currentLabel] += 1 #将该关键字对应的值 + 1 shannonEnt = 0.0 #初始香农熵为 0 for key in labelCounts: #计算香农熵 prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob,2) return shannonEnt def createDataSet():#创建一个数据集 dataSet = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']] labels = ['no surfacing','flippers'] return dataSet,labels
下面开始进行计算
>>> import trees >>> dataSet,labels = trees.createDataSet() >>> trees.calcShannonEnt(dataSet) 0.9709505944546686
熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何进行变化的。
>>> dataSet [[1, 1, 'jack'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']] >>> trees.calcShannonEnt(dataSet) 1.3709505944546687
得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集,下一接我们将学习如何划分数据集以及如何度量信息增益。
另一个度量集合无序程度的方法是基尼不纯度,简单来说就是从一个数据集中随机选取子项。
from math import log def calcShannonEnt(dataSet): #根据公式计算香农熵。主要就是根据类别来说,前面的两个数字就没用到。 numEntries = len(dataSet) # dataSet的长度(此处为5) labelCounts = {} #创建一个labelCounts 集合 for featVec in dataSet: #将其中的每一个元素都遍历一边 currentLabel = featVec[-1] #取出dataSet中最后一列的值 if currentLabel not in labelCounts.keys(): #如果labelCount中没有该关键字的话。 labelCounts[currentLabel] = 0 #加上该关键字然后对应的值设置为 0 labelCounts[currentLabel] += 1 #将该关键字对应的值 + 1 shannonEnt = 0.0 #初始香农熵为 0 for key in labelCounts: #计算香农熵 prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob,2) return shannonEnt def createDataSet():#创建一个数据集 dataSet = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']] labels = ['no surfacing','flippers'] return dataSet,labels def splitDataSet(dataSet,axis,value): #看名字是划分数据用的。 reDataSet = [] for featVec in dataSet: # 从dataSet中一个一个遍历。 这里是从 中选择, if featVec[axis] == value: # 其中的单个数组中的axis的值如果等于 value reducedFeatVec = featVec[:axis] # 将单数组的前axis项加入。不包括axis reducedFeatVec.extend(featVec[axis+1:]) # 注意extend和append的区别, reDataSet.append(reducedFeatVec) #加入reDataSet。 return reDataSet # 将数组返回。 def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #计算出不同物体时间的描述量的数量。 baseEntropy = calcShannonEnt(dataSet) #计算原始数据的香农熵 bestInfoGain = 0.0 # bestFeature = -1 # for i in range(numFeatures): # 将每组待处理数据都走一遍。 featList = [example[i] for example in dataSet] #收集每组数据的第一个属性 uniqueVals = set(featList) # 用set来生成唯一性。 找到有几个 类别。 newEntropy = 0.0 for value in uniqueVals: # 将每个类别下放 , 分别计算以该类别分类的时候香农熵是多少。 subDataSet = splitDataSet(dataSet,i,value) # 找出符合要求的数据。 # print("subDataSet",subDataSet,"\n") prob = len(subDataSet)/float(len(dataSet)) # 这一行和下一行用于计算 该分类在总数中所占的香农熵的大小。 # print("prob",prob,"\n") newEntropy += prob * calcShannonEnt(subDataSet) # # print("newEntropy",newEntropy,"baseEntropy",baseEntropy,"\n") infoGain = baseEntropy - newEntropy #上面的for 已经将 某种分类情况下的香农熵计算完毕,得到了按照该分类下的香农熵,然后在这里 计算如果分类越精确的话香农熵是应该越小的 # print("当前是按照第%d个描述量分类" % i + "现在的香农熵是%lf" % newEntropy + "\n" + "量化计算值是%lf" % infoGain) # print(baseEntropy) #也就是 info的值应该越大。 print(infoGain,bestInfoGain,"\n") if(infoGain>=bestInfoGain): bestInfoGain = infoGain bestFeature = i print(infoGain,bestInfoGain,"\n") return bestFeature # 计算出按照哪一个属性分类是 , 分类更精确。。。
#34行代码计算了整个数据集的原始香农熵,我们保存最初的无序度量值,用于和划分完之后的数据集计算的熵值进行比较。第一个for 循环遍历数据集中所有的特征。使用列表推导来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新列表中。然后使用python的原生集合(set)数据类型。集合数据类型与列表类型相似,不同之处仅在于集合类型中的每个值互不同。从列表中创建集合是python语言得到列表中唯一元素之最快的办法。
#遍历当前特征中的所有唯一属性值,对每个特征划分一次数据集,然后计算数据集的新熵值,并对所有唯一特征值得到的熵求和,信息增益是熵的减少或者是数据无序度的减少,大家坑定对于将熵用于度量数据的无序度的减少更容易理解,最后,比较所有特征中的信息增益,返回最好特征划分索引值。
#程序运行输出的结果是0也就是代表了用每一组的第一个数据进行划分的时候可以得到最大的信息增益。
预备知识已成开始构建决策树。
目前我们已经学习了从数据集中构造决策树算法所需要的子功能模块。其工作原理如下:得到原始数据,然后基于最好的属性值划分数据集,由于特征可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据将被向下传递到树分支的下一个结点,在这个结点上,我们可以再次划分数据因此我们可以采用递归的方法处理数据集。
递归结束的条件是:程序遍历完所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类。如果所有的实例都具有相同的分类,则得到一个叶子结点或者终止块。任何到达叶子结点的数据必然属于叶子结点的分类。
第一个结束条件使得算法可以终止,我们甚至可以设置算法可以划分的最大分组数目。后续章节还会介绍其他决策树算法,如C4.5和CART,这些算法在运行的时候并不总是在每次划分分组的时候都会消耗特征值。由于特征数目并不是在每次划分数据分组的时候都会减少,因此这些算法在实际应用的时候可能会引起一定的问题。目前我们并不需要考虑这些问题,只需要在算法开始运行前计算列的数目,查看算法是否使用了所有的属性即可。如果数据集已经处理了所有的属性,但是类标签依然不是唯一的,此时我们需要决定如何定义该叶子结点,在这种情况下,我们通常会会采用多种多数表决的方法确定该叶子结点的分类。
from math import log import operator def calcShannonEnt(dataSet): #根据公式计算香农熵。主要就是根据类别来说,前面的两个数字就没用到。 numEntries = len(dataSet) # dataSet的长度(此处为5) labelCounts = {} #创建一个labelCounts 集合 for featVec in dataSet: #将其中的每一个元素都遍历一边 currentLabel = featVec[-1] #取出dataSet中最后一列的值 if currentLabel not in labelCounts.keys(): #如果labelCount中没有该关键字的话。 labelCounts[currentLabel] = 0 #加上该关键字然后对应的值设置为 0 labelCounts[currentLabel] += 1 #将该关键字对应的值 + 1 shannonEnt = 0.0 #初始香农熵为 0 for key in labelCounts: #计算香农熵 prob = float(labelCounts[key])/numEntries shannonEnt -= prob * log(prob,2) return shannonEnt def createDataSet():#创建一个数据集 dataSet = [[1,1,'yes'],[1,1,'yes'],[1,0,'no'],[0,1,'no'],[0,1,'no']] labels = ['no surfacing','flippers'] return dataSet,labels def splitDataSet(dataSet,axis,value): #看名字是划分数据用的。 reDataSet = [] for featVec in dataSet: # 从dataSet中一个一个遍历。 这里是从 中选择, if featVec[axis] == value: # 其中的单个数组中的axis的值如果等于 value reducedFeatVec = featVec[:axis] # 将单数组的前axis项加入。不包括axis reducedFeatVec.extend(featVec[axis+1:]) # 注意extend和append的区别, reDataSet.append(reducedFeatVec) #加入reDataSet。 return reDataSet # 将数组返回。 def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0]) - 1 #计算出不同物体时间的描述量的数量。 baseEntropy = calcShannonEnt(dataSet) #计算原始数据的香农熵 bestInfoGain = 0.0 # bestFeature = -1 # for i in range(numFeatures): # 将每组待处理数据都走一遍。 featList = [example[i] for example in dataSet] #收集每组数据的第一个属性 uniqueVals = set(featList) # 用set来生成唯一性。 找到有几个 类别。 newEntropy = 0.0 for value in uniqueVals: # 将每个类别下放 , 分别计算以该类别分类的时候香农熵是多少。 subDataSet = splitDataSet(dataSet,i,value) # 找出符合要求的数据。 # print("subDataSet",subDataSet,"\n") prob = len(subDataSet)/float(len(dataSet)) # 这一行和下一行用于计算 该分类在总数中所占的香农熵的大小。 # print("prob",prob,"\n") newEntropy += prob * calcShannonEnt(subDataSet) # # print("newEntropy",newEntropy,"baseEntropy",baseEntropy,"\n") infoGain = baseEntropy - newEntropy #上面的for 已经将 某种分类情况下的香农熵计算完毕,得到了按照该分类下的香农熵,然后在这里 计算如果分类越精确的话香农熵是应该越小的 # print("当前是按照第%d个描述量分类" % i + "现在的香农熵是%lf" % newEntropy + "\n" + "量化计算值是%lf" % infoGain) # print(baseEntropy) #也就是 info的值应该越大。 print(infoGain,bestInfoGain,"\n") if(infoGain>=bestInfoGain): bestInfoGain = infoGain bestFeature = i print(infoGain,bestInfoGain,"\n") return bestFeature # 计算出按照哪一个属性分类是 , 分类更精确。。。 #34行代码计算了整个数据集的原始香农熵,我们保存最初的无序度量值,用于和划分完之后的数据集计算的熵值进行比较。第一个for 循环遍历数据集中所有的特征。使用列表推导来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新列表中。然后使用python的原生集合(set)数据类型。集合数据类型与列表类型相似,不同之处仅在于集合类型中的每个值互不同。从列表中创建集合是python语言得到列表中唯一元素之最快的办法。 #遍历当前特征中的所有唯一属性值,对每个特征划分一次数据集,然后计算数据集的新熵值,并对所有唯一特征值得到的熵求和,信息增益是熵的减少或者是数据无序度的减少,大家坑定对于将熵用于度量数据的无序度的减少更容易理解,最后,比较所有特征中的信息增益,返回最好特征划分索引值。 #程序运行输出的结果是0也就是代表了用每一组的第一个数据进行划分的时候可以得到最大的信息增益。 def majorityCnt(classList): classCount = {} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.iteritems(),key = operator.itemgetter(1),reverse=True) return sortedClassCount[0][0] def createTress(dataSet,labels): classList = [example[-1] for example in dataSet] # 1 (一下两行)类别完全相同则停止继续划分 if classList.count(classList[0]) == len(classList): return classList[0] # 2 (一下两行) 遍历完所有特征时返回出现次数最多的 if len(dataSet[0]) == 1: return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet) bestFeatureLabel = labels[bestFeat] myTree = {bestFeatureLabel:{}} # 3 得到列表包含的所有属性值 del(labels[bestFeat]) featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: subLabels = labels[:] myTree[bestFeatureLabel][value] = createTress(splitDataSet(dataSet,bestFeat,value),subLabels) return myTree