目录

一、决策树的概述

1.1 决策树的概念

1.2 决策树分类举例

1.3 决策树的步骤

1.4 决策树的优缺点

二、决策树的构造

2.1 决策树的一般流程

2.2 信息增益

2.3 信息增益率

2.4 基尼指数

2.5 划分数据集

2.5 递归构建决策树

三、在 Python 中使用 Matplotlib 注解绘制树形图

3.1 Matplotlib 注解

3.2 构造注解树

四、测试和存储分类器

4.1 测试算法:使用决策树执行分类

4.2 实用算法:决策树的存储

4.3 算法结果:准确率

五、决策树的三种实现算法

5.1 ID3算法

5.2 C4.5算法

5.3 CART算法

六、决策树实验分析与总结

6.1 三种决策树算法比较

6.2 实验总结


一、决策树的概述

1.1 决策树的概念

        决策树(decision tree)是一种基本的分类与回归方法。决策树模型呈树形结构,在分类问题中,表示基于特征对实例进行分类的过程。它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

        决策树是一种描述对实例进行分类的树形结构,其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果,本质是一颗由多个判断节点组成的树。分类决策树模型是一种树形结构。 决策树由结点和有向边组成。结点有两种类型:内部结点和叶节点。内部结点表示一个特征或属性,叶节点表示一个类。

1.2 决策树分类举例

机器学习上两篇博客是讲k-近邻算法的分类任务,但是其最大的缺点是无法给出数据的内在含义,决策树的优势在于数据形式非常容易理解。

决策树分类的思想类似于我们找对象,嘿嘿(*^▽^*)。现想象一个女孩的母亲要给这个女孩介绍男朋友,于是有了下面的对话:

女儿:多大年纪了?                       (年龄)

母亲:26。

女儿:长的帅不帅?                       (长相)

母亲:挺帅的。

女儿:收入高不?                           (收入情况)

母亲:不算很高,中等情况。

女儿:是公务员不?                        (是否公务员)

母亲:是,在税务局上班呢。

女儿:那好,我去见见。

根据上面的对话,如下画出找对象的决策树,可以看到其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果。

从上面的对话中,可以看出这位女儿对于找对象的决策过程,她从年龄、长相、收入情况以及是不是公务员这四个方面就行决策选择的。决策过程中提出的每个判定问题都是对某个属性的“测试”,例如“年龄=?”“长相=?”;每个测试的结果或是导出最终结论,或是导出进一步的判定问题,其考虑范围是在上次决策结果的限定范围之内,例如若在“年龄=26,长相=帅”之后再判断“收入=?”,则仅在考虑年龄合适长得帅的对象。

决策过程中提出的每个判定问题都是对某个属性的“测试” ,每个测试的结果或是导出最终结论,或者导出进一步的判定问 题,其考虑范围是在上次决策结果的限定范围之内, 从根结点到每个叶结点的路径对应了一个判定测试序列。

决策树学习的目的是为了产生一棵泛化能力强, 即处理未见示例能力强的决策树。

1.3 决策树的步骤

决策树通常有三个步骤:特征选择决策树的生成决策树的修剪

  • 特征选择:从训练数据的特征中选择一个特征作为当前节点的分裂标准(特征选择的标准不同产生了不同的特征决策树算法)。
  • 决策树生成:根据所选特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止声场。
  • 决策树剪枝:决策树容易过拟合,需要剪枝来缩小树的结构和规模(包括预剪枝和后剪枝)。

决策树学习的本质是从训练数据集中归纳出一组分类规则或者说是条件概率模型,与训练数据集不相矛盾的决策树可能有多个或者一个没有,我们需要找到一个与训练数据集矛盾较小的决策树,同时具有很好的泛化能力。换句话说,我们选择的条件概率模型应该不仅对现有的训练数据集有很好的拟合效果,而且能够对未知的数据有很好的预测(泛化能力)。实现的方法通过以上的三个方法。

1.4 决策树的优缺点

优点:

  • 计算复杂度不高,输出结果易理解,对中间值缺失不敏感,可以处理不相关特征数据。
  • 准确性高: 挖掘出来的分类规则准确性高, 便于理解, 决策树可以清晰的显示哪些字段比较重要, 即可以生成可以理解的规则。
  • 可以处理连续和离散字段、不需要任何领域知识和参数假设、适合高维数据。

缺点:

  • 对于各类别样本数量不一致的数据, 信息增益偏向于那些更多数值的特征。
  • 容易过拟合、忽略属性之间的相关性。

适用数据类型:数值型和标称型

二、决策树的构造

2.1 决策树的一般流程

