机器学习实战第三章决策树01决策树简介

本章内容简介

  • 决策树简介
  • 在数据集中度量一致性
  • 使用递归构造决策树
  • 使用Matplotlib绘制树形图

你是否玩过二十个问题的游戏,游戏的规则很简单:参与游戏的一方在脑海里想某个事物,其他参与者向他提问题,只允许提20个问题,问题的答案也只能用对或者错回答。问问题的人通过推断分解,逐步缩小待猜测事物的范围。决策树的工作原理与20个问题类似,用户输入一系列数据,然后给出游戏答案。

我们经常使用决策树处理分类问题,近来的调查表明决策树也是最经常使用的数据挖掘算法。它之所以如此流行,一个很重要的原因就是使用者基本上不用了解机器学习算法,也不用深究它是如何工作的。

如果你以前没有机会接触过决策树,完全不用担心,它的概念非常简单。即使不知道它也可以通过简单的图形了解其工作原理,图3-1所示的流程图就是一个决策树,正方形代表判断模型,椭圆形代表终止模型,表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作分支,它可以到达另一个判断模块或者终止模块。图3-1构造了一个假象的邮件分类系统,它首先检测发送邮件域名地址。如果地址为myEmployer.com,则将其放在分类“无聊时需要阅读的邮件”中。如果邮件不是来自这个域名,则检查邮件内容里是否包含单词曲棍球,如果包含则将邮件归类到“需要及时处理的朋友邮件”,如果不包含则将邮件归类“无需阅读的垃圾邮件”。

第二章介绍的k-近邻算法可以完成很多分类任务,但是它最大的缺点就是无法给出数据的内在含义,决策树主要优势就在于数据形式非常容易理解。

本章构造的决策树算法能够读取数据集合,构建类似于图3-1的决策树。决策树很多任务都是为了数据中所蕴含的知识信息,因此决策树可以使用不熟悉的数据集合,并从中提取出一系列规则,机器学习算法最终将使用这些机器从数据集中创造的规则。专家系统中经常使用决策树,而且决策树给出结果往往可以匹敌在当前领域具有几十年工作经验的人类专家。

现在我们已经大致了解了决策树可以完成那些任务,接下来我们将学习如何从一堆原始数据中构造决策树。首先我们讨论构造决策树的方法,以及如何编写构造树的Python代码;接着提出一些度量算法成功率的方法;最后使用递归建立分类器,并且使用Matplotlib绘制决策树图。构造完成决策树分类器之后,我们将输入一些隐形眼睛的处方数据,并由决策树分类器预测需要的镜片类型。

3.1 决策树的构造

决策树

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据

缺点:可能会产生过度匹配问题。

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

本节将一步步地构造决策树算法,并会涉及许多有趣的细节。首先我们讨论数学上如何使用信息论划分数据集,然后编写代码将理论应用到具体的数据集上,最后编写代码构建决策树。

在构造决策树时,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。完成测试之后,原始数据集就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型,则当前无需阅读的垃圾邮件已经正确地划分数据分类,无需进一步对数据集进行分割。如果数据子集内的数据类型不属于同一类型,则需要重复划分数据子集的过程。如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。

创建分支的伪代码函数createBranch()如下所示:

检测数据集中的每个子项是否属于同一分类:

     if so return 类标签;

     else

           寻找划分数据集的最好特征

           划分数据集

           创建分支节点

                  for每个划分的子集

                        调用函数createBranch并增加返回结果到分支节点中

                 return 分支节点

上面的伪代码createBranch是一个递归函数,在倒数第二行直接调用了它自己。后面我们将把上面的伪代码转换为Python代码,这里我们需要进一步了解算法是如何划分数据集的。

决策树的一般流程

(1)收集数据:可以使用任何方法。

(2)准备数据:树构造算法只适用于标称型数据(标称型目标变量的结果只在有限目标集中取值,如真与假(标称型目标变量主要用于分类)),因此数值型数据(数值型目标变量则可以从无限的数值集合中取值,如0.100,42.001等 (数值型目标变量主要用于回归分析))必须离散化。

(3)分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。

(4)训练算法:构造树的数据结构

(5)测试算法:使用经验树计算错误率

(6)使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好的理解数据的内在含义。

