决策树
小白记录自已的学习过程
part1:什么是决策树?
- 以下面对简单的例子来说明,它就是一个if-then结构的流程图,如果满足什么条件了结果就怎么样了,处理的是分类数据,分类数据具有属性,属性也就是分类变量(在决策树里把分类变量叫做特征变量)
上面这个就是一个明显的if-then流程图,也就是一个简单的决策树了,决策树其实也就是树,一个分叉的树
这里给出一个简单的例子
这是课本上的海洋生物的例子
下面把这个分类数据转化为特征向量和标签向量组合的形式,每个样本点都是一个向量,有三个元素组成,最后一个元素是标签向量,也就是一个根据前面的特征向量对应的类别,是不是鱼类,eg:当不浮出水面,并且有脚蹼的时候就是鱼类了
那么这里有两个特征,是是否浮出水面的结果重要呢,还是是否有脚蹼的结果重要呢,那我们就要进行特征选择了,那我们怎么进行特征选择呢?
- 1.经验熵
先引入经验熵的定义,经验熵就是描述数据集分类的不确定性,令数据集为D=[[1,1,1], [1,1,1], [1,0,0],[0,1,0], [0,1,0]]
这里的D是一个五行三列的numpy数组
- 这里的k为类别标签的类别数,这里的类别标签的类别只有0,1,在这里我们一定要明白的是我们是为了得到最后的分类结果,最后是为了得到是不是鱼类,所以如果是任意一个海洋生物,根据已知的这5个结果判断它是不是鱼类的不确定性就是g(D),所以经验熵越大,根据样本分类的不确定性越大,错误越大。在这个例子当中数据集D的经验熵就是:
- 2、条件熵
下面在引入条件熵的定义,条件熵也就是在所选特征变量的条件下,数据集D分类结果的不确定性,比如说在浮出水面是否可以生存这一特征变量的条件下 ,根据这一特征变量的取值是否,可以把数据集分成两块,当取值为是或否的时候,分别判断这个时候分类的不确定性。这个时候是有两个数据集的,在两个数据集上D1,D2上分别计算经验熵
D1=[[1,1,1], [1,1,1], [1,0,0]]
D2=[[0,1,0], [0,1,0]]
此时
由此可知当浮出水面不能呼吸时,分类结果是确定的,也就是不是鱼类。为了书写方便,假设第一个特征变量为”x1”,第二个特征变量为”x2”,所以条件熵
同理
- 3、信息增益
信息增益的意思就是在给定特征变量以后,分类的不确定性减少了多少,当然不确定性减少的越多越好,也就是信息增益越大越好
-
我们选择特征变量就是选择使得信息增益最大的那个变量作为根节点,根节点就是最优特征变量,然后根据最优特征变量的取值来画有向边,这里根据计算结果可以知道x1对分类是最确定的,最明显的,当x1=0时,数据集D2的类别都是0,也就是当不能呼吸时,它一定不是鱼类,所以这一部分不用再继续划分了,当x1=1时,数据集D1的类别是不确定的,因为此时还有没有用到的特征变量x2,所以再利用x2的取值对D1进行处理,把D1分为了D11,D12,
D11=[[1,1,1],[1,1,1]],
D12=[[1,0,0]] -
所以此时计算,H(D11),H(D12),进而得到g(D1,x2),
当x2=1时,分类结果为使鱼类,当x2=0时,分类结果为不是鱼类,所以此时每个属性下的分类结果是唯一的,所以停止分析了。如果对于上一步当x1=1时,是不是鱼类也确定的话,就用不着x2了,也就停止分析了,另外就是当属性都遍历完了,每个属性也就是每个特征变量的取值都用完了,在某个取值下的分类结果还不确定的话采用投票的方法,投票法就是次数最多的那个就是它的类别。 -
上面的都是理论原理的分析,下面进行代码操作部分(这里仅仅先写决策树是怎么生成的代码)
**好羞愧啊,因为第一次写博客所以不知道怎么在这里插入公式,我只能在word里面敲然后截屏
from math import log#因为要用到对数函数
#首先定义数据集
def createData():#因为我这里是自已写出来数据,所以不用往里面传入参数,所以这样定义的
dataSet=[[1,1,1],
[1,1,1],
[1,0,0],
[0,1,0],
[0,1,0]]#labels就是数据集D,1代表是,0代表否
labels=["can breath","have foot"]#这个labels放的是特征变量的集合,也就是之间假设的x1,x2
return dataSet,labels
#数据集有了,然后按照之前的理论,下面就是应该计算原始数据集D经验熵了
def jingyanshang(dataSet):
#首先需要知道数据一共有多少个样本点
m=len(dataSet)
#然后需要知道类别标签有几个,也就是有几类,每类分别的个数
labelscounts={}#首先定义一个放类别次数的字典
for feature in dataSet:
currentlabel=feature[-1]
# if currentlabel not in labelscounts.keys(): #如果标签没有放入统计次数的字典,添加进去
# labelscounts[currentlabel]=0
# labelscounts[currentlabel]+=1
labelscounts[currentlabel]=labelscounts.get(currentlabel,0)+1
#上面已经求出来了类别的次数,下面带入数据计算经验熵
#首先定义经验熵
shang=0
#这里因为类别只有是鱼类,不是鱼类,所以分别计算p也是可以的,
#但是当多分类就不好了,所以用循环
for key in labelscounts:#对于字典来说,这样写代表遍历的是键
p=float(labelscounts[key]/m)
shang-=p*log(p,2)#对数函数是这样写的log(p,2)=log2 p
return shang
if __name__=="__main__":
dataSet,labels=createData()#这块一定要注意了啊,就是返回了几个值,就用几个值接受
shang=jingyanshang(dataSet)
print(dataSet)
print(shang)
#经验熵计算完了,我们现在就是要选择最优的特征变量来划分数据集了,那么首先就是选择最优特征
def chooseBestFeature(dataSet):
#首先需要知道特征变量的个数
numfeature=len(dataSet[0])-1
#dataSet,labels=createData()
shang=jingyanshang(dataSet)#先把经验熵放这,因为一会要用到它
tiaojianshang=0
infogain=0
bestfeaureindex=-1
bestinfogain=0
for i in range(numfeature):
featurevalue=[example[i] for example in dataSet]
uniquefeaurevalue=set(featurevalue)#这个步骤是为了获得独一无二的特征变量的取值
#然后利用这个独一无二的取值对原始数据集进行分割
tiaojianshang=0
for value in uniquefeaurevalue:
retdataSet=splitdataSet(dataSet,i,value)#现在划分的数据集出来啦
#接下连就对这划分后的数据集计算经验熵
numretdataSet=len(retdataSet)
pi=numretdataSet/float(len(dataSet))
#这个第一次得到的结果是在x1的条件下D的条件熵
tiaojianshang+=pi*jingyanshang(retdataSet)
infogain=shang-tiaojianshang#这个算的是第一个特征变量的信息增益
print("第%d个特征变量的信息增益为%s"%(i,infogain))
if infogain>bestinfogain:
bestinfogain=infogain
bestfeaureindex=i
return bestfeaureindex
#这里需要定义一个分割数据集的函数
def splitdataSet(dataSet,axis,value):
#这三个参数分别是待分割的数据集,按照哪个特征变量进行分割
#value是这个特征变量的取值
#首先定义一个放切分后的数据集的列表,当然列表里的元素还是列表
retdataSet=[]
for feature in dataSet:
if feature[axis]==value:
reducefeature=feature[:axis]
reducefeature.extend(feature[axis+1:])
retdataSet.append(reducefeature)
return retdataSet
if __name__=="__main__":
dataSet,labels=dataSet,labels=createData()
shang=jingyanshang(dataSet)#先把经验熵放这,因为一会要用到它
index=chooseBestFeature(dataSet)
bestVector=labels[index]
print("最优的索引值为:"+str(index))
print("最优的特征变量为:"+bestVector)
#就是一定要记得用谁先定义谁
执行这些代码的结果就是获得了最优的特征变量和它对应的索引值,如下
[[1, 1, 1], [1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 1, 0]]
0.9709505944546686
第0个特征变量的信息增益为0.4199730940219749
第1个特征变量的信息增益为0.17095059445466854
最优的索引值为:0
最优的特征变量为:can breath
-
下面我们来真正的构造决策树,用递归的方法构造决策树,上面的只是构造决策树的子模块,挑选特征向量的必备算法。用递归的方法就是我们之前说的当某个属性下的类别不唯一时,就要在去除上一个最优变量以后,再重新利用
这种方法找最优,在把树分叉,循环终止的 条件有以下几个
-
当样本点的类别都相同时,停止循环
-
当遍历完所有特征变量的属性时,停止循环,此时若遍历完所有属性时,仍存在在某一属性下类别不唯一的时候,就需要用投票的方法(这个代码我下面会介绍)
def majorityCnt(classlist):
classCount={}
#统计classList中每个元素出现的次数
for vote in classlist:
if vote not in classCount.keys():
classCount[vote]=0
classCount[vote]+=1
#根据字典的值降序排列
sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
return sortedClassCount[0][0]
# def majoritycounts(classlist):
# classcounts={}#定义一个字典来统计类标签向量
# for vote in classlist:
# classcounts[vote]=classcounts.get(vote,0)+1
# class=sorted(classcounts.items(),key=operator.itemgetter(1),reverse=True)
# return class[0][0]
#创建决策树
#dataSet是训练数据集,注意训练数据集是不断变化的,递归一次变一次,不要以为是不变的
#labels是特征变量的名字,bestlabels是当前最好的特征变量的集合
def createtree(dataSet,labels,bestlabels):
classlist=[example[-1] for example in dataSet]
#下面是第一个终止条件,当标签都相同时停止递归,直接输出
if len(set(classlist))==1:
return classlist[0]
#下面是第二个终止条件,当遍历完了所有的特征变量了,那么此时len(dataSet[0])==1
#也就是只有一个类标签了
if len(dataSet[0])==1:
#这个时候就要用投票原则了,这个时候在每个属性下类别唯一是投票原则的特例
#所以还是利用投票原则的方法
return majorityCnt(classlist)
#如果以上两种情况都不是的话,那么就递归的再选最优特征变量啊
bestfeatureindex=chooseBestFeature(dataSet)
bestVector=labels[bestfeatureindex]
bestlabels.append(bestVector)
#根据最优特征的标签生成树
#用字典的形式表示树
mytree={bestVector:{}}
#删除已经使用过的特征标签
del labels[bestfeatureindex]
#得到训练集中所有最优特征的属性值,并且还要唯一化
featvalues=[example[bestfeatureindex] for example in dataSet]
uniquefeatvalues=set(featvalues)
#然后遍历最优特征的属性值,创建决策树
for value in uniquefeatvalues:
#sublabels=labels[:]#创建这个非常重要,为了保证每次调用时原来的标签列表的内容不变
mytree[bestVector][value]=createtree(splitdataSet(dataSet,bestfeatureindex,value),labels,bestlabels)
#splitdataSet(dataSet,bestVector,value)这个调用得到的就是利用最优特征变量划分的数据集
return mytree
if __name__=="__main__":
dataSet,labels=createData()
bestlabels=[]#一定要先定义这个要不没办法传参
mytree=createtree(dataSet,labels,bestlabels)
print(mytree)
#在这里一定要明白一个概念就是度读程序的时候先读程序的主函数,然后再读定义函数
运行出来的结果为
{'can breath': {0: 0, 1: {'have foot': {0: 0, 1: 1}}}}
也就是以字典的形式表示了决策树
- 我上面定义的投票法,注释的那块还有一个代码,我不知道那样写为啥不对,对于统计次数,如果有大神能看见,拜托告诉我一下哈,小白很多都不懂,第一次发博文,欢迎大家批评指正!