**
什么是决策树
**
决策树(Decision Tree)是一种依托决策而建立起来的一种树。是有监督学习的一种,是一种基本的分类与回归的方法。也就是说决策树有两种:分类树和回归树。
先从分类树开始学习
其实从分类树这个名词我们就可以开始发挥想象力了,一棵树,有根有许多分支。那么与决策有什么关系呢?决策就是如果。。。那么。。。的规则,根据如果满足什么条件就做一个选择,这样就可以从树的根部分出多个分支出来。我们知道每个分支上面必然有一个终结的位置,从现实中的树看就是叶子,对应到我们的决策树,叶子点就是一个类别标签,即某中分类。这样我们可以看出分类树的任务就是确定对象属于哪一种预定义的目标类
比如预测小明同学今天是否要学习。
是否学习的决策过程解释:
椭圆框内:是决策树的特征(根据特征来分类),比如<女票>;
表情图:是决策树的类别(决策树是用来分类的),比如<学习>;
有向箭头:是决策树特征的属性值,比如<需要>;
这个图中的节点有三类:
根节点:没有进边,只有出边
中间节点:即有进边也有出边,但进边只有一条,出边可以有很多条
叶节点:只有一条进边,没有出边。每个叶子节点都是一个类别标签
父节点和子节点:在两个相连接的节点中,更靠近根节点的父节点,另一个则是子节点,两者相对的。即某个父节点在某个相对关系中是父节点,但在另一个相对关系中可以是子节点。如上图中的<任务>在与<吃鸡>的关系中是父节点,但是在与<女票>的关系中,则是子节点。
可以把决策树看作是-个 If-then规则的集合。将决策树转换成if-then规则的过程是这样的
.由决策树根节点到叶节点的每一条路径构建一条规则。如<女票>到<任务>之间有一条规则,<任务>到叶节点<起来干活了>之间也有一条规则。
.路径上中间节点的特征对应着规则的条件,也就是叶节点的类标签对应着规则的结论。如<任务>特征对应着规则条件中的(无女票),叶节点<起来干活了>对应着结论标签<学习>
决策树的路径或者其对应的if-then规则集合有一个重要的性质:互斥并且完备。也就是说,每一个实例都被有且仅有一条路径或者规则所覆盖。这里的覆盖是指实例的特征与路径上的特征-致.或实例满足规则的条件。如从根<女票>到<起来干活了>有且仅有一条路径可达。
决策树算法构成:
1、决策树的构造
①决策树的特征选择
②决策树的生成
③决策树的剪枝过程
2、决策树的决策(遍历)
使用决策树做分类的时候,我们首先要收集足够多的数据,如果数据收集不充分,将会导致没有足够的特征去构建错误率低的决策树。数据特征充足,但是不知道用哪些特征好,也会导致最终无法构建出分类效果好的决策树。
第一部分①决策树的特征选择,决策树本身是个分类决策做决定的过程,那么对我们做决策有影响(术语叫:信息增益Or信息增益率)的事物就可能作为特征,所以,女票必须是特征;
特征选择就是决定用哪个特征来划分特征空间,其目的在于选取对训练数据具有分类能力的特征,这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大的区别,则称这个特征是没有分类能力的,经验上扔掉这些特征对决策树的学习的精度不会产生太大影响。
那么如何才能选择最优的特征来划分呢?我们希望的是在随着不断地划分,决策树的分支节点所包含的样本尽可能属于同一类别,也就是说节点的纯度越来越高。
如下面三个圆中,第一个圆的纯度最高,第三个圆的纯度最低。
**第二部分②决策树的生成,**当我们完成特征选择后,怎么把这些特征排成一颗树呢?哪个特征应该放在树的顶端Or树的中部Or树的底部呢?按照常识,当然是按照重要性(术语叫:信息增益大小)大小来排布了。而女票是个重要性(术语叫:信息增益)最大,所以排第一没毛病!
**第三部分③决策树的剪枝:**经过②决策树的生成,我们得到的只是一个片面的局部的决策树模型,他只能实现局部最优化,也就是可能在人生的某一时期实现最优化,但不能确保整个人生的最优化。因此为了实现人生损失的最小化(术语叫:损失函数的最小化Or正则化的极大似然估计),就必须进行第三部分的剪枝过程。
其次,根据决策树构成三部分的不同而组成了不同的决策树算法。
决策树本身是个分类过程,当然有着不同的分法,这就像不同的人拥有着不同的价值观,也就对同一事物(女票第一还是吃鸡第一)有着不同的评价。在业界内,一般有着这样三种不同的评价(女票第一还是吃鸡第一)算法。
决策树的算法:
CART算法–1984年
ID3算法–1986年
C4.5算法–1993年(机器学习十大算法之一)
算法的不同具体点说就是决策树组成的三大部分不同,也许是特征选择不同、也许是决策树的生成不同、也许是决策的剪枝不同。
最后,当我们通过决策树的算法构造好了一颗决策树后,我们就可以进行决策了。决策树的决策过程简单点说就是决策树的遍历。
根据以上理论,我们一步一步用代码来实现。
熵
在实际的应用中,我们是用不纯度来进行衡量,常用的度量指标有:熵、增益率、基尼值数
首先,我们需要熟悉信息论中熵的概念。熵度量了事物的不确定性,越不确定的事物,它的熵就越大。具体的,随机变量X的熵的表达式如下:
其中n代表X的n种不同的离散取值。而pi代表了X取值为i的概率,log为以2或者e为底的对数。举个例子,比如X有2个可能的取值,而这两个取值各为1/2时X的熵最大,此时X具有最大的不确定性。值为H(X)=−(1/2log1/2+1/2log1/2)=log2。如果一个值概率大于1/2,另一个值概率小于1/2,则不确定性减少,对应的熵也会减少。比如一个概率1/3,一个概率2/3,则对应熵为H(X)=−(1/3log13+2/3log2/3)=log3−2/3log2<log2).
信息期望
通过上面的公式,我们可以计算所有类别所有可能值包含的信息期望值,得到公式:
用函数实现该公式,代码如下:
def calEnt(dataSet):
n = dataSet.shape[0] #数据集总行数
iset = dataSet.iloc[:,-1].value_counts() #标签的所有类别
p = iset/n #每一类标签所占比
ent = (-p*np.log2(p)).sum() #计算信息熵
return ent
信息增益
信息增益(Information Gain)的计算公式其实就是父节点的信息熵与其下所有子节点总信息熵之差。但这里要注
意的是,此时计算子节点的总信息熵不能简单求和,而要求在求和汇总之前进行修正。
假设离散属性a有V个可能的取值{a1,a2,……,av} ,若使用a对样本数据集D进行划分,则会产生V个分支节点,其中第v个分支节点包含了D中所有在属性a上取值为 av 的样本,记为Dv .我们可根据信息熵的计算公式计算出Dv 的信息熵,再考虑到不同的分支节点所包含的样本数不同,给分支节点赋予权重|Dv|/| Dv| ,这就是所谓的的修正。所以信息增益的计算公式为
以书上的海洋生物数据为例,我们来构建数据集,并计算其香农熵。
调用函数获得信息期望值
#创建数据集
import numpy as np
import pandas as pd
def createDataSet():
row_data = {'no surfacing':[1,1,1,0,0],
'flippers':[1,1,0,1,1],
'fish':['yes','yes','no','no','no']}
dataSet = pd.DataFrame(row_data)
return dataset
dataSet = createDataSet()
calEnt(dataSet)
运行结果:
运行结果熵为:0.9709505944546686
熵越高,信息的不纯度就越高,也就是混合的数据就越多。
那我们手动计算一下,海洋生物数据集中第0列的信息增益:
代码实现如下:
a=(3/5)*(-(2/3)*np.log2(2/3)-(1/3)*np.log2(1/3))
calEnt(dataSet)-a
运行结果为:0.4199730940219749
用同样的方法,我们可以把第1列的信息增益也算出来,结果为0.17
数据集最佳切分函数
划分数据集的最大准则是选择最大信息增益,也就是信息下降最快的方向。
要实现数据集最佳切分函数,可用下列代码实现:
#选择最优的列进行切分
def bestSplit(dataSet):
baseEnt = calEnt(dataSet) #计算原始熵
bestGain = 0 #初始化信息增益
axis = -1 #初始化最佳切分列,标签列
for i in range(dataSet.shape[1]-1): #对特征的每一列进行循环
levels= dataSet.iloc[:,i].value_counts().index #提取出当前列的所有取值
ents = 0 #初始化子节点的信息熵
for j in levels: #对当前列的每一个取值进行循环
childSet = dataSet[dataSet.iloc[:,i]==j] #某一个子节点的dataframe
ent = calEnt(childSet) #计算某一个子节点的信息熵
ents += (childSet.shape[0]/dataSet.shape[0])*ent #计算当前列的信息熵
#print(f'第{i}列的信息熵为{ents}')
infoGain = baseEnt-ents #计算当前列的信息增益
#print(f'第{i}列的信息增益为{infoGain}')
if (infoGain > bestGain):
bestGain = infoGain #选择最大信息增益
axis = i #最大信息增益所在列的索引
return axis
通过上面手动计算,我们知道:
第0列的信息增益为0.42,第1列的信息增益为0.17,0.42>0.17,所以我们应该选择第0列进行切分数据集。
接下来,我们来验证我们构造的数据集最佳切分函数返回的结果与手动计算的结果是否一致。
bestSplit(dataSet) #返回的结果为0,即选择第0列来切分数据集
** 按给定列切分数据集**
通过最佳切分函数返回最佳切分列的索引,我们就可以根据这个索引,构建一个按照给定列切分数据集的函数
def mySplit(dataSet,axis,value):
col = dataSet.columns[axis]
redataSet = dataSet.loc[dataSet[col]==value,:].drop(col,axis=1)
return redataSet
验证函数,以axis=0,value=1为例
value =1
axis=0
mySplit(dataSet,axis,value)
col = dataSet.columns[axis]
dataSet.loc[dataSet[col]==value,:].drop(col,axis=1)
构建决策树
构建函数说明:
函数功能:基于最大信息增益切分数据集,递归构建决策树
参数说明:
dataSet:原始数据集(最后一列是标签)
返回:
myTree:字典形式的树
def createTree(dataSet):
featlist = list(dataSet.columns) #提取出数据集所有的列
classlist = dataSet.iloc[:,-1].value_counts() #获取最后一列类标签
#判断最多标签数目是否等于数据集行数,或者数据集是否只有一列
if classlist[0]==dataSet.shape[0] or dataSet.shape[1] == 1:
return classlist.index[0] #如果是,返回类标签
axis = bestSplit(dataSet) #确定出当前最佳切分列的索引
bestfeat = featlist[axis] #获取该索引对应的特征
myTree = {bestfeat:{}} #采用字典嵌套的方式存储树信息
del featlist[axis] #删除当前特征
valuelist = set(dataSet.iloc[:,axis]) #提取最佳切分列所有属性值
for value in valuelist: #对每一个属性值递归建树
myTree[bestfeat][value] = createTree(mySplit(dataSet,axis,value))
return myTree
查看函数运行结果
myTree = createTree(dataSet)
myTree
输出结果:{‘no surfacing’: {0: ‘no’, 1: {‘flippers’: {0: ‘no’, 1: ‘yes’}}}}
决策树的存储
构造决策树是很耗时的任务,即使处理很小的数据集,也要花费几秒的时间,如果数据集很大,将会耗费很多计算
时间。因此为了节省时间,建好树之后立马将其保存,后续使用直接调用即可。
我这边使用的是numpy里面的save()函数,它可以直接把字典形式的数据保存为.npy文件,调用的时候直接使用load()函数即可。
{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}}
#树的存储
np.save('myTree.npy',myTree)
#树的读取
read_myTree = np.load('myTree.npy').item()
read_myTree
输出结果:{‘no surfacing’: {0: ‘no’, 1: {‘flippers’: {0: ‘no’, 1: ‘yes’}}}}