一些决策树算法采用二分法划分数据,本书并不采用这种方法。如果依据某个属性划分数据将会产生4个可能的值,我们把数据划分成四块,并创建四个不同的分支。本书将使用ID3算法划分数据,该算法处理如何划分数据集,何时停止划分数据集(参考https://en.wikipedia.org/wiki/ID3_algorithm。每次划分数据集时我们只选取一个特征属性,如果训练集中存在20个特征,第一次我们选择哪个特征作为划分的参考属性呢?)

表3-1的数据包含5个海洋动物,特征包括:不浮出水面是否可以生存,以及是否有脚蹼。我们可以将这些动物分成两类:鱼类和非鱼类。现在我们想要决定依据第一个特征还是第二个特征划分数据。在回答这个问题之前,我们必须采用量化的方法判断如何划分数据。下一小节将详细讨论这个问题。

3.1.1 信息增益

划分数据集的大原则是:将无序的数据变得更加有序。我们可以使用多种方法划分数据集,但是每种方法都有各自的优缺点。组织杂乱无章数据的一种方法就是使用信息论度量信息,信息论是量化处理信息的分支科学。我们可以在划分数据之前使用信息论量化度量信息的内容。

在划分数据集之前之后信息发生的变化称为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。

在可以评测哪种数据划分方式是最好的数据划分之前,我们必须学习如何计算信息增益。集合信息的度量方式称为香农熵或者简称熵,这个名字来源于信息论之父克劳德.香农。

熵定义为信息的期望值,在明晰这个概念之前,我们必须知道信息的定义。如果待分类的事务可能划分在多个分类之中,则符号x_{i}的信息定义为l(x_{i})=-log_{2}^{p(x_{i})}其中p(x_{i})是选择该分类的概率。

为了计算熵,我们需要计算所有类别可能值包含的信息期望值,通过下面的公式得到:

H=-\sum _{i=1}^{n}p\left ( x_{i} \right )log_{2}^{p\left ( x_{i} \right )}

其中n是分类的数目。

下面我们将学习如何使用Python计算信息熵,创建名为trees.py的文件,将程序清单3-1的代码内容录入到trees.py文件中,此代码的功能是计算给定数据集的熵。

程序清单3-1 给定数据集的香农熵

from math import log

#计算给定数据集的香农熵
def calShnnonEnt(dataSet):
    numEntries=len(dataSet)
    labelCounts={}
    for featVec in dataSet:
        #1为所有可能分类创建字典
        currentLabel=featVec[-1]
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        labelCounts[currentLabel]+=1
        
    shannonEnt=0.0
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntries
        shannonEnt -=prob*log(prob,2)    #2以2为底求对数
    return shannonEnt

程序清单3-1的代码非常简单。首先,计算数据集中实例的总数。我们也可以在需要时再计算这个值,但是由于代码中多次用到这个值,为了提高代码效率,我们显式地声明一个变量保存实例总数。然后,创建一个数据字典,它的键值是最后一列数值。如果当前键值不存在,则扩展字典并将当前键值加入字典。每个键值都记录了当前类别出现的次数。最后,使用所有类别标签的发生频率计算类别出现的概率。我们将用这个概率计算香农熵,统计所有类标签发生的次数。下面我们看看如何使用熵划分数据集。

在trees.py文件中,我们可以利用createDataSet()函数得到表3-1所示的简单鱼鉴定数据集,你可以输入自己的createDataSet()函数:

#鱼数据集
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

myDat,labels=createDataSet()
print(myDat)
print(calShnnonEnt(myDat))
输出
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
0.9709505944546686

熵越高,则混合的数据也越多,我们可以在数据集中添加更多的分类,观察熵是如何变化的。

myDat[0][-1]='maybe'
print(myDat)
print(calShnnonEnt(myDat))
输出
[[1, 1, 'maybe'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
1.3709505944546687

得到熵之后,我们就可以按照获取最大信息增益的方法划分数据集,下一节我们将具体学习如何划分数据集以及如何度量信息增益。

3.1.2 划分数据集

上节我们学习了如何度量数据集的无序程度,分类算法除了需要测量信息熵,还需要划分数据集,度量花费数据集的熵,以便判断当前是否正确地划分了数据集。我们将对每个特征划分数据集的结果计算一次信息熵,然后判断按照哪个特征划分数据集是最好的划分方式。想象一个分布在二维空间的数据散点图,需要在数据之间划条线,将它们分成两部分,我们应该按照x轴还是y轴划线呢?答案就是本节讲述的内容。

程序清单3-2 按照给定特征划分数据集

#按照给定特征划分数据集
def splitDataSet(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

程序清单3-2的代码使用了三个输入参数:待划分的数据集、划分数据集的特征、特征的返回值。需要注意的是,Python语言不用考虑内存分配问题。Python语言在函数中传递的是列表的引用,在函数内部对列表对象的修改,将会影响该列表对象的整个生存周期。为了消除这个不良影响,我们需要在函数的开始声明一个新列表对象。因为该函数代码在同一数据集上被调用多次,为了不修改原始数据,创建一个新的列表对象。数据集这个列表中的各个元素也是列表,我们要遍历数据集中的每一个元素,一旦发现符合要求的值,则将其添加到新创建的列表中。在if语句中,程序将符合特征数据的数据抽取出来。后面讲述的更简单,这里我们可以这样理解这段代码:当我们按照某个特征划分数据时,就需要将所有符合要求的元素抽取出来。代码中使用了Python语言列表类型自带的extend()和append()方法。这两个方法功能类似,但是在处理多个列表时,这两个方法的处理结果是完全不同的。

我们可以在前面的简单样本数据上测试函数splitDataSet()。

myDat,labels=createDataSet()
print(myDat)
输出
[[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
print(splitDataSet(myDat,0,1))
输出
[[1, 'yes'], [1, 'yes'], [0, 'no']]
print(splitDataSet(myDat,0,0))
输出
[[1, 'no'], [1, 'no']]

接下来我们将遍历整个数据集,循环计算香农熵和splitDataSet()函数,找到最好的特征划分方式。熵计算将会告诉我们如何划分数据集是最好的数据组织方式。

程序清单3-3 选择最好的数据集划分方式

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]#获取某一特征的所有值
        uniqueVals=set(featList)#set是一个无序不重复元素集,使用set函数对属性值列表进行唯一化,防止重复计算
        #
        newEntropy=0.0
        #计算每种划分方式的信息熵,并求和
        for value in uniqueVals:
            subDataSet=splitDataSet(dataSet,i,value)
            prob=len(subDataSet)/float(len(dataSet))
            newEntropy+=prob*calcShannonEnt(subDataSet)
        infoGain=baseEntropy-newEntropy
        #
        #计算最好的信息增益
        if (infoGain>bestInfoGain):
            bestInfoGain=infoGain
            bestFeature=i
    return bestFeature

 程序清单3-3给出了函数chooseBestFeatureToSplit()的完整代码,该函数实现选取特征,划分数据集,计算得出最好的划分数据集的特征。函数 chooseBestFeatureToSplit()使用了程序清单3-1和程序清单3-2中的函数。在函数中调用的数据需要满足一定的要求:第一个要求是,数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度;第二个要求是数据的最后一列或者每个实例的最后一个元素是当前实例的类别标签。数据集一旦满足上述要求,我们就可以在函数的第一行判定当前数据集包含多少特征属性。我们无需限定list中的数据类型,他们既可以是数字,也可以是字符串,并不影响实际计算。

在开始划分数据集之前,程序清单3-3的第三行代码计算了整个数据集的原始香农熵,我们保存最初的无序度量值,用于与划分完之后的数据集计算的熵值进行比较。第一个for循环遍历数据集中的所有特征。使用列表推导来创建新的列表,将数据集中所有第i个特征值或者所有可能存在的值写入这个新list中。然后使用Python语言原生的集合(set)数据类型。集合数据类型与列表类型相似,不同之处在于集合类型中的每个值互不相同。从列表中创建集合是Python语言得到列表中唯一元素的最快方法。

遍历当前特征中的所有唯一属性值,对每一个特征划分一次数据集,然后计算数据集的新熵值,并对所有唯一特征值得到的熵求和。信息增益是熵的减少或者是数据无序度的减少。熵越大,物质越混乱,越无序。最后,比较所有特征中的信息增益,返回最好特征划分的索引值。

测试上面代码的实际输出结果:

myDat,labels=createDataSet()
print(chooseBestFeatureToSplit(myDat))
输出
0

代码运行结果告诉我们,第0个特征是最好的用于划分数据集的特征。结果是否正确呢?这个结果又有什么意义呢?如果我们按照第一个特征属性划分数据,也就是说第一个特征是1的放在一个组,第一个特征是0的放在另一个组,数据一致性如何?按照上述方法划分数据集,第一个特征为1的海洋生物分组将有两个属于鱼类,一个属于非鱼类;另一个分组则全部属于非鱼类。如果按照第二个特征分组,结果是第一个海洋动物分组将有两个属于鱼类,两个属于非鱼类;另一个分组则只有一个非鱼类。如果不相信目测结果,读者可以使用程序3-1的calcShannonEntropy()函数测试不同特征分组的输出结果。

本节我们学习了如何度量数据集的信息熵,如何有效地划分数据集,下一节,我们将介绍如何将这些函数功能放在一起,构建决策树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值