本篇文章将从决策树模型的原理开始讲起,通过解决如何在夏日中挑选西瓜这一问题来全面掌握。
本文目录:
一:问题导向
夏日炎炎,我们如何使用过去的经验来判断一个西瓜是不是好瓜呢?
我们一般会根据它的 色泽,根蒂,敲声,纹理,触感等等来判断它是不是一个好瓜。
以下是去年父亲买瓜时的信息:
色泽,根蒂,敲声,纹理,脐部,触感,好坏
青绿,蜷缩,浊响,清晰,凹陷,硬滑,好瓜
乌黑,蜷缩,沉闷,清晰,凹陷,硬滑,好瓜
乌黑,蜷缩,浊响,清晰,凹陷,硬滑,好瓜
青绿,蜷缩,沉闷,清晰,凹陷,硬滑,好瓜
浅白,蜷缩,浊响,清晰,凹陷,硬滑,好瓜
青绿,稍蜷,浊响,清晰,稍凹,软粘,好瓜
乌黑,稍蜷,浊响,稍糊,稍凹,软粘,好瓜
乌黑,稍蜷,浊响,清晰,稍凹,硬滑,好瓜
乌黑,稍蜷,沉闷,稍糊,稍凹,硬滑,坏瓜
青绿,硬挺,清脆,清晰,平坦,软粘,坏瓜
浅白,硬挺,清脆,模糊,平坦,硬滑,坏瓜
浅白,蜷缩,浊响,模糊,平坦,软粘,坏瓜
青绿,稍蜷,浊响,稍糊,凹陷,硬滑,坏瓜
浅白,稍蜷,沉闷,稍糊,凹陷,硬滑,坏瓜
乌黑,稍蜷,浊响,清晰,稍凹,软粘,坏瓜
浅白,蜷缩,浊响,模糊,平坦,硬滑,坏瓜
青绿,蜷缩,沉闷,稍糊,稍凹,硬滑,坏瓜
但我们会对这些判断条件有一个优先级,先要挑最重要的那个因素考虑,可能对西瓜而言没有这么明显,但当我们去看医生时,如果我们的症状是腹泻,头疼,脱水,昏厥,那么医生会按照 昏厥 这个 权重最大 的症状先判断生命特征,再按照 *脱水 *去开点滴,再按照 腹泻 判断可能是肠胃炎等等。
树结构大家应该都熟知了,通过根节点开始,通过判断节点信息的是或否决定分支,逐次向下,最后在叶子节点生成最终的决策。
决策树的过程和这个类似,就是先挑“权重”最大的那个考虑,然后再往下细分。
那么我们用一个什么标准来 衡量某个特征是权重最大的呢?
这里有信息增益和基尼系数两个。我们使用ID3算法
ID3算法采用的是信息增益这个量。
二:ID3算法的预备知识
(理论熟练的同学可以跳过这一部分)
2.1 熵
在物理中: 熵是混乱的度量指标。
对于一个数据集而言,它的类别越多,越混乱.
我们引入 “信息熵” 来作为度量样本集合混乱度(纯度)的指标,采用信息增益这个量来作为纯度的度量,选取信息增益最大的特征进行分裂(即作为结点)。
信息熵的计算公式是:
2.2 . 信息增益
构造树的基本想法是随着树深度的增加,节点的熵迅速地降低。熵降低的速度越快越好,这样我们有望得到一棵高度最矮的决策树。
在没有给定西瓜信息时,根据去年数据,我们只知道新的一个瓜好瓜的概率为8/17,坏瓜的概率为9/17.此时的熵为:
属性有六个:色泽,根蒂,敲声,纹理,脐部,触感。我们首先要决定哪个属性作树的根节点。
对每项指标分别统计:在不同的取值下好瓜和坏瓜的个数。
| 色泽 | 根蒂 | 敲声 | 纹理 | 脐部 | 触感 |
|---|---|---|---|---|---|
| 好/坏: | |||||
| 青绿:3/3 | 蜷缩:5/3 | 浊响 :6/4 | … | ||
| 乌黑:4/2 | 稍蜷 :3/4 | 沉闷:2/3 | … | ||
| 浅白:1/4 | 硬挺 :0/2 | 清脆:0/2 | … |
对每个属性计算信息熵:
我们以色泽为例:
由此我们可以计算出色泽属性的信息增益是:
同理,按照其他方法构造出其他属性的信息增益,分别如下:
,
经过比较,我们得出信息增益最大的属性为纹理 于是我们得到第一个划分属性结点(纹理)。
到现在可得出如下初步构建的决策树:
再次根据节点的标签划分三个子结点的集合,这里的3个子集合相当于一个类似总集合X一样的地位。
重复上面找的纹理结点的方法进行递归。
利用信息增益最大的方法来进行特征选择。
不断递归构建整个决策树。
注意:我们这里使用了比较好的数据,但实际应用中我们还需要对决策树进行剪枝来应对参差不齐的数据(剪枝:可以理解为根据测试集消除影响不大的节点)
这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。
过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。
大家可以下思考一下,我们在下一节详细论述并解决上述问题
三:代码实现及结果分析
3.1标注数据:
在编写代码之前,我们先对数据集进行属性标注。
-
色泽:0代表青绿,1代表乌黑,2代表浅白;
-
根蒂:0代表蜷缩,1代表稍蜷,2代表硬挺;
-
敲声:0代表清脆,1代表浊响,2代表沉闷;
-
脐部:0代表凹陷,1代表稍凹,2代表平坦;
-
纹理:0代表稍糊,1代表清晰
-
触感:0代表硬滑,1代表软粘
-
好坏:no代表坏瓜,yes代表好瓜。
3.2 计算信息熵(经验熵)
确定这些之后,我们就可以创建数据集,并计算经验熵了,代码编写如下:
from math import log
#训练数据集
def createDataSet():
dataSet = [[0,0,1,1,0,0,'yes'],
[1,0,2,1,0,0,'yes'],
[1,0,1,1,0,0,'yes'],
[0,0,2,1,0,0,'yes'],
[2,0,1,1,0,0,'yes'],
[0,1,1,1,1,1,'yes'],
[1,1,1,0,1,1,'yes'],
[1,1,1,1,1,0,'yes'],
[1,1,2,0,1,0,'no'],
[0,2,0,1,2,1,'no'],
[2,2,0,0,2,0,'no'],
[2,0,1,0,2,1,'no'],
[0,1,1,0,0,0,'no'],
[2,1,2,0,0,0,'no'],
[1,1,1,1,1,1,'no'],
[2,0,1,0,2,0,'no'],
[0,0,2,0,1,0,'no']]
labels = ['色泽','根蒂','敲声','纹理','脐部','触感','好坏']#分类属性
return dataSet, labels #返回数据集和分类属性
计算给定数据集的经验熵(信息熵)
def calMessageEnt(dataSet):
numEntires = len(dataSet) #返回数据集的行数
labelCounts = {} #保存每个标签出现次数的次数
#对每组特征向量就进行统计
for featVec in dataSet:
currentLabel = featVec[-1] #提取标签的信息
#如果标签不在统计里面
if (currentLabel not in labelCounts.keys()):
labelCounts[currentLabel] = 0
#如果在,计数
labelCounts[currentLabel]+=1
#信息熵
MessageEnt = 0.0
#计算信息熵
for key in labelCounts:
#选择该标签(Label)的概率
prob = float(labelCounts[key])/numEntires
#利用公式计算
MessageEnt -= prob*log(prob,2)
#返回信息熵
return MessageEnt
if __name__ =='__main__':
dataSet,features =createDataSet()
print(dataSet)
print(calMessageEnt(dataSet))
结果如下:信息熵和我们上文算的一样
注意:在公式中,是把所有的事件代入公式求和得出信息熵 ,所以使用累加的形式
一般写+=习惯,但对于这个公式是-= ,一定要注意,否则只算以此,而不能循环
利用公式计算
MessageEnt -= prob*log(prob,2)
因为涉及到大量的公式,一定要多输出计算的数值和我们手动算的进行对比,确保无误在向下进行
3.3:计算信息增益
#按照给定特征划分 数据集
# dataSet - 待划分的数据集
# axis - 划分数据集的特征
# value - 需要返回的特征的值
def splitDataSet(dataSet,axis,value):
#创建返回的数据集列表
retDataSet = []
#遍历数据集
for featVec in dataSet:
#如果划分的等于那个特征
if featVec[axis] == value:
#去掉axis特征
reducedFeatVec = featVec[:axis]
#将符合条件的添加到返回的数据集
reducedFeatVec.extend(featVec[axis+1:])
retDataSet.append(reducedFeatVec)
#返回划分后的数据集
return retDataSet
#选择最优特征
# dataSet - 数据集
# bestFeature - 信息增益最大的(最优)特征的索引值
# bestInFoGain 信息增益
def chooseBestFeatureToSplit(dataSet):
#特征的数量
numFeatures = len(dataSet[0])-1
#计算数据集的信息熵
baseEntropy = calMessageEnt(dataSet)
print('数据集的信息熵',baseEntropy)
#信息增益
bestInfoGain =0.0
#最优特 征索引值
bestFeature = -1
#遍历所有特征
for i in range(numFeatures):
#获取dataSet的第i个所有特征
featList = [example[i] for example in dataSet]
#创建SET集合,元素不可重复
uniqueVals =set(featList)
newEntropy = 0.0 #经验条件熵
#计算信息增益
for value in uniqueVals:
#划分子集
subDataSet = splitDataSet(dataSet,i,value)
#计算子集的概率
prob = len(subDataSet)/float(len(dataSet))
print('第',i,'个特征,第',value,'个子集的概率',prob)
#根据公式计算经验条件熵
newEntropy += prob*calMessageEnt(subDataSet)
print('第',i,'特征子集信息熵',calMessageEnt(subDataSet))
print('第',i,'个特征的经验条件熵',newEntropy)
#信息增益
infoGain = baseEntropy - newEntropy
#打印每个特征的信息增益
print("第%d个特征的增益为%.3f" % (i, infoGain))
#计算最优特征的信息增益
if (infoGain > bestInfoGain):
#更新信息增益,找到最大的信息增益
bestInfoGain = infoGain
#记录信息增益最大的特征的索引值
bestFeature = i
#返回信息增益最大的特征的索引值
return bestFeature
if __name__ == '__main__':
dataSet, features = createDataSet()
print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))
由下图可知,色泽特征的信息熵与信息增益,计算过程无误
且由结果获知:最优特征为 纹理
四:决策树生成和修剪
刚才已经学习了从数据集构造决策树算法所需要的子功能模块,包括信息熵的计算和最优特征的选择,
简单回顾:
- 标注原始数据集,然后基于最好的属性值划分数据集,
- 第一次划分之后,数据集被向下传递到树的分支的下一个结点。
- 在这个结点上,根据信息增益,我们可以再次划分数据。
- 采用递归的原则处理数据,构建完整的树。
决策树生成算法递归地产生决策树,直到不能继续下去未为止。这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。
过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。
解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。
我们在下一篇文章中着重论述这个问题~~