决策树伪代码是:
- 检测数据集中的每个子项是否为同一个分类
- 如果是,则返回该类标签
- 如果不是:
根据信息增益寻找划分此数据集的最好的特征
划分数据集
创建分支节点
for 每个划分的子集
调用自身函数形成递归并增加返回结果到分支节点中
return 分支节点
★ 必要知识
1. 香农熵公式:
其中: 为样本数据集D中第K类样本所占的比例(k=1,2,...|y|), 在本例中,k类就是是否为鱼类,k要么是1,要么是2,就这两种情况,所以 就是正例(是鱼类)或者反例(不是鱼类)的概率
2. 信息增益 :
D数据集中,在属性(或特征)a上有V个不同的取值{,, ... }。因此用属性a对数据集D进行划分,会产生V个分支点,其中,第v个分支节点包含了D中所有在属性a上取值为 的样本,记为 。
考虑到不同的分支点所包含的数据个数不同,所以给分支节点赋予权重 ||/|| ,故数据个数越多的分支节点影响越大
其python代码如下:
1. 得到数据集及特征属性:
海洋生物数据 不浮出水面是否能生存(no surfacing) 是否有脚蹼(flippers) 属于鱼类 1 是 是 yes 2 是 是 yes 3 是 否 no 4 否 是 no 5 否 是 no
# coding=utf-8 import operator #运算符 from math import log 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
2. 计算给定数据集的香农熵
def calcShannonEnt(dataSet): #dataSet是输入的列表 numEntries=len(dataSet) #得到数据集的行数 labelCount={} #labelCount是存放类别标签及其对应个数的字典 for featVec in dataSet: #遍历数据集,得到的是一个个一维列表 currLabel=featVec[-1] #currLabel是featVec的最后一列,结合咱们这个数据集的特点,最后一列是类别标签(判断是不是鱼类),所以currLabel不是yes就是no,并且currLabel是个一列表 if currLabel not in labelCount.keys(): #如果这个类别标签(yes或者no)不在labelCount字典的键中 labelCount[currLabel]=0 #那么把这个标签存入labelCount的"键"中,并把其对应的"值"赋为0 labelCount[currLabel]+=1 #如果在的话把该类别标签对应的“值”加上1 shannonEnt=0.0 #初始香农熵为0 for key in labelCount: #遍历字典,得到的是字典的“键” prob=float(labelCount[key])/numEntries #算出该类别标签(yes或no)的概率,即公式中的p(x),其实就是计算正例的概率和反例的概率 shannonEnt -= prob * log(prob, 2) # 根据数学公式写出的香农熵代码,log函数表示以2为底,prob的对数 return shannonEnt # 返回香农熵
3. 根据选定的特征划分数据集(选出既满足划分的又把该特征剔除的剩余的数据集存贮下来)
def splitDataSet(dataSet,axis,value): #axis表示你要选取数据集中的第axis列作为特征,value为你取的值,用它和刚选取的第axis列的特征值做比较 retDataSet=[] #保存提出特征之后的剩余的数据集,先设成列表,这样可以用append和extend函数 for featVec in dataSet: #遍历数据集,取出featVec的是一个列表 if featVec[axis]==value : #选择划分依据,即此列满足选择要求,可作为特征提出去 reduceFeatVec=featVec[:axis] #把aixs之前的列拷贝到reduceFeatVec列表中,当然不包括第axis列,遵循左闭右开原则 reduceFeatVec.extend(featVec[axis+1:]) #把从第axis+1列开始到最后一列添加到reduceFeatVec中 retDataSet.append(reduceFeatVec) #以满足划分条件为前提,把每一个子项的特征提出去之后,再一个个存入retDataSet二维列表中,形式基本是[[x,e,w],[w,r,n],[s,e,t]...] return retDataSet #返回剩余的数据集
注意: extend() 是只把内容添加进去,不包括内容以前的格式
而 append() 把内容和格式一起添加进去了
4. 选择出最好的数据集划分:(即信息增益越大,划分后的纯度越高,越能符合我们的要求)
def chooseBestFeatureToSplit(dataSet): #选择最好的特征 numFeature=len(dataSet[0])-1 #得到每个子项的列数的下标值,因为我们的下标都是从0开始数的 initEntry=calcShannonEnt(dataSet) #初始香农熵赋为initEntry bestInfoGain=0.0 #初始化最好的特征信息增益为0.0 bestFeature=-1 #初始化最好特征的下标为-1 for i in range(numFeature): #根据左闭右开,这里i是取不到numFeature的,即又少了最后一列,因为最后一列不是特征,是类别标签(即是不是鱼),所以在此不应该包含 featList=[example[i] for example in dataSet] #这里是列表生成式,example是数据集的每个子项(比如第一行),是列表格式,而examploe[i]为某一行的第i个元素,它是某个特征的对应值,则featList是包含这些特征值的列表,相当于把数据集的特征值一列一列的提取出来了 uniqueVals=set(featList) #把这些特征值去重复 newEntry=0.0 #新的香农熵初始化为0 for value in uniqueVals: #遍历uniqueVals集合的值,里面是某个特征对应的值,比如本例中,若选取的是有无脚蹼特征,那么集合里面就是有脚蹼或者无脚蹼,即里面就是0和1 subDataSet=splitDataSet(dataSet,i,value) #选取此特征,subDataSet为满足该划分的数据集 prob=len(subDataSet)/float(len(dataSet)) #满足该划分的数据集,prob就是公式中的权重 newEntry+=prob*calcShannonEnt(subDataSet) #计算该特征划分下不同特征取值的权重香农熵 infoGain=initEntry-newEntry #这就是信息增益公式 if infoGain>bestInfoGain: #挑选出信息增益最大的特征(属性) bestInfoGain=infoGain bestFeature= i return bestFeature #返回该特征的下标
5. 当数据集处理了所有属性,却没法确定最终类标签(此例中即到底是不是鱼),那么可通过多数表决的方法来确定最终分类
def majorityCnt(classList): #classList为类标签,即是鱼(yes)或不是鱼(no) classCount={} #存放“键”为yes或no的字典,比较其对应的”值”便可知道谁出现的次数多,最终目的就是这个 for vote in classList: if vote not in classCount.keys(): #判断类别(yes或no)是否为字典的“键“ classCount[vote]=0 #如果不在,把yes或者no存入键内,对应的”值“初始化为0 classCount[vote]+=1 #如果在,键对应的“值”加1,即计数加上1 sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True) #把字典分割成一个个元组,并按每个元组的第二元素的大小从大到小排列 return sortedClassCount[0][0] #返回出现次数最多的类别的名称,即yes或no
6. 创建树:
def createTree(dataSet,labels): #labels保存的是特征的名称(比如本例的no surfacing,flippers) classList=[example[-1] for example in dataSet] #把最后一列即类别(是否为鱼)存入到classList列表中 if classList.count(classList[0])==len(classList): #如果classList中的所有类标签都相同 return classList[0] if len(dataSet[0])==1: #如果用完了所有特征 return majorityCnt(classList) bestFeatSub=chooseBestFeatureToSplit(dataSet) #得到最好特征的下标 bestFeatLabel=labels[bestFeatSub] #根据下标把特征名取出来 myTree={bestFeatLabel:{}} #定义树的格式是字典格式 del(labels[bestFeatSub]) #从labels列表中删除变量名,但不删除它引用的数据 featValues=[example[bestFeatSub] for example in dataSet] #把该特征的取值取出来,放到featValues列表中 uniqueValues=set(featValues) #把属性值去重复 for value in uniqueValues: #遍历属性值 subLabel=labels[:] #当函数的参数是列表时,参数是按引用方式传递的,为了保证每次调用函数createTree时不改变原列表的内容,故用sublabel代替原列表 myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeatSub,value),subLabel) #递归调用 return myTree
7. 实例化即函数调用
myDat,labels=createDataSet() result=createTree(myDat,labels) print(result) #打印树,其实是字典格式
8. 输出结果:
C:\Users\Anaconda3\python.exe C:/Users/PycharmProjects/machine_learning/快速数据分析/测试.py {'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}} Process finished with exit code 0