# -*- encoding:utf-8 -*- from math import log import operator import matplotlib as plt def calcShannonEnt(dataSet): ''' 1.首先计算数据集种实例的个数 2.然后创建一个词典,键值是最后一列的数值,如果键值不存在,那么扩展字典保存当前键值,每个键值记录当前类别出现的次数 3.最后,使用所在类别的发生频率计算类别出现的频率 我们将用这个概率计算香浓熵,统计所有类标发生的次数 熵越高,则混合的数据越多,可以在数据集中添加更多的分类, 例如观测熵的变化 :param dataSet: :return: ''' numEntries=len(dataSet) labelsCounts={} # 遍历数据记录 for fect in dataSet: # 获得当前类标签 currentLabels=fect[-1] if currentLabels not in labelsCounts.keys(): # 如果在标签-个数的字典中不存在,那么将添加一个值 labelsCounts[currentLabels]=0 # 添加一个标签字典 labelsCounts[currentLabels]+=1 # 统计+1 shannonEnt=0.0 # 获得类别(遍历所有类别) for key in labelsCounts: # 计算各个类别的频率 # p=label-count/total dataset prob=float(labelsCounts[key])/numEntries # IG-=(i to n)log2P(xi) shannonEnt-=prob*log(prob,2) return shannonEnt def createDatsSet(): 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): ''' 1.输入参数:待划分的数据集;划分数据集的特征;需要返回的特征值 2.新建一个list列表,遍历数据集中的每个元素,发现符合的元素则将其添加到新建的列表中 :param dataSet:数据集 :param axis: 拆分的属性特征(认为是最好的特征) :param value:需要返回的特征值 :return: ''' retDataSet=[] # 创建空的列表保存计算后的值 for fect in dataSet: # 遍历数据集 # 如果取出的特征是最好(需要返回的值) # 那么将重组数据,提取出特征值,然后对剩下的数据进行重组,(特征值后面的值前移) if fect[axis]==value: reduceFectVec=fect[:axis] # 每条记录的前axis个值 reduceFectVec.extend(fect[axis+1:]) # 重组数据 retDataSet.append(reduceFectVec) return retDataSet def chooseBestFeatureToSplit(dataSet): ''' 选择最好的数据集划分形式 1.首先获得数据记录的特征个数 2.计算数据集的熵 3.遍历每个特征i: 将数据集中的所有第i个特征进行抽取,然后唯一化 对每个唯一化的值value进行遍历: 数据子集=拆分数据集(数据集,第i个特征,唯一化遍历的值value) prob=计算子集长度/数据集的长度 对唯一值的熵进行求和 4.比较所有特征中的信息增益,返回最好特征划分的索引值 :param dataSet: :return: ''' numFeatures=len(dataSet[0])-1 # 数据集的特征长度,这里需要为后面遍历每个特征提供数据 baseEntropy=calcShannonEnt(dataSet) # 计算熵(数据集) beatInfoGain=0.0 bestFeature=-1 for i in range(numFeatures): # 遍历每个特征进行数据拆分 # 取出所有数据记录的第i个特征,组装成填充到列表featList featList=[example[i] for example in dataSet] # 对提取到的特征值进行唯一化 uniqueVals=set(featList) newEntropy=0.0 for value in uniqueVals: # 遍历每个唯一化后的值 # 对第i个特征的值进行拆分出的数据子集 # 1.遍历当前特征中的所有唯一属性值,对每个特征划分一次数据集 # 2.然后计算数据集的新熵值,并对所有唯一特征值得到的熵求和,信息增益是熵的减少或数据无序度的减少 # 3.比较所有特征中的信息增益,返回最好特征划分的索引值 subDataSet =splitDataSet(dataSet,i,value) prob=len(subDataSet)/float(len(dataSet)) newEntropy+=prob*calcShannonEnt(subDataSet) infoGain=baseEntropy-newEntropy if (infoGain > beatInfoGain): beatInfoGain=infoGain bestFeature=i return bestFeature # 原理: # 1.得到原始数据集 # 2.然后基于最好的属性值划分数据集,由于特征值可能多于2个, # 因此,可能存在大于两个分支的数据集划分 # (1)第一次划分后,数据将被向下传递到树分支的下一个节点 # (2)然后在这个节点上,我们可以再次划分数据 # 3. # 递归的终止条件是:程序遍历完所有划分数据集的属性; # 或者每个分支上的所有实例都具有相同的类 # 如果所有实例具有相同的分类,则得到一个叶子节点或终止块 # 任何达到叶子节点的数据必然属于叶子节点的分类 def majorityCnt(classList): ''' 多数表决的方法决定该叶子节点的分类 同时,查看是不是已经使用所有的属性(在还没找出最好的分类结论之前) :param classList: 数据集的类别标签 :return: ''' classCount={} # 对类别数据集进行遍历 for vote in classList: # 如果类别不在统计字典中,则添加一个 if vote not in classCount.keys(): # 计为0 classCount[vote]=0 # 类别+1 classCount[vote]+=1 # 对类别进行排序, print classCount,'不排序前' sortedClassCount=sorted(classCount.iteritems(),key=operator.itemgetter(1),reverse=True) print sortedClassCount,'排序后' return sortedClassCount[0][0] def createTree(dataSet,labels): ''' 1. 输入两个参数:数据集和标签列表。 标签列表包含了数据集中所有特征的标签 算法本身并不需要这个变量,但是为了给出数据明确的含义,我们将它作为一个输人参数提供。 此外,前面提到的对数据集的要求这里依然需要满足。上述代码首先创建了名为。133^ 1 化的列表变量, 其中包含了数据集的所有类标签。递归函数的第一个停止条件是所有的类标签完全相同,则直接返回该类标签0 。 递归函数的第二个停止条件是使用完了所有特征,仍然不能将数据集划分成仅包含唯一类别的分组©。 由于第二个条件无法简单地返回唯一的类标签,这里使用程序清单3-3的函数挑选出现次数最多的类别作为返回值。 2.下一步程序开始创建树,这里使用Python语言的字典类型存储树的信息,当然也可以声明特 殊的数据类型存储树,但是这里完全没有必要。字典变量bestFeat存储了树的所有信息,这对于 其后绘制树形图非常重要。当前数据集选取的最好特征存储在变量beStFeat 中,得到列表包含 的所有属性值© 。这部分代码与程序清单3-3中的部分代码类似,这里就不再进一步解释了。 最后代码遍历当前选择特征包含的所有属性值,在每个数据集划分上递归调用函数 createTree ( ) ,得到的返回值将被插人到字典变量myTree中,因此函数终止执行时,宇典中将 会嵌套很多代表叶子节点信息的字典数据。在解释这个嵌套数据之前,我们先看一下循环的第一行 subLabels = labels[:],这行代码复制了类标签,并将其存储在新列表变量即证让訂沖。之 所以这样做,是因为在Python语言中函数参数是列表类型时,参数是按照引用方式传递的。为了保 证每次调用函数createTree时不改变原始列表的内容,使用新变量subLabels代替原始列表。 现在我们可以测试上面代码的实际输出结果,首先将程序清单3-4的内容输人到文件1^队?7 中’ 然后在Python命令提示符下输入下列命令: :param dataSet: :param labels: :return: ''' classList=[example[-1] for example in dataSet] # 获得所有标签 if classList.count(classList[0])==len(classList): #如果标签值的个数和数据长度一致,那么返回一个值就可以了 return classList[0] if len(dataSet[0])==1: # 如果数据长度为1,(只有一个值) return majorityCnt(classList) bestFeat=chooseBestFeatureToSplit(dataSet) # 选择最好的特征 bestFeatLabels=labels[bestFeat] #取出最好标签的 # mytree={'flipper:{0,'no',1,'yes'}'} myTree={bestFeatLabels:{}} del(labels[bestFeat]) featValues=[example[bestFeat] for example in dataSet] uniqueVals=set(featValues) for value in uniqueVals: subLabels=labels[:] myTree[bestFeatLabels][value]=createTree(splitDataSet(dataSet,bestFeat,value),subLabels) return myTree def storeTree(inputTree,filename): import pickle fw=open(filename,'w') pickle.dump(inputTree,fw) fw.close() def grabTree(filename): import pickle fr=open(filename,'r') return pickle.load(fr) class Mat: def __init__(self): self.decisionNode=dict(boxstyle='sawtooth',fc='0.8') self.leafNode=dict(boxstyle='round4',fc='0.8') self.arrow_args=dict(arrowstyle='<-') def createPlot(self): fig=plt.figure(1,facecolor='white') fig.clf() plt.ax1=plt.subplot(111,frameon=False) if __name__=='__main__': myDat,lables=createDatsSet() # 观察熵的变化 # myDat[0][-1]='maybe' bestFeature=chooseBestFeatureToSplit(myDat) # bestFeature=i 表示第i个特征是最好的用于划分数据集的特 print(myDat) myTree=createTree(myDat,labels=lables) # 变量myTree包含了很多代表树结构信息的嵌套字典,从左边开始, # 第一个关键字 no surfacing 是第一个划分数据集的特征名称,该关键字的值也是另一个数据字典。 # 第二个关键字是 no surfacing 特征划分的数据集,这些关键字的值是no surfacing 节点的子节点。 # 这些值可能是类标签,也可能是另一个数据字典。 # 如果值是类标签,则该子节点是叶子节点; # 如果值是另一个数据字典,则子节点是一个判断节点,这种格式结构不断重复就构成了整棵树。 # 本节的例子中,这棵树包含了3个叶子节点以及2个判断节点。 # {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}} print myTree
机器学习实战_决策树
最新推荐文章于 2024-10-13 17:29:26 发布