(1) 收集数据:可以使用任何方法。
(2) 准备数据:树构造算法只是用于标称型数据,因此数值型数据必须离散化。
(3) 分析数据:可以使用任何方法,决策树构造完成后,可以检查决策树图形是否符合预期。
(4) 训练算法:构造一个决策树的数据结构。
(5) 测试算法:使用经验树计算错误率。当错误率达到可接收范围,此决策树就可投放使用。
(6) 使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

在构造决策树之前,我们先来看看若一个男生找对象相亲,那么他对白富美三个属性的决策是怎么样子的;下图是不同属性为根结点,其他结点属性次序不同生成的决策树:

从上面的图中我们可以看出,前面五个决策树的规模比最后一个决策树大得多,这说明特征选择会影响决策树的形状及规模大小,也会影响决策的快慢,从上面六个决策树里可以观察到,富是起主要作用的特征属性,再者是白,然后再到美,所以对于结点的特征选取(即也是属性划分)是很重要的。

决策树学习的关键在于如何选择最优划分属性。一般而言,随着划分过程不断进行,我们希望决策树的分支结点所包含的样本尽可能属于同一类别,即结点的“纯度 ”(purity)越来越高。

经典的属性划分方法:

  • 信息增益: ID3
  • 信息增益率:C4.5
  • 基尼指数:CAR

2.2 信息增益

信息熵

信息熵是度量样本集合纯度最常用的一种指标,假定当前样本集合D中第k类样本所占的比例为 pk (K=1, 2, ..., |y|) ,则D的信息熵定义为:

  • Ent(D)的值越小,则D的纯度越高
  • 计算信息熵时约定:若p = 0,则plog2p=0 , Ent(D)的最小值为0,最大值为log2 |y|;
  • 当随机变量只有两个值,例如1, 0时,即X的分布为 P(X=1)=p , P(X=0)=1-p , 0<=p<=1. 则熵可知,当p=0或p=1时,H(p)=0,随机变 量完全没有不确定性。
  • 熵H(p)随概率p变化的曲线如下图:

  • 可知,当p=0或p=1时,H(p)=0,随机变 量完全没有不确定性
# 计算给定数据集的熵
def calcShannonEnt(dataSet):numEntries = len(dataSet)                          #获得数据集的行数labelCounts = {}                                   #用于保存每个标签出现次数的字典for featVec in dataSet:currentLabel = featVec[-1]                     #提取标签信息if currentLabel not in labelCounts.keys():     #如果标签未放入统计次数的字典,则添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                 #标签计数shannonEnt = 0.0                                   #熵初始化for key in labelCounts:prob = float(labelCounts[key])/numEntries      #选择该标签的概率shannonEnt -= prob*log(prob, 2)                #以2为底求对数return shannonEnt                                  #返回经验熵

信息增益

离散属性a有V个可能的取值{ , , ...,  },用a来进行划分,则会产生V个分支结点,其中第v个分支结点包含了D中所有在属性a上取值为 的样本,记为 。则可计算出用属性a对样本集D进行划分所获得的信息增益:

  • 一般而言,信息增益越大,则意味着使用属性a来进行划分所获得的 “纯度提升”越大。
  • ID3(Iterative Dichotomiser,迭代二分器)决策树学习算法[Quinlan, 1986] 以信息增益为准则来选择划分属性

信息增益实例

夏日炎炎,我们如何使用过去的经验来判断一个西瓜是不是好瓜呢?我们一般会根据它的色泽,根蒂,敲声,纹理,触感等等来判断它是不是一个好瓜。

该数据集包含 17个训练样本,结果有两种,一是好瓜,二是坏瓜,所以 , 其中正例占 , 反例占 ,计算得到根结点的信息熵为:

以属性“色泽”为例,其对应的3个数据子集分别为(色泽= 青绿),(色泽=乌黑), (色泽=浅白)

子集包含编号为{1, 4, 6, 10, 13, 17} 的6个样例,其中正例占 ,反例占 的信息熵为:

同理的信息熵为:

属性“色泽”的信息增益为:

类似的,我们可以计算出其他属性的信息增益:

显然,属性“纹理”的信息增益最大,其被选为划分属性,下图给出了根据“纹理”属性划分之后的数据子集:

对每一个数据子集按照上面步骤继续划分下去得到最终的决策树,需要注意的是每次样例子集中的属性不包含父结点中划分所依赖的属性,如下图所示:

2.3 信息增益率

采用信息增益来进行划分属性的决策有一个潜在的问题,当某一个属性的取值种类非常多时,对应每一个属性取值的样本子集,其分类的信息熵可能会变得很小。为了说明,采用一种极端情况,假设我们对上面中要分类的西瓜数据进行决策树生成时,把“编号”也当作一种可以作为划分依据的属性。则在这种情况下,每一个编号属性对应一个实例,且其分类是确定的,那么对于每一个“编号”属性取值来说,其分类信息熵为 0,计算“编号”分类所带来的信息增益为:

