史诗级长文--决策树

决策树

    决策树(decision tree)是一种基本的分类与回归方法。

    举个通俗易懂的例子,如下图所示的流程图就是一个决策树,长方形代表判断模块(decision block),椭圆形成代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作为分支(branch),它可以达到另一个判断模块或者终止模块。我们还可以这样理解,分类决策树模型是一种描述对实例进行分类的树形结构。决策树由结点(node)和有向边(directed edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性,叶结点表示一个类。如下图所示的决策树,长方形和椭圆形都是结点。长方形的结点属于内部结点,椭圆形的结点属于叶结点,从结点引出的左右箭头就是有向边。而最上面的结点就是决策树的根结点(root node)。

       这就是一个假想的相亲对象分类系统。它首先检测相亲对方是否有房。如果有房,则对于这个相亲对象可以考虑进一步接触。如果没有房,则观察相亲对象是否有上进心,如果没有,直接Say Goodbye,此时可以说:"你人很好,但是我们不合适。"如果有,则可以把这个相亲对象列入候选名单,好听点叫候选名单,有点瑕疵地讲,那就是备胎。

      把决策树看成一个if-then规则的集合,将决策树转换成if-then规则的过程是这样的:由决策树的根结点(root node)到叶结点(leaf node)的每一条路径构建一条规则;路径上内部结点的特征对应着规则的条件,而叶结点的类对应着规则的结论。决策树的路径或其对应的if-then规则集合具有一个重要的性质:互斥并且完备。这就是说,每一个实例都被一条路径或一条规则所覆盖,而且只被一条路径或一条规则所覆盖。这里所覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件。

使用决策树做预测需要以下过程:

  • 收集数据:可以使用任何方法。比如想构建一个相亲系统,我们可以从媒婆那里,或者通过采访相亲对象获取数据。根据他们考虑的因素和最终的选择结果,就可以得到一些供我们利用的数据了。
  • 准备数据:收集完的数据,我们要进行整理,将这些所有收集的信息按照一定规则整理出来,并排版,方便我们进行后续处理。
  • 分析数据:可以使用任何方法,决策树构造完成之后,我们可以检查决策树图形是否符合预期。
  • 训练算法:这个过程也就是构造决策树,同样也可以说是决策树学习,就是构造一个决策树的数据结构。
  • 测试算法:使用经验树计算错误率。当错误率达到了可接收范围,这个决策树就可以投放使用了。
  • 使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

决策树的构建的准备工作 

      使用决策树做预测的每一步骤都很重要,数据收集不到位,将会导致没有足够的特征让我们构建错误率低的决策树。数据特征充足,但是不知道用哪些特征好,将会导致无法构建出分类效果好的决策树模型。从算法方面看,决策树的构建是我们的核心内容。

这一过程可以概括为3个步骤:特征选择、决策树的生成和决策树的修剪

1.特征选择

      特征选择在于选取对于数据具有分类能力的特征。这样可以提高决策树学习的效率,如果利用一个特征进行分类的结果与随机分类的结果没有很大差别,则称这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度影响不大。通常特征选择的标准是信息增益(information gain)或信息增益比,为了简单,本文使用信息增益作为选择特征的标准。

     再讨论信息增益之前,先看一组实例。

                    

       希望通过所给的训练数据学习一个贷款申请的决策树,用于是否通过贷款申请进行分类,即当新的客户提出贷款申请时,根据申请人的特征利用决策树决定是否批准贷款申请。

      特征选择就是决定用哪个特征来划分特征空间。比如,我们通过上述数据表得到两个可能的决策树,分别由两个不同特征的根结点构成。

      图(a)所示的根结点的特征是年龄,有3个取值,对应于不同的取值有不同的子结点。图(b)所示的根节点的特征是工作,有2个取值,对应于不同的取值有不同的子结点。两个决策树都可以从此延续下去。问题是:究竟选择哪个特征更好些?这就要求确定选择特征的准则。直观上,如果一个特征具有更好的分类能力,或者说,按照这一特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就能够很好地表示这一直观的准则。

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

香农熵

   熵定义为信息的期望值。在信息论与概率统计中,熵是表示随机变量不确定性的度量。如果待分类的事物可能划分在多个分类之中,则符号xi的信息定义为 :

                                               

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

                                                

其中n是分类的数目。熵越大,随机变量的不确定性就越大。 

当熵中的概率由数据估计(特别是最大似然估计)得到时,所对应的熵称为经验熵(empirical entropy)。什么叫数据估计?比如有10个数据,一共有两个类别,A类和B类。其中有7个数据属于A类,则该A类的概率即为十分之七。其中有3个数据属于B类,则该B类的概率即为十分之三。浅显的解释就是,这概率是我们根据数据数出来的。我们定义贷款申请样本数据表中的数据为训练数据集D,则训练数据集D的经验熵为H(D),|D|表示其样本容量,及样本个数。设有K个类Ck=1,2,3,...,K,|Ck|为属于类Ck的样本个数,因此经验熵公式就可以写为 :

                                                  

 用代码计算经验熵

  在编写代码之前,我们先对数据集进行属性标注。

  • 年龄:0代表青年,1代表中年,2代表老年;
  • 有工作:0代表否,1代表是;
  • 有自己的房子:0代表否,1代表是;
  • 信贷情况:0代表一般,1代表好,2代表非常好;
  • 类别(是否给贷款):no代表否,yes代表是。
# -*- coding: UTF-8 -*-
from math import log
def createDataSet():dataSet = [[0, 0, 0, 0, 'no'],         #数据集[0, 0, 0, 1, 'no'],[0, 1, 0, 1, 'yes'],[0, 1, 1, 0, 'yes'],[0, 0, 0, 0, 'no'],[1, 0, 0, 0, 'no'],[1, 0, 0, 1, 'no'],[1, 1, 1, 1, 'yes'],[1, 0, 1, 2, 'yes'],[1, 0, 1, 2, 'yes'],[2, 0, 1, 2, 'yes'],[2, 0, 1, 1, 'yes'],[2, 1, 0, 1, 'yes'],[2, 1, 0, 2, 'yes'],[2, 0, 0, 0, 'no']]labels = ['不放贷', '放贷']             #分类属性return dataSet, labels                #返回数据集和分类属性
def calcShannonEnt(dataSet):numEntires = len(dataSet)                        #返回数据集的行数labelCounts = {}                                #保存每个标签(Label)出现次数的字典for featVec in dataSet:                            #对每组特征向量进行统计currentLabel = featVec[-1]                    #提取标签(Label)信息if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                #Label计数shannonEnt = 0.0                                #经验熵(香农熵)for key in labelCounts:                            #计算香农熵prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率shannonEnt -= prob * log(prob, 2)            #利用公式计算return shannonEnt                                #返回经验熵(香农熵)if __name__ == '__main__':dataSet, features = createDataSet()#print(dataSet)print(calcShannonEnt(dataSet))

                           

计算结果与手算的一致

信息增益

     信息增益是相对于特征而言的,信息增益越大,特征对最终的分类结果影响也就越大,我们就应该选择对最终分类结果影响最大的那个特征作为我们的分类特征。

      条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵(conditional entropy)H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望:

                   

同理,当条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的条件熵称为条件经验熵。明确了条件熵和经验条件熵的概念。接下来,让我们说说信息增益。前面也提到了,信息增益是相对于特征而言的。所以,特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:

一般地,熵H(D)与条件熵H(D|A)之差称为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。设特征A有n个不同的取值{a1,a2,···,an},根据特征A的取值将D划分为n个子集{D1,D2,···,Dn},|Di|为Di的样本个数。记子集Di中属于Ck的样本的集合为Dik,即Dik = Di ∩ Ck,|Dik|为Dik的样本个数。于是经验条件熵的公式可以些为:

 

    以贷款申请样本数据表为例进行说明。看下年龄这一列的数据,也就是特征A1,一共有三个类别,分别是:青年、中年和老年。我们只看年龄是青年的数据,年龄是青年的数据一共有5个,所以年龄是青年的数据在训练数据集出现的概率是十五分之五,也就是三分之一。同理,年龄是中年和老年的数据在训练数据集出现的概率也都是三分之一。现在我们只看年龄是青年的数据的最终得到贷款的概率为五分之二,因为在五个数据中,只有两个数据显示拿到了最终的贷款,同理,年龄是中年和老年的数据最终得到贷款的概率分别为五分之三、五分之四。所以计算年龄的信息增益,过程如下:

同理,计算其余特征的信息增益g(D,A2)、g(D,A3)和g(D,A4)。分别为:

最后,比较特征的信息增益,由于特征A3(有自己的房子)的信息增益值最大,所以选择A3作为最优特征。

# -*- coding: UTF-8 -*-
from math import logdef calcShannonEnt(dataSet):numEntires = len(dataSet)                        #返回数据集的行数labelCounts = {}                                #保存每个标签(Label)出现次数的字典for featVec in dataSet:                            #对每组特征向量进行统计currentLabel = featVec[-1]                    #提取标签(Label)信息if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                #Label计数shannonEnt = 0.0                                #经验熵(香农熵)for key in labelCounts:                            #计算香农熵prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率shannonEnt -= prob * log(prob, 2)            #利用公式计算return shannonEnt                                #返回经验熵(香农熵)def createDataSet():dataSet = [[0, 0, 0, 0, 'no'],                        #数据集[0, 0, 0, 1, 'no'],[0, 1, 0, 1, 'yes'],[0, 1, 1, 0, 'yes'],[0, 0, 0, 0, 'no'],[1, 0, 0, 0, 'no'],[1, 0, 0, 1, 'no'],[1, 1, 1, 1, 'yes'],[1, 0, 1, 2, 'yes'],[1, 0, 1, 2, 'yes'],[2, 0, 1, 2, 'yes'],[2, 0, 1, 1, 'yes'],[2, 1, 0, 1, 'yes'],[2, 1, 0, 2, 'yes'],[2, 0, 0, 0, 'no']]labels = ['不放贷', '放贷']            #分类属性return dataSet, labels                             #返回数据集和分类属性def splitDataSet(dataSet, axis, value):       retDataSet = []                                        #创建返回的数据集列表for featVec in dataSet:                             #遍历数据集if featVec[axis] == value:reducedFeatVec = featVec[:axis]                #去掉axis特征reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集retDataSet.append(reducedFeatVec)return retDataSet                                      #返回划分后的数据集def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                    #特征数量baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵bestInfoGain = 0.0                                  #信息增益bestFeature = -1                                    #最优特征的索引值for i in range(numFeatures):                         #遍历所有特征#获取dataSet的第i个所有特征featList = [example[i] for example in dataSet]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                                             #返回信息增益最大的特征的索引值if __name__ == '__main__':dataSet, features = createDataSet()print("最优特征索引值:" + str(chooseBestFeatureToSplit(dataSet)))

                   

最优特征的索引值为2,也就是特征A3(有自己的房子)。

2.决策树的生成和修剪 

    已经学习了从数据集构造决策树算法所需要的子功能模块,包括经验熵的计算和最优特征的选择,其工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分。第一次划分之后,数据集被向下传递到树的分支的下一个结点。在这个结点上,我们可以再次划分数据。因此我们可以采用递归的原则处理数据集。

     构建决策树的算法有很多,比如C4.5、ID3和CART,这些算法在运行时并不总是在每次划分数据分组时都会消耗特征。由于特征数目并不是每次划分数据分组时都减少,因此这些算法在实际使用时可能引起一定的问题。目前我们并不需要考虑这个问题,只需要在算法开始运行前计算列的数目,查看算法是否使用了所有属性即可。

    决策树生成算法递归地产生决策树,直到不能继续下去未为止。这样产生的树往往对训练数据的分类很准确,但对未知的测试数据的分类却没有那么准确,即出现过拟合现象。过拟合的原因在于学习时过多地考虑如何提高对训练数据的正确分类,从而构建出过于复杂的决策树。解决这个问题的办法是考虑决策树的复杂度,对已生成的决策树进行简化。

   ID3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。具体方法是:从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子节点;再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树。ID3相当于用极大似然法进行概率模型的选择。

再次回顾上述放贷的数据

由于特征A3(有自己的房子)的信息增益值最大,所以选择特征A3作为根结点的特征。它将训练集D划分为两个子集D1(A3取值为"是")和D2(A3取值为"否")。由于D1只有同一类的样本点,所以它成为一个叶结点,结点的类标记为“是”。

对D2则需要从特征A1(年龄),A2(有工作)和A4(信贷情况)中选择新的特征,计算各个特征的信息增益:

根据计算,选择信息增益最大的特征A2(有工作)作为结点的特征。由于A2有两个可能取值,从这一结点引出两个子结点:一个对应"是"(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为"是";另一个是对应"否"(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为"否"。

该决策树只用了两个特征(有两个内部结点),生成的决策树如下图所示。

                    

编写代码构建决策树

使用字典存储决策树

{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
# -*- coding: UTF-8 -*-
from math import log
import operatordef calcShannonEnt(dataSet):numEntires = len(dataSet)                        #返回数据集的行数labelCounts = {}                                #保存每个标签(Label)出现次数的字典for featVec in dataSet:                            #对每组特征向量进行统计currentLabel = featVec[-1]                    #提取标签(Label)信息if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                #Label计数shannonEnt = 0.0                                #经验熵(香农熵)for key in labelCounts:                            #计算香农熵prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率shannonEnt -= prob * log(prob, 2)            #利用公式计算return shannonEnt                                #返回经验熵(香农熵)def createDataSet():dataSet = [[0, 0, 0, 0, 'no'],                        #数据集[0, 0, 0, 1, 'no'],[0, 1, 0, 1, 'yes'],[0, 1, 1, 0, 'yes'],[0, 0, 0, 0, 'no'],[1, 0, 0, 0, 'no'],[1, 0, 0, 1, 'no'],[1, 1, 1, 1, 'yes'],[1, 0, 1, 2, 'yes'],[1, 0, 1, 2, 'yes'],[2, 0, 1, 2, 'yes'],[2, 0, 1, 1, 'yes'],[2, 1, 0, 1, 'yes'],[2, 1, 0, 2, 'yes'],[2, 0, 0, 0, 'no']]labels = ['年龄', '有工作', '有自己的房子', '信贷情况']        #特征标签return dataSet, labels                             #返回数据集和分类属性def splitDataSet(dataSet, axis, value):       retDataSet = []                                        #创建返回的数据集列表for featVec in dataSet:                             #遍历数据集if featVec[axis] == value:reducedFeatVec = featVec[:axis]                #去掉axis特征reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集retDataSet.append(reducedFeatVec)return retDataSet                                      #返回划分后的数据集def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                    #特征数量baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵bestInfoGain = 0.0                                  #信息增益bestFeature = -1                                    #最优特征的索引值for i in range(numFeatures):                         #遍历所有特征#获取dataSet的第i个所有特征featList = [example[i] for example in dataSet]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 = {}for vote in classList:                                        #统计classList中每个元素出现的次数if vote not in classCount.keys():classCount[vote] = 0   classCount[vote] += 1sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序return sortedClassCount[0][0]                                #返回classList中出现次数最多的元素def createTree(dataSet, labels, featLabels):classList = [example[-1] for example in dataSet]            #取分类标签(是否放贷:yes or no)if classList.count(classList[0]) == len(classList):            #如果类别完全相同则停止继续划分return classList[0]if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍历完所有特征时返回出现次数最多的类标签return majorityCnt(classList)bestFeat = chooseBestFeatureToSplit(dataSet)                #选择最优特征bestFeatLabel = labels[bestFeat]                            #最优特征的标签featLabels.append(bestFeatLabel)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, featLabels)return myTreeif __name__ == '__main__':dataSet, labels = createDataSet()featLabels = []myTree = createTree(dataSet, labels, featLabels)print(myTree)

            递归创建决策树时,递归有两个终止条件:第一个停止条件是所有的类标签完全相同,则直接返回该类标签;第二个停止条件是使用完了所有特征,仍然不能将数据划分仅包含唯一类别的分组,即决策树构建失败,特征不够用。此时说明数据纬度不够,由于第二个停止条件无法简单地返回唯一的类标签,这里挑选出现数量最多的类别作为返回值。

运行上述代码,我们可以看到如下结果:

3.使用决策树执行分类 

     依靠训练数据构造了决策树之后,我们可以将它用于实际数据的分类。在执行数据分类时,需要决策树以及用于构造树的标签向量。然后,程序比较测试数据与决策树上的数值,递归执行该过程直到进入叶子结点;最后将测试数据定义为叶子结点所属的类型。在构建决策树的代码,可以看到,有个featLabels参数。它是用来干什么的?它就是用来记录各个分类结点的,在用决策树做预测的时候,我们按顺序输入需要的分类结点的属性值即可。举个例子,比如我用上述已经训练好的决策树做分类,那么我只需要提供这个人是否有房子,是否有工作这两个信息即可,无需提供冗余的信息。

用决策树做分类的代码很简单,编写代码如下:

# -*- coding: UTF-8 -*-
from math import log
import operatordef calcShannonEnt(dataSet):numEntires = len(dataSet)                        #返回数据集的行数labelCounts = {}                                #保存每个标签(Label)出现次数的字典for featVec in dataSet:                            #对每组特征向量进行统计currentLabel = featVec[-1]                    #提取标签(Label)信息if currentLabel not in labelCounts.keys():    #如果标签(Label)没有放入统计次数的字典,添加进去labelCounts[currentLabel] = 0labelCounts[currentLabel] += 1                #Label计数shannonEnt = 0.0                                #经验熵(香农熵)for key in labelCounts:                            #计算香农熵prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率shannonEnt -= prob * log(prob, 2)            #利用公式计算return shannonEnt                                #返回经验熵(香农熵)def createDataSet():dataSet = [[0, 0, 0, 0, 'no'],                        #数据集[0, 0, 0, 1, 'no'],[0, 1, 0, 1, 'yes'],[0, 1, 1, 0, 'yes'],[0, 0, 0, 0, 'no'],[1, 0, 0, 0, 'no'],[1, 0, 0, 1, 'no'],[1, 1, 1, 1, 'yes'],[1, 0, 1, 2, 'yes'],[1, 0, 1, 2, 'yes'],[2, 0, 1, 2, 'yes'],[2, 0, 1, 1, 'yes'],[2, 1, 0, 1, 'yes'],[2, 1, 0, 2, 'yes'],[2, 0, 0, 0, 'no']]labels = ['年龄', '有工作', '有自己的房子', '信贷情况']        #特征标签return dataSet, labels                             #返回数据集和分类属性def splitDataSet(dataSet, axis, value):       retDataSet = []                                        #创建返回的数据集列表for featVec in dataSet:                             #遍历数据集if featVec[axis] == value:reducedFeatVec = featVec[:axis]                #去掉axis特征reducedFeatVec.extend(featVec[axis+1:])     #将符合条件的添加到返回的数据集retDataSet.append(reducedFeatVec)return retDataSet                                      #返回划分后的数据集def chooseBestFeatureToSplit(dataSet):numFeatures = len(dataSet[0]) - 1                    #特征数量baseEntropy = calcShannonEnt(dataSet)                 #计算数据集的香农熵bestInfoGain = 0.0                                  #信息增益bestFeature = -1                                    #最优特征的索引值for i in range(numFeatures):                         #遍历所有特征#获取dataSet的第i个所有特征featList = [example[i] for example in dataSet]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 = {}for vote in classList:                                        #统计classList中每个元素出现的次数if vote not in classCount.keys():classCount[vote] = 0   classCount[vote] += 1sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)        #根据字典的值降序排序return sortedClassCount[0][0]                                #返回classList中出现次数最多的元素
def createTree(dataSet, labels, featLabels):classList = [example[-1] for example in dataSet]            #取分类标签(是否放贷:yes or no)if classList.count(classList[0]) == len(classList):            #如果类别完全相同则停止继续划分return classList[0]if len(dataSet[0]) == 1 or len(labels) == 0:                                    #遍历完所有特征时返回出现次数最多的类标签return majorityCnt(classList)bestFeat = chooseBestFeatureToSplit(dataSet)                #选择最优特征bestFeatLabel = labels[bestFeat]                            #最优特征的标签featLabels.append(bestFeatLabel)myTree = {bestFeatLabel:{}}                                    #根据最优特征的标签生成树del(labels[bestFeat])                                        #删除已经使用特征标签featValues = [example[bestFeat] for example in dataSet]        #得到训练集中所有最优特征的属性值uniqueVals = set(featValues)                                #去掉重复的属性值for value in uniqueVals:                                    #遍历特征,创建决策树。                       myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), labels, featLabels)return myTreedef classify(inputTree, featLabels, testVec):firstStr = next(iter(inputTree))                                                        #获取决策树结点secondDict = inputTree[firstStr]                                                        #下一个字典featIndex = featLabels.index(firstStr)                                               for key in secondDict.keys():if testVec[featIndex] == key:if type(secondDict[key]).__name__ == 'dict':classLabel = classify(secondDict[key], featLabels, testVec)else: classLabel = secondDict[key]return classLabelif __name__ == '__main__':dataSet, labels = createDataSet()featLabels = []myTree = createTree(dataSet, labels, featLabels)testVec = [0,1]                                        #测试数据result = classify(myTree, featLabels, testVec)if result == 'yes':print('放贷')if result == 'no':print('不放贷')

 用决策树分类。输入测试数据[0,1],它代表没有房子,但是有工作,分类结果如下所示:

总结

优点
  • 易于理解和解释。决策树可以可视化。
  • 几乎不需要数据预处理。其他方法经常需要数据标准化,创建虚拟变量和删除缺失值。决策树还不支持缺失值。
  • 使用树的花费(例如预测数据)是训练数据点(data points)数量的对数。
  • 可以同时处理数值变量和分类变量。其他方法大都适用于分析一种变量的集合。
  • 可以处理多值输出变量问题。
  • 使用白盒模型。如果一个情况被观察到,使用逻辑判断容易表示这种规则。相反,如果是黑盒模型(例如人工神经网络),结果会非常难解释。
  • 即使对真实模型来说,假设无效的情况下,也可以较好的适用。
缺点
  • 决策树学习可能创建一个过于复杂的树,并不能很好的预测数据。也就是过拟合。修剪机制(现在不支持),设置一个叶子节点需要的最小样本数量,或者数的最大深度,可以避免过拟合。
  • 决策树可能是不稳定的,因为即使非常小的变异,可能会产生一颗完全不同的树。这个问题通过decision trees with an ensemble来缓解。
  • 概念难以学习,因为决策树没有很好的解释他们,例如,XOR, parity or multiplexer problems。
  • 如果某些分类占优势,决策树将会创建一棵有偏差的树。因此,建议在训练之前,先抽样使样本均衡。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603410.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C练习——肇事卡车车牌号

题目: 一辆卡车违反交通规则,撞人后逃跑。现场有3人目击事件,但没有记住车牌号,只记住了车号的一些特征。 甲说:“牌照前两位数字是相同的”,乙说:“牌照的后两位数字是相同的,但与…

pinia的独立维护,统一导出及持久化

目录 1.说明及示例 2.注意 1.说明及示例 在src下创建store文件夹,在store文件夹下创建index.js文件,内容如下: import { createPinia } from "pinia"; // pinia的持久化 import piniaPluginPersistedstate from "pinia-pl…

odoo17 | 视图字段验证约束

前言 前一章介绍了向模型添加一些业务逻辑的能力。现在我们可以将按钮链接到业务代码,但是我们如何防止用户输入错误的数据呢?例如,在我们的房地产模块中,没有什么可以阻止用户设置负预期价格。 Odoo提供了两种方法来设置自动验证的不变量…

【gRPC学习】使用go学习gRPC

个人博客:Sekyoro的博客小屋 个人网站:Proanimer的个人网站 RPC是远程调用,而google实现了grpc比较方便地实现了远程调用,gRPC是一个现代的开源远程过程调用(RPC)框架 概念介绍 在gRPC中,客户端应用程序可以直接调用另一台计算机上的服务器应用程序上的方法&#…

认识加密和解密

加密技术源远流长,自从古代有了信息的传递和存储,就有了加密技术的运用。此后,很长一段时间里,加密及解密技术在军事、政治、外交、金融等特殊领域里被普遍采用,并经过长时间的研究和发展,形成了比较完备的…

element plus 表格组件怎样在表格中显示图片

官方给的&#xff1a; <el-table-column label"Thumbnail" width"180"><template #default"scope"><div style"display: flex; align-items: center"><el-image :preview-src-list"srcList"/><…

一起玩儿物联网人工智能小车(ESP32)——24. 变量与函数(二)

摘要&#xff1a;本文介绍变量和函数的基本知识 在前面一篇中了解了变量&#xff0c;接着就来了解一下函数。函数是程序中的一个关键概念&#xff0c;它可以简化程序的编写&#xff0c;使代码更加模块化、可复用&#xff0c;提高程序的可读性。其实在之前已经多次遇到函数了&am…

Arduion Modbus通讯示例

实现了Arduion和Qt上位机利用Modbus协议采集DHT11数据&#xff0c;以及开关LED灯 软件界面&#xff1a; 实物界面&#xff1a; arduion下位机代码&#xff1a; #include <ModbusRtu.h> #include <DHT.h>#define DHTPIN 2 // DHT11连接到Arduino的数字引…

使用C#发送邮箱验证码

使用C#发送邮箱验证码 在很多应用程序中&#xff0c;我们需要使用邮箱来进行用户身份验证。其中一种常见的方式是通过发送验证码到用户的邮箱&#xff0c;然后要求用户输入该验证码进行验证。本文将介绍如何使用 C# 发送邮箱验证码。 声明 验证码登录没有用任何的工具&#…

学习尚硅谷Vue的TodoList案例下半部分总结

今天学习尚硅谷Vue的TodoList案例四到八节的内容&#xff0c;主要实现的功能有勾选、删除、底部统计、底部交互这几部分。先说勾选&#xff0c;想要获取到用户是否勾选&#xff0c;就要给checkbox多选框注册鼠标点击事件&#xff0c;这里的思路是用户虽然是获取到了是否勾选但还…

项目框架构建之3:Nuget服务器的搭建

本文是“项目框架构建”系列之3&#xff0c;本文介绍一下Nuget服务器的搭建&#xff0c;这是一项简单的工作&#xff0c;您或许早已会了。 1.打开vs2022创建Asp.net Web应用程序 框架选择.net framework4.8&#xff0c;因为nuget服务器只支持.net framework。 2.选择空项目和保…

C++ 学习系列 -- tuple 原理

一 可变参数模板 variadic template 前面的章节 C 学习系列 -- 模板 template-CSDN博客 我们介绍了 c 中的模板概念&#xff0c;本章则在其基础上介绍了新的概念 可变参数模板 variadic template &#xff0c;顾名思义&#xff0c;可变参数模板意思为模板参数的类型与数量是变…

微信小程序封装vant 下拉框select 单选组件

先上效果图&#xff1a; 主要是用vant 小程序组件封装的&#xff1a;vant 小程序ui网址&#xff1a;vant-weapp 主要代码如下: 先封装子组件&#xff1a; select-popup 放在 components 文件夹里面 select-popup.wxml: <!--pages/select-popup/select-popup.wxml--> &…

爆肝整理,企业级性能测试-性能方案设计详细总结(二)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、测试策略 1&a…

【小沐学C++】C++ 实现鼠标键盘钩子HOOK

文章目录 1、简介2、相关函数2.1 SetWindowsHookEx2.2 UnhookWindowsHookEx2.3 CallNextHookEx 3、相关结构体3.1 KBDLLHOOKSTRUCT3.2 MSLLHOOKSTRUCT 4、挂钩过程5、代码测试5.1 代码1 结语 1、简介 https://learn.microsoft.com/zh-cn/windows/win32/winmsg/about-hooks 挂…

Java学习笔记(十)——异常

一、异常的概念 二、异常体系图&#xff08;重要&#xff09; 三、常见的异常 &#xff08;一&#xff09;常见的运行时异常 1、NullPointerException空指针异常 2、ArithmeticException数学运算异常 3、ArrayIndexOutOfBoundsException数组下标越界异常 4、ClassCastEx…

CSS 压重按钮 效果

<template><view class="cont"><div class="container"><div class="pane"><!-- 选项1 --><label class="label" @click="handleOptionClick(0)":style="{ color: selectedOption ==…

约数个数和约数之和算法总结

知识概览 约数个数 由算数基本定理 可得对于N的任何一个约数d&#xff0c;有 因为N的每一个约数和~的一种选法是一一对应的&#xff0c;根据乘法原理可得&#xff0c; 一个数的约数个数为 约数之和 一个数的约数之和公式为 多项式乘积的每一项为 正好对应的是一个数的每一个约…

【网络安全】upload靶场pass11-17思路

目录 Pass-11 Pass-12 Pass-13 Pass-14 Pass-15 Pass-16 Pass-17 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x…

JetCache源码解析——配置加载和初始化

JetCache自动化配置加载 JetCache的配置加载主要是在jetcache-autoconfigure模块中完成的&#xff0c;其中加载配置的核心类是JetCacheAutoConfiguration&#xff0c;主要是用于创建全局性的一些Bean&#xff0c;例如全局缓存配置类GlobalCacheConfig&#xff0c;AutoConfigur…