为了减少这种偏好带来的不利影响,著名的C4.5决策树算法不直接使用信息增益,而使用增益率来选择最优划分属性。

信息增益率定义为:

其中

称为属性a的“固有值”(intrinsic value) [Quinlan, 1993]. 属性a的可能取值数目越多(即V越大),则IV(a) 的值通常会越大。

实例计算信息增益率

对西瓜数据,触感、色泽、编号:                

我们注意到,信息增益率对属性值较少的属性比较偏好,因此,C4.5算法并不是直接选择增益率最大的作为候选划分属性,而是使用一个启发式:先从候选划分属性中找出信息增益高于平均水平的属性(保证属性值多的属性被选择),再从中选择增益率最高的(在属性值多的属性中挑属性值少的属性),这样是保证了相对准确。

2.4 基尼指数

基尼系数比信息熵要简单很多,基尼系数的计算公式如下所示:

其中,k是数据集中样本类型数量;是第 i 类样本的数量占总样本数量的比例。

基尼系数的性质与信息熵一样:度量随机变量的不确定度的大小

  • G 越大,数据的不确定性越高;
  • G 越小,数据的不确定性越低;
  • G = 0,数据集中的所有样本都是同一类别;

直观上的理解为,Gini(D)反映了从数据集D中随机抽取两个样本,其类别标记不一致的概率。因此,Gini(D)越小,则数据集D纯度越高。于是产生了基尼指数(Gini index):

实例计算基尼指数

同样对于上面的对西瓜数据来计算色泽的基尼指数:

①求基尼值,青绿、乌黑、浅白

②求基尼指数

2.5 划分数据集

既然前面,我们已经讨论出了三种划分方式(信息增益、信息增益率、基尼指数),现在我们就开始划分数据,下面我以信息增益的示例来划分属性:

# 按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):                  #dataSet:带划分数据集   axis:划分数据集的特征   value:需要返回的特征的值retDataSet=[]                                      #创建新的list对象for featVec in dataSet:                            #遍历元素if featVec[axis] == value:                     #符合条件的,抽取出来reducedFeatVec = featVec[:axis]reducedFeatVec.extend(featVec[axis+1:])retDataSet.append(reducedFeatVec)return retDataSet

 知识点总结:

python中append()与extend()的区别

1、append():用于在列表末尾添加新的对象,列表只占一个索引位,在原有列表上增加

①语法:list.append()

②参数:向列表中添加一个对象,即添加到列表末尾的对象,任意对象都是可以的,直接将整个参数放入列表末尾。

③返回值:无返回值,但是会修改原来的列表。

2、extend():向列表尾部加一个列表,列表中的每个元素都追加进来,在原有列表上增加

①语法:list.extend()

②参数:把一个序列参数的内容添加到列表中,即元素列表,对象必须是一个可以迭代的序列,将参数打散后依次放入列表末尾。
③返回值:无返回值,但会在已存在的列表中添加新的列表内容。

# 选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1       #特征数量baseEntropy = calcShannonEnt(dataSet)   #计数数据集的香农熵bestInfoGain = 0.0                      #信息增益bestFeature = -1                        #最优特征的索引值for i in range(numFeatures):            #遍历数据集的所有特征featList = [example[i] for example in dataSet]          #获取dataSet的第i个所有特征uniqueVals = set(featList)                              #创建set集合{},元素不可重复newEntropy = 0.0                                        #信息熵for value in uniqueVals:                                #循环特征的值subDataSet = splitDataSet(dataSet, i, value)        #subDataSet划分后的子集prob = len(subDataSet) / float(len(dataSet))        #计算子集的概率newEntropy += prob * calcShannonEnt((subDataSet)) infoGain = baseEntropy - newEntropy                     #计算信息增益print("第%d个特征的信息增益为%.3f" % (i, infoGain))      #打印每个特征的信息增益if (infoGain > bestInfoGain):                           #计算信息增益bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益bestFeature = i                                     #记录信息增益最大的特征的索引值return bestFeature                                          #返回信息增益最大特征的索引值

2.5 递归构建决策树

伪代码:

显然,决策树的生成是一个递归过程,在决策树基本算法中,有三种情形会导致递归返回:

①当前结点包含的样本全部属于同一类别C:

无需划分,叶子节点标记为类别C

②当前属性集为空,或所有样本在所有属性上取值相同:

当前结点标记为叶子节点,类别=该结点所含样本最多的类别

③当前结点包含的样本集合为空:

当前结点标记为叶子节点,类别=该结点的父节点所含样本最多的类别

代码实现:

# 统计出现次数最多的元素(类标签)
def majorityCnt(classList):classCount={}  #统计classList中每个类标签出现的次数for vote in classList:if vote not in classCount.keys():classCount[vote] = 0classCount[vote] += 1sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)    #根据字典的值降序排列return sortedClassCount[0][0]  #返回出现次数最多的类标签
# 创建决策树
def createTree(dataSet,labels):classList = [example[-1] for example in dataSet]           #取分类标签if classList.count(classList[0]) == len(classList):        #如果类别完全相同,则停止继续划分return classList[0]if len(dataSet[0]) == 1:                                   #遍历完所有特征时返回出现次数最多的类标签return majorityCnt(classList)bestFeat = chooseBestFeatureToSplit(dataSet)               #选择最优特征bestFeatLabel = labels[bestFeat]                           #最优特征的标签myTree = {bestFeatLabel:{}}                                #根据最优特征的标签生成树del(labels[bestFeat])                                      #删除已经使用的特征标签featValues = [example[bestFeat] for example in dataSet]    #得到训练集中所有最优特征的属性值uniqueVals = set(featValues)                               #去掉重复的属性值for value in uniqueVals:                                   #遍历特征,创建决策树subLabels = labels[:]                                  #复制所有标签,这样树就不会弄乱现有标签myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)return myTree

小结:到这就可以捋清楚决策树从无到有的过程,首先在收集好数据的前提下,通过数据集选择最优特征值来划分数据集,然后根据划分后得数据集一层一层递归生成决策树。一开始老师讲解这个课的时候,不太能想象的用python怎么实现树的构造,学习到后面才了解是用字典结构,花了有点时间去学习用字典来表示树。

三、在 Python 中使用 Matplotlib 注解绘制树形图

现在我们已经能基本懂得来决策树的构造了,进一步拓展,实现决策树的可视化,同样还是和KNN算法一样用Matplotlib来实现可视化。

3.1 Matplotlib 注解

首先,我们绘制决策树,那肯定要先绘制节点以及箭头:

# 使用文本注解绘制树节点
import matplotlib.pyplot as plt# 定义文本框和箭头格式
decisionNode = dict(boxstyle="square", fc="0.8")  #boxstyle文本框样式、fc=”0.8” 是颜色深度
leafNode = dict(boxstyle="round4", fc="0.8")      #叶子节点
arrow_args = dict(arrowstyle="<-")                #定义箭头# 绘制带箭头的注解
def plotNode(nodeTxt, centerPt, parentPt, nodeType):createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)

语句解析:

plt.annotate(str, xy=data_point_position, xytext=annotate_position, 
                   va="center",  ha="center", xycoords="axes fraction", 
                   textcoords="axes fraction",bbox=annotate_box_type,arrowprops=arrow_style)

str是给数据点添加注释的内容,如字符串
xy是要添加注释的数据点的位置
xytext是注释内容的位置
bbox是注释框的风格和颜色深度,fc越小,注释框的颜色越深,支持输入一个字典
va="center",  ha="center"表示注释的坐标以注释框的正中心为准,而不是注释框的左下角(v代表垂直方向,h代表水平方向)
xycoordstextcoords可以指定数据点的坐标系和注释内容的坐标系,通常只需指定xycoords即可,textcoords默认和xycoords相同
arrowprops可以指定箭头的风格支持,输入一个字典

 测试:

# 测试生成节点和箭头
fig = plt.figure(1, facecolor='white')
fig.clf()
font = {'family': 'MicroSoft YaHei'}
matplotlib.rc("font", **font)
createPlot.ax1 = plt.subplot(111, frameon=False)
plotNode('决策节点', (0.6, 0.1), (0.1, 0.5), decisionNode)
plotNode('叶子节点', (0.8, 0.1), (0.3, 0.8), leafNode)
plt.show()

测试结果:

3.2 构造注解树

 Matplotlib 注解绘制树形图完整代码:

import matplotlib
import matplotlib.pyplot as plt# 定义文本框和箭头格式
decisionNode = dict(boxstyle="square", fc="0.8")  #boxstyle文本框样式、fc=”0.8” 是颜色深度
leafNode = dict(boxstyle="round4", fc="0.8")      #叶子节点
arrow_args = dict(arrowstyle="<-")                #定义箭头# 绘制带箭头的注解
def plotNode(nodeTxt, centerPt, parentPt, nodeType):#createPlot.ax1是表示: ax1是函数createPlot的一个属性createPlot.ax1.annotate(nodeTxt, xy=parentPt, xycoords='axes fraction',xytext=centerPt,textcoords='axes fraction',va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)# 获取叶节点的数目和树的层数
def getNumLeafs(myTree):numLeafs = 0                                      # 初始化firstStr = list(myTree.keys())[0]                 # 获得第一个key值(根节点)secondDict = myTree[firstStr]                     # 获得value值for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':  # 测试节点的数据类型是否为字典numLeafs += getNumLeafs(secondDict[key])  # 递归调用else:numLeafs += 1return numLeafs# 获取树的深度
def getTreeDepth(myTree):maxDepth = 0                                           # 初始化firstStr = list(myTree.keys())[0]                      # 获得第一个key值(根节点)secondDict = myTree[firstStr]                          # 获得value值for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':       # 测试节点的数据类型是否为字典thisDepth = 1 + getTreeDepth(secondDict[key])  # 递归调用else:thisDepth = 1if thisDepth > maxDepth:maxDepth = thisDepthreturn maxDepth# 决策树存储信息
def retrieveTree(i):listOfTrees = [{'有自己的房子': {0: {'有工作': {0: 'no', 1: {'年龄': {0: {'信贷情况': {'no': 'no', 'yes': 'yes'}}, 2: 'yes'}}}}, 1: {'年龄': {0: {'信贷情况': {'no': 'no', 'yes': 'yes'}}, 1: 'yes', 2: 'yes'}}}}]return listOfTrees[i]# 在父子节点间填充文本信息
def plotMidText(cntrPt, parentPt, txtString):xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)# 画树
def plotTree(myTree, parentPt, nodeTxt):numLeafs = getNumLeafs(myTree)        # 获取树高depth = getTreeDepth(myTree)          # 获取树深度firstStr = list(myTree.keys())[0]     # 这个节点的文本标签cntrPt = (plotTree.xOff + (1.0 + float(numLeafs)) / 2.0 / plotTree.totalW, plotTree.yOff) #plotTree.totalW, plotTree.yOff全局变量,追踪已经绘制的节点,以及放置下一个节点的恰当位置plotMidText(cntrPt, parentPt, nodeTxt)                #标记子节点属性plotNode(firstStr, cntrPt, parentPt, decisionNode)secondDict = myTree[firstStr]plotTree.yOff = plotTree.yOff - 1.0 / plotTree.totalD  #减少y偏移for key in secondDict.keys():if type(secondDict[key]).__name__ == 'dict':plotTree(secondDict[key], cntrPt, str(key))else:plotTree.xOff = plotTree.xOff + 1.0 / plotTree.totalWplotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))plotTree.yOff = plotTree.yOff + 1.0 / plotTree.totalD# 绘制决策树
def createPlot(inTree):fig = plt.figure(1, facecolor='white')                          # 创建一个新图形fig.clf()                                                       # 清空绘图区font = {'family': 'MicroSoft YaHei'}matplotlib.rc("font", **font)axprops = dict(xticks=[], yticks=[])createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)plotTree.totalW = float(getNumLeafs(inTree))plotTree.totalD = float(getTreeDepth(inTree))plotTree.xOff = -0.5 / plotTree.totalW;plotTree.yOff = 1.0;plotTree(inTree, (0.5, 1.0), '')plt.show()

  测试:

# 测试结果
myTree = retrieveTree(0);
createPlot(myTree)

四、测试和存储分类器

4.1 测试算法:使用决策树执行分类

# 使用决策树进行分类
def classify(inputTree, featLabels, testVec):firstStr = list(inputTree.keys())[0]                          #根节点secondDict = inputTree[firstStr]                              #获得value值featIndex = featLabels.index(firstStr)                        #特征值下标key = testVec[featIndex]valueOfFeat = secondDict[key]if isinstance(valueOfFeat, dict):classLabel = classify(valueOfFeat, featLabels, testVec)   # 递归调用else:classLabel = valueOfFeatreturn classLabel

4.2 实用算法:决策树的存储

# 存储
def storeTree(inputTree,filename):import picklefw = open(filename,'w')pickle.dump(inputTree,fw)fw.close()# 读取
def grabTree(filename):import picklefr = open(filename)return pickle.load(fr)

4.3 算法结果:准确率

count = 0                    #初始化,正确数目为0
numTest = len(testDate)      #测试样本数
for test in testDate:        #遍历测试每一个数集result = classify(myTree, labels, test[0:4])if(result == test[-1]):print(result)count += 1
print("准确率为:%f"%(count/numTest))

五、决策树的三种实现算法

5.1 ID3算法

(1)代码实现

上面的讲解,已经使用信息增益来划分属性的,即使用的是ID3算法,完整代码如下:

# 计算给定数据集的熵
def calcShannonEnt(dataSet):numEntries = len(dataSet)                          #获得数据集的行数labelCounts = {}                                   #用于保存每个标签出现次数的字典for featVec in dataSet:currentLabel = featVec[-1]                     #提取标签信息if currentLabel not in labelCounts.keys():     #如果标签未放入统计次数的字典,则添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                 #标签计数shannonEnt = 0.0                                   #熵初始化for key in labelCounts:prob = float(labelCounts[key])/numEntries      #选择该标签的概率shannonEnt -= prob*log(prob, 2)                #以2为底求对数return shannonEnt                                  #返回经验熵# 按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):                  #dataSet:带划分数据集   axis:划分数据集的特征   value:需要返回的特征的值retDataSet=[]                                      #创建新的list对象for featVec in dataSet:                            #遍历元素if featVec[axis] == value:                     #符合条件的,抽取出来reducedFeatVec = featVec[:axis]reducedFeatVec.extend(featVec[axis+1:])retDataSet.append(reducedFeatVec)return retDataSet# 选择最好的数据集划分方式 ID3算法——信息增益
def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1       #特征数量baseEntropy = calcShannonEnt(dataSet)   #计数数据集的香农熵bestInfoGain = 0.0                      #信息增益bestFeature = -1                        #最优特征的索引值for i in range(numFeatures):            #遍历数据集的所有特征featList = [example[i] for example in dataSet]          #获取dataSet的第i个所有特征uniqueVals = set(featList)                              #创建set集合{},元素不可重复newEntropy = 0.0                                        #信息熵for value in uniqueVals:                                #循环特征的值subDataSet = splitDataSet(dataSet, i, value)        #subDataSet划分后的子集prob = len(subDataSet) / float(len(dataSet))        #计算子集的概率newEntropy += prob * calcShannonEnt((subDataSet))infoGain = baseEntropy - newEntropy                     #计算信息增益print("第%d个特征的信息增益为%.3f" % (i, infoGain))      #打印每个特征的信息增益if (infoGain > bestInfoGain):                           #计算信息增益bestInfoGain = infoGain                             #更新信息增益,找到最大的信息增益bestFeature = i                                     #记录信息增益最大的特征的索引值return bestFeature                                          #返回信息增益最大特征的索引值# 统计出现次数最多的元素(类标签)
def majorityCnt(classList):classCount={}  #统计classList中每个类标签出现的次数for vote in classList:if vote not in classCount.keys():classCount[vote] = 0classCount[vote] += 1sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)    #根据字典的值降序排列return sortedClassCount[0][0]  #返回出现次数最多的类标签# 创建决策树
def createTree(dataSet,labels):classList = [example[-1] for example in dataSet]           #取分类标签if classList.count(classList[0]) == len(classList):        #如果类别完全相同,则停止继续划分return classList[0]if len(dataSet[0]) == 1:                                   #遍历完所有特征时返回出现次数最多的类标签return majorityCnt(classList)bestFeat = chooseBestFeatureToSplit(dataSet)               #选择最优特征bestFeatLabel = labels[bestFeat]                           #最优特征的标签myTree = {bestFeatLabel:{}}                                #根据最优特征的标签生成树del(labels[bestFeat])                                      #删除已经使用的特征标签featValues = [example[bestFeat] for example in dataSet]    #得到训练集中所有最优特征的属性值uniqueVals = set(featValues)                               #去掉重复的属性值for value in uniqueVals:                                   #遍历特征,创建决策树subLabels = labels[:]                                  #复制所有标签,这样树就不会弄乱现有标签myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)return myTree

(2)结果测试:

#ID3算法 生成的决策树
{'有自己的房子': {0: {'有工作': {0: 'no', 1: {'年龄': {0: {'信贷情况': {'no': 'no', 'yes': 'yes'}}, 2: 'yes'}}}}, 1: {'年龄': {0: {'信贷情况': {'no': 'no', 'yes': 'yes'}}, 1: 'yes', 2: 'yes'}}}}

(3)准确率

用信息增益来划分属性,ID3算法的准确率为83.3%

5.2 C4.5算法

(1)代码实现

C4.5使用信息增益比作为特征选择的度量,与ID3相比较,没有差多少,就是把信息增益改成信息增益率,即在ID3算法基础上,根据信息增益的公式修改 chooseBestFeatureToSplit() 函数

#选择最好的数据集划分方式 C4.5算法——信息增益率
def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                        #特征数量baseEntropy = calcShannonEnt(dataSet)                    #获取信息熵bestinfoGainratio = 0.0                                  #信息增益率初始化为0,bestFeature = -1                                         #最优的划分特征,初始化为-1for i in range(numFeatures):                             #遍历所有的特征featList = [example[i] for example in dataSet]       #创建list用来存每个样本在第i维度的特征值uniqueVals = set(featList)                           #获取该特征下的所有不同的值newEntropy = 0.0                                     #初始化信息熵IV = 0.0for value in uniqueVals:                             #遍历该特征维度下对应的所有特征值subDataSet = splitDataSet(dataSet, i, value)     #划分数据集prob = len(subDataSet)/float(len(dataSet))newEntropy += prob * calcShannonEnt(subDataSet)IV -= prob * log(prob,2)                         #计算信息增益率infoGain = baseEntropy - newEntropy                  #计算信息增益if IV == 0.0:infoGainratio = 0.0else:infoGainratio = float(infoGain) / float(IV)if (infoGainratio > bestinfoGainratio):bestinfoGainratio = infoGainratio                #找到最大的信息增益率bestFeature = i                                  #记录信息增益率最大的索引值return bestFeature

(2)结果测试:

#C4.5算法 生成的决策树
{'有自己的房子': {0: {'有工作': {0: 'no', 1: {'年龄': {0: {'信贷情况': {'yes': 'yes', 'no': 'no'}}, 2: 'yes'}}}}, 1: {'有工作': {0: 'yes', 1: {'年龄': {0: {'信贷情况': {'yes': 'yes', 'no': 'no'}}, 1: 'yes'}}}}}}

 (3)准确率

用信息增益率来划分属性,C4.5算法的准确率为83.3%,与ID3算法求得的准确率一样

5.3 CART算法

(1)代码实现

若用基尼指数来划分属性,相较于ID3算法和C4.5算法,基尼指数不需要计算信息熵,方便许多,即在ID3算法基础上,修改 chooseBestFeatureToSplit() 函数,并且还要添加一个每个属性的计算概率的函数calcProbabilityEnt()函数:

# 计算概率
def calcProbabilityEnt(dataSet):numEntries = len(dataSet)                # 数据集大小feaCounts = 0                            # 特征数量feature = dataSet[0][len(dataSet[0]) - 1]for feaVec in dataSet:if feaVec[-1] == feature:feaCounts += 1probabilityEnt = float(feaCounts) / numEntries    #概率= 特征数量/数据集大小return probabilityEnt# 选择最好的数据集划分方式 CAR算法——基尼指数
def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                     #特征数量if numFeatures == 1:return 0bestGini = 1                                          #最佳基尼指数bestFeature = -1                                      #最优的划分特征,初始化为-1for i in range(numFeatures):  # 遍历所有的特征featList = [example[i] for example in dataSet]feaGini = 0  # 定义特征的值的基尼系数uniqueVals = set(featList)                        #获取该特征下的所有不同的值for value in uniqueVals:subDataSet = splitDataSet(dataSet, i, value)  #划分数据集prob = len(subDataSet) / float(len(dataSet))probabilityEnt = calcProbabilityEnt(subDataSet)feaGini += prob * (2 * probabilityEnt * (1 - probabilityEnt))if (feaGini < bestGini):bestGini = feaGini                            #基尼指数bestFeature = i                               #记录基尼指数最小的索引值return bestFeature

(2)结果测试:

#CAR算法 生成的决策树
{'有自己的房子': {0: {'有工作': {0: 'no', 1: {'年龄': {0: {'信贷情况': {1: 'yes'}}, 2: 'yes'}}}}, 1: {'年龄': {0: {'有工作': {1: {'信贷情况': {0: 'yes'}}}}, 1: 'yes', 2: 'yes'}}}}

(3)准确率

用基尼指数来划分属性,CAR算法的准确率为93.3%,在这三个算法中这个准确率是最高的了!

六、决策树实验分析与总结

6.1 三种决策树算法比较

ID3算法:

  • ID3算法核心就是“最大信息熵增益” 原则选择划分当前数据集的最好特征。
  • 而且对于连续型特征,比如长度,密度都是连续值,无法在ID3运用,因为一开始我用的鸢尾花实例数据,ID3不能判断连续型量。
  • 前面也说到了,利用信息熵划分属性,会对倾向于可取值数目较多的属性。
  • 没有考虑过拟合的问题。

C4.5算法:

  • C4.5算法流程与ID3算法相类似,只不过将信息增益改为信息增益率,以解决偏向取值较多的属性的问题。
  • 可以处理属性变量为连续型的,将连续的特征离散化。

CAR算法:

  • CART算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,则不纯度越低,特征越好。这和信息增益及信息增益率是相反的。
  • 对于CART算法的连续值的处理问题,其思想和C4.5算法是相同的,都是将连续的特征离散化。

6.2 实验总结

决策树,其实和KNN算法的目的是一样的,就是做分类任务。对于我这次实验,最基本的就是懂得了ID3、C4.5、CART三种算法。一开始对信息熵、信息增益、信息增益率、基尼指数,不太理解,突然来的新东西需要时间消化,我比较困难的地方是用Matplotlib绘制决策树,那个代码比较难理解,而且对于python3的环境,有些函数已经更新了,也导致了过程中的报错,解决问题实在是太艰难了,不过好在实验做出来了!还有一个就是,python用字典结构存储决策树,是我没有想到的,花了一点点时间才看明白。

机器学习 —— 决策树相关推荐

  1. 机器学习——决策树学习

    机器学习--决策树学习 一.什么是决策树 二.决策树的学习过程 特征选择: 决策树生成: 剪枝: 三.决策树的一个具体的实例 导入数据 python strip() 函数和 split() 函数的详解 ...

  2. 3.1 机器学习 --- 决策树

    3.1 机器学习 - 决策树 一 金融评分卡 模型做好后尽量快的上线,以符合要求的模型,尽快上线. 超参数赋予权重,使得模型的参数不一致,而达到均衡样本数据 二 决策树原理 1. 找女朋友 性别:男 ...

  3. 关于《机器学习决策树导出图片、PDF支持中文》解决办法

    很久没有更新博客了,一直以为自己默默无闻,埋头苦干.实际上是一种懒惰! 2020年的春天,是一段难忘的记忆!在这段特殊的时光里,让我静心思考,潜心研究.每每遇到技术困惑,总能在CSDN找到解决办法,由 ...

  4. 机器学习 | 决策树原理剪枝连续值缺失值处理

    机器学习 | 决策树 最近学习了西瓜书决策树一章,简单整理了笔记,后续会继续补充给 文章目录 机器学习 | 决策树 一.基本信息论 1. 信息量 2. 信息熵 二.决策树常用算法 1.ID3 2.C4 ...

  5. 机器学习——决策树(decision tree)

    相关文章链接: 机器学习--人工神经网络(NN) 机器学习--卷积神经网络(CNN) 机器学习--循环神经网络(RNN) 机器学习--长短期记忆(LSTM) 机器学习--决策树(decision tr ...

  6. 机器学习 --- 决策树(educode实训作业)

    引言:本文主要对educoder机器学习 --- 决策树的实训作业进行讲解,创造不易!请您点点关注,点点收藏叭!!! 目录 第1关:什么是决策树 任务描述 相关知识 引例 决策树的相关概念 编程要求 ...

  7. 机器学习 决策树篇——解决离散变量的分类问题

    机器学习 决策树篇--解决离散变量的分类问题 摘要 熵增益和熵增益率计算 熵增益和熵增益率运行结果 离散变量的决策树模型 决策树模型运行结果 摘要 本文通过python实现了熵增益和熵增益率的计算.实 ...

  8. 机器学习——决策树模型:Python实现

    机器学习--决策树模型:Python实现 1 决策树模型的代码实现 1.1 分类决策树模型(DecisionTreeClassifier) 1.2 回归决策树模型(DecisionTreeRegres ...

  9. 机器学习 决策树篇——解决连续变量的分类问题

    机器学习 决策树篇--解决连续变量的分类问题 摘要 信息熵.条件熵.熵增益.熵增益率的计算 GiNi系数.GiNi系数增益的计算 python代码 连续变量决策树分类案例 摘要 本文通过python实 ...

  10. 机器学习 | 决策树之回归树

    机器学习 | 决策树之回归树 上文提到决策树不仅可用于分类,还可进行回归,本文将继续记录回归决策树. 文章目录 机器学习 | 决策树之回归树 前言 一.回归树的构建方法 二.递归二分法 三.回归树的剪 ...

最新文章

  1. [LeetCode]题解(python):153-Find Minimum in Rotated Sorted Array
  2. 你不知道的JS(this)---#
  3. customplot设置单个点的颜色_CAD教程,CAD大神总结CAD快捷键及一些参数设置大集合,码走...
  4. 计算机指令取决,不同的计算机,其指令不同,这主要取决于什么?
  5. codeblocks如何导入项目_T3如何利用系统工具导入导出复制存货档案
  6. uart怎么判断帧错误_UART通讯总线工作原理的理解
  7. maven profile多环境动态配置文件使用
  8. [原创]Tsung压力测试工具简介及安装
  9. 如何通过配置tomcat或是web.xml让ie直接下载txt类型的文件
  10. 面试怪事:网上抄答案的程序员
  11. NLP Prompt范式,两种主要类型:填充文本字符串空白的完形填空(Cloze)prompt,和用于延续字符串前缀的前缀 (Prefix) prompt。
  12. Python错误和异常
  13. HOG特征提取(一)
  14. 预告 ARC 重写:iOS 实现的 json 数据源的 O-R Mapping
  15. 股权转让项目:厦门古龙温泉山庄开发有限公司60%股权转让
  16. Python基础语法1-8章
  17. 2017-12-29-effective-python-02-functions
  18. 签约喜讯|国盛智科携手瑞云服务云,推动数字化营销服务管理战略升级
  19. 暴力搜索算法浅显易懂
  20. IBM V7000错误代码及解决

热门文章

  1. android+模拟人体扫描,人体模型模拟器app
  2. 小车加速减速的c语言,c2控制速度技巧
  3. BJTUOJ 1853 gangpener买零食(水~)
  4. 学会这4招,轻松解决开关电源EMI
  5. VVIC根据ID取商品详情
  6. 英文学习20180206
  7. 阿里云服务器地域和可用区常见问题及官方资料解答
  8. 002.2-地下管线转3dtiles工具
  9. 升级到win11后VMware不能开启虚拟机了
  10. 超声波风速风向传感器