精华内容
下载资源
问答
  • 本代码主要利用Python工具决策树,简单明了,易于理解
  • 决策树的构造 我的微信公众号: s406205391; 欢迎大家一起学习,一起进步!...**决策树流行的很重要的原因就是他的工作很简单,不需要了解机器学习的知识。下图所示的流程图就是一个决策树。图中构
  • 机器学习实战决策树

    2021-05-24 19:20:50
    1.1 决策树的构造 决策树 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。 缺点:可能会产生过度匹配问题。 适用数据类型:数值型和标称型。 在构造决策树时,我们需要...

    1.1 决策树的构造
    决策树
    在机器学习中,决策树是一个预测模型,他代表的是对象属性与对象值之间的一种映射关系。树中每个节点表示某个对象,而每个分叉路径则代表的某个可能的属性值,而每个叶结点则对应从根节点到该叶节点所经历的路径所表示的对象的值。决策树仅有单一输出,若欲有复数输出,可以建立独立的决策树以处理不同输出。
    决策树是一种树形结构,其中每个内部节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶节点代表一种类别。
    优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
    缺点:可能会产生过度匹配问题。
    适用数据类型:数值型和标称型。
    在构造决策树时,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。为了找到决定性的特征,划分出最好的结果,我们必须评估每个特征。完成测试之后,原始数据集就被划分为几个数据子集。这些数据子集会分布在第一个决策点的所有分支上。如果某个分支下的数据属于同一类型,则当前无需阅读的垃圾邮件已经正确地划分数据分类,无需进一步对数据集进行分割。如果数据子集内的数据不属于同一类型,则需要重复划分数据子集的过程。如何划分数据子集的算法和划分原始数据集的方法相同,直到所有具有相同类型的数据均在一个数据子集内。
    决策树的一般流程
    (1) 收集数据:可以使用任何方法。
    (2) 准备数据:树构造算法只适用于标称型数据,因此数值型数据必须离散化。
    (3) 分析数据:可以使用任何方法,构造树完成之后,我们应该检查图形是否符合预期。
    (4) 训练算法:构造树的数据结构。
    (5) 测试算法:使用经验树计算错误率。
    (6) 使用算法:此步骤可以适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。
    1.1.1 信息增益
    在划分数据集的前后信息发生的变化称为信息增益,知道如何计算信息增益,我们就可以计算每个特征值划分数据集获得的信息增益,获得信息增益最高的特征就是最好的选择。
    熵定义为信息的期望值,在明晰这个概念之前,我们必须知道信息的定义。如果待分类的事务可能划分在多个分类之中,则符号xi的信息定义为:
    在这里插入图片描述
    其中p(xi)是选择该分类的概率。
    为了计算熵,我们需要计算所有类别所有可能值包含的信息期望值,通过下面的公式得到:
    在这里插入图片描述
    其中n是分类的数目。
    程序清单1-1 计算给定数据集的香农熵

    #计算给定数据集的香农熵
    def calcShannonEnt(dataSet):
        #得到文件行数
        numEntries = len(dataSet)
        #创建一个空字典,键为类别,值为该类别出现的次数
        labelCounts = {}
        #统计每个类别出现的次数
        for featVec in dataSet:
            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)
        return shannonEnt
    

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

    def splitDataSet(dataSet,axis,value):
        retDataSet = []
        for featVec in dataSet:
            if featVec[axis] == value:
                reducedFeatVec = featVec[:axis]
                reducedFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reducedFeatVec)
        return retDataSet
    

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

    def chooseBestFeatureToSplit(dataSet):
        #计算每条数据的特征数量
        numFeatures = len(dataSet[0]) - 1
        #计算原始数据集的熵值(混乱程度)
        baseEntropy = calcShannonEnt(dataSet)
        bestInfoGain = 0.0;bestFeature = -1
        #遍历所有特征,最后计算每个特征的信息增益,选取最好的
        for i in range(numFeatures):
            #创建对应特征的属性值列表  featList=[1,1,0,1,1]
            featList = [example[i] for example in dataSet]
            # 得到当前特征下的不同属性值集合
            uniqueVals = set(featList)
            #初始化熵
            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
    

    1.1.3 递归构建决策树

    def majorityCnt(classList):
        classCount= {}
        for vote in classList:
            if vote not in classCount.keys():
                classCount[vote] = 0
                classCount[vote] += 1
            sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse = True)
            return sortedClassCount[0][0]
    

    程序清单1-4 创建树的函数代码

    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
    

    1.2 在 Python 中使用 Matplotlib 注解绘制树形图
    程序清单1-5 使用文本注解绘制树节点

    import matplotlib.pyplot as plt
    from pylab import *
    mpl.rcParams["font.sans-serif"] = ["SimHei"]
    #定义文本框和箭头格式
    decisionNode = dict(boxstyle = "sawtooth",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)
    def createPlot():
        fig = plt.figure(1,facecolor = "white")
        fig.clf()
        createPlot.ax1 = plt.subplot(111,frameon = False)
        plotNode(U"决策节点",(0.5,0.1),(0.1,0.5),decisionNode)
        plotNode(U"叶节点",(0.8,0.1),(0.3,0.8),leafNode)
        plt.show()
    createPlot()
    

    在这里插入图片描述
    1.2.2 构造注解树
    程序清单1-6 获取叶节点的数目和树的层数

    def getNumLeafs(myTree):
        numLeafs = 0
        firstStr = list(myTree.keys())[0]
        secondDict = myTree[firstStr]
        for key in secondDict.keys():
            if type(secondDict[key]) == dict:
                numLeafs += getNumLeafs(secondDict[key])
            else:numLeafs += 1
        return numLeafs
    def getTreeDepth(myTree):
        maxDepth = 0
        firstStr = list(myTree.keys())[0]
        secondDict = myTree[firstStr]
        for key in secondDict.keys():
            if type(secondDict[key]) == dict:
                thisDepth = 1 + getTreeDepth(secondDict[key])
            else: thisDepth = 1
            if thisDepth > maxDepth:maxDepth = thisDepth
        return maxDepth
    

    程序清单1-7 plotTree函数

    def retrieveTree(i):
        listOfTrees = [{"no surfacing":{0:"no",1:{"flippers":{0:"no",1:"yes"}}}},
                      {"no surfacing":{0:"no",1:{"flippers":{0:{"head":{0:"no",1:"yes"}},1:"no"}}}}
                      ] 
        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)
    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)
        plotMidText(cntrPt,parentPt,nodeTxt)
        plotNode(firstStr,cntrPt,parentPt,decisionNode)
        secondDict = myTree[firstStr]
        plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
        for key in secondDict.keys():
            if type(secondDict[key])== dict:
                plotTree(secondDict[key],cntrPt,str(key))
            else:
                plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
                plotNode(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()
        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()
    

    1.3 测试和存储分类器
    1.3.1 测试算法:使用决策树执行分类
    程序清单1-8 使用决策树的分类函数

    def classify(inputTree,featLabels,testVec):
        firstStr = list(inputTree.keys())[0]
        secondDict = inputTree[firstStr]
        featIndex = featLabels.index(firstStr)
        for key in secondDict.keys():
            if testVec[featIndex] == key:
                if type(secondDict[key]) == dict:
                    classLabel = classify(secondDict[key],featLabels,testVec)
                else: classLabel = secondDict[key]
        return classLabel
    

    1.3.2 使用算法:决策树的存储
    程序清单1-9 使用pickle模块存储决策树

    def storeTree(inputTree,filename):
        import pickle
        fw = open(filename,"wb")
        pickle.dump(inputTree,fw)
        fw.close()
    
    def grabTree(filename):
        import pickle
        fr = open(filename,"rb")
        return pickle.load(fr)
    
    展开全文
  • 机器学习实战决策树书中源代码(python2)以及我博客的源代码(python3+注释),博客地址链接http://blog.csdn.net/jichun4686/article/details/76099814
  • 机器学习实战第三章决策树代码,说实话感觉这一章不太实用
  • 改文档描述了机器学习中的决策树相关细节,包括数据集描述以及算法描述和结果展示
  • 机器学习实战项目决策树完整项目,一个完成的项目,值得学习借鉴
  • 决策树算法是一种归纳分类算法,它通过对训练集的学习,挖掘出有用的规则,用于对新集进行预测。 决策树算法采用树形结构,自顶向下递归方式构造决策树 决策树由下面几种元素构成: 根节点:包含所有的样本; 内部...
    • 什么是决策树?介绍

     

    决策树算法是一种归纳分类算法,它通过对训练集的学习,挖掘出有用的规则,用于对新集进行预测。

    决策树算法采用树形结构,自顶向下递归方式构造决策树

    决策树由下面几种元素构成:

    1. 根节点:包含所有的样本;
    2. 内部节点:对应样本特征属性;
    3. 分支:样本测试的结果;
    4. 叶子节点:代表决策的结果。
    • 如何构造决策数?

     下图是关于判断猫咪是否是哺乳动物的决策树。

    1.构造

    什么是构造呢?构造就是生成一棵完整的决策树。简单来说, 构造的过程就是选择什么属性作为节点的过程 ,那么在构造过程中,会存在三种节点:

    1)根节点:就是树的最顶端,最开始的那个节点,如上图的“体温”。

    2)内部节点:就是树中间的那些节点,如上图的“胎生”;

    3)叶节点:就是树最底部的节点,也就是决策结果,如上图的“哺乳动物”,“非哺乳动物”。

    节点之间存在父子关系。比如根节点会有子节点,子节点会有子子节点,但是到了叶节点就停止了,叶节点不存在子节点。那么在构造过程中,你要解决三个重要的问题:

    1)选择哪个属性作为根节点;

    2)选择哪些属性作为子节点;

    3)什么时候停止并得到目标状态,即叶节点。

    例子:

    下面我们通过一个例子对决策树的构造进行具体的讲解。 我们现在有这样一个数据集

     

    我们该如何构造一个判断是否去打篮球的决策树呢?再回顾一下决策树的构造原理,在决策过程中有三个重要的问题:将哪个属性作为根节点?选择哪些属性作为后继节点?什么时候停止并得到目标值?

    显然将哪个属性(天气、温度、湿度、刮风)作为根节点是个关键问题,在这里我们先介绍两个指标:纯度和信息熵。

    先来说一下纯度。你可以把决策树的构造过程理解成为寻找纯净划分的过程。数学上,我们可以用纯度来表示,纯度换一种方式来解释就是让目标变量的分歧最小。

    我在这里举个例子,假设有 3 个集合:

    集合 1,6 次都去打篮球;

    集合 2,4 次去打篮球,2 次不去打篮球;

    集合 3,3 次去打篮球,3 次不去打篮球。

    按照纯度指标来说,集合 1> 集合 2> 集合 3。因为集合 1 的分歧最小,集合 3 的分歧最大。

    但是我们如何将这种分歧进行量化呢?

    答案就是“信息熵”。

    “信息熵”:

    我们先来介绍信息熵(entropy)的概念。

    1948 年, 香农 提出了“信息熵(shāng)” 的概念,解决了对信息的量化度量问题。 一条信息的信息量大小和它的不确定性有直接的关系。比如说,我们要搞清楚一件非常非常不确定的事,或是我们一无所知的事情,就需要了解大量的信息。相反,如果我们对某件事已经有了较多的了解,我们不需要太多的信息就能把它搞清楚。所以,从这个角度,我们可以认为,信息量的度量就等于不确定性的多少。

    公式:

    变量的不确定性越大,熵也就越大,因此我们定义的“纯度”就代表信息量的多少,我们通过信息熵就可以对各个节点进行构造了。

    我举个简单的例子,假设有 2 个集合

    集合 1,5 次去打篮球,1 次不去打篮球;

    集合 2,3 次去打篮球,3 次不去打篮球。

    在集合 1 中,有 6 次决策,其中打篮球是 5 次,不打篮球是 1 次。那么假设:类别 1 为“打篮球”,即次数为 5;类别 2 为“不打篮球”,即次数为 1。那么节点划分为类别 1 的概率是 5/6,为类别 2 的概率是 1/6,带入上述信息熵公式可以计算得出:

    同样,集合 2 中,也是一共 6 次决策,其中类别 1 中“打篮球”的次数是 3,类别 2“不打篮球”的次数也是 3,那么信息熵为多少呢?我们可以计算得出:

     

    从上面的计算结果中可以看出,信息熵越大,纯度越低。当集合中的所有样本均匀混合时,信息熵最大,纯度最低。

    我们在构造决策树的时候,会基于纯度来构建。而经典的 “不纯度”的指标有三种,分别是信息增益(ID3 算法)、信息增益率(C4.5 算法)以及基尼指数(Cart 算法)。

    1.ID3

    ID3 算法计算的是 信息增益 ,信息增益指的就是划分可以带来纯度的提高,信息熵的下降。

    在计算的过程中,我们会计算每个子节点的归一化信息熵,即按照每个子节点在跟节点中出现的概率,来计算这些子节点的信息熵。所以信息增益的公式可以表示为:

    • 它的计算公式是:父节点的信息熵减去所有子节点的信息熵。

    公式中 D 是根节点,Di 是子节点,Gain(D,a) 中的 a 作为 D 节点的属性选择。

    假设天气 = 晴的时候,会有 5 次去打篮球,5 次不打篮球。其中 D1 刮风 = 是,有 2 次打篮球,1 次不打篮球。D2 刮风 = 否,有 3 次打篮球,4 次不打篮球。那么 a 代表节点的属性,即天气 = 晴。

    针对这个例子,D 作为节点的信息增益为:

    也就是 D 节点的信息熵 -2 个子节点的归一化信息熵。2 个子节点归一化信息熵 =3/10 的 D1 信息熵 +7/10 的 D2 信息熵。

    我们基于 ID3 的算法规则,完整地计算下我们的训练集,训练集中一共有 7 条数据,3 个打篮球,4 个不打篮球,所以根节点的信息熵是:

    如果你将天气作为属性的划分,会有三个叶子节点 D1、D2 和 D3,分别对应的是晴天、阴天和小雨。我们用 + 代表去打篮球,- 代表不去打篮球。于是我们可以用下面的方式来记录 D1,D2,D3:

    D1(天气 = 晴天)={1+,2-}

    D2(天气 = 阴天)={1+,1-}

    D3(天气 = 小雨)={1+,1-}

    我们先分别计算三个叶子节点的信息熵:

    因为 D1 有 3 个记录,D2 有 2 个记录,D3 有 2 个记录,所以 D 中的记录一共是 3+2+2=7,即总数为 7。所以 D1 在 D(根节点)中的概率是 3/7,D2 在根节点的概率是 2/7,D3 在根节点的概率是 2/7。那么作为子节点的归一化信息熵 = 3/70.918+2/71.0+2/7*1.0=0.965。

    因为我们用 ID3 中的信息增益来构造决策树,所以要计算每个节点的信息增益。

    天气属性节点的信息增益 = 根节点信息熵 - 子节点信息熵:

    Gain(D , 天气)=0.985-0.965=0.020。

    同理我们可以计算出其他属性作为根节点的信息增益,它们分别为 :

    Gain(D , 温度)=0.128

    Gain(D , 湿度)=0.020

    Gain(D , 刮风)=0.020

    我们能看出来温度作为属性的信息增益最大。因为 ID3 就是要将信息增益最大的节点作为父节点,这样可以得到纯度高的决策树,所以我们将温度作为根节点。

    然后我们要将第一个叶节点,也就是 D1={1-,2-,3+,4+}进一步进行分裂,往下划分,计算其不同属性(天气、湿度、刮风)作为节点的信息增益,可以得到:

    Gain(D , 天气)=0

    Gain(D , 湿度)=0

    Gain(D , 刮风)=0.0615

    我们能看到刮风为 D1 的节点都可以得到最大的信息增益,这里我们选取刮风作为节点。同理,我们可以按照上面的计算步骤得到完整的决策树。

    下面我们就来构建这颗决策树!

    样本数据集

    还是使用上面这个例子。我们的数据集有10个样本,每个样本有各自的特征['天气','温度','湿度','刮风'],每个特征有各自的属性,比如特征“天气”有属性[“晴”,“雨”,“阴”]。

    # -*- coding:utf-8 -*-
    import time
    import json
    import math
    
    dataset = [['晴', '高', '中', '否', '不打球'],
               ['晴', '高', '中', '是', '不打球'],
               ['阴', '高', '高', '否', '打球'],
               ['雨', '高', '高', '否', '打球'],
               ['雨', '低', '高', '否', '不打球'],
               ['晴', '中', '中', '是', '打球'],
               ['阴', '中', '高', '是', '不打球'], ]
    dataLabels = ['天气', '温度', '湿度', '刮风']
    
    
    class bs(object):
        def __init__(self):
            self.num = 0
    
        def calcShannonEnt(self, dataSet):
            """使用math数学计算模块输出信息熵值"""
            # 样本总个数,这里为个
            totalNum = len(dataSet)
            # 类别集合,字典数据类型
            labelSet = {}
            # 计算每个类别的样本个数
            for dataVec in dataSet:
                # 取出样本最后一列的标签'是否打球'
                label = dataVec[-1]     # 取最后一个
                # 将label添加到labelSet集合,记录“打球”和“不打球”的数量,最后labelSet为:{'不打球': 4, '打球':3 }
                if label not in labelSet.keys():    #
                    labelSet[label] = 0
                labelSet[label] += 1  # 第一个没计入
            shannonEnt = 0
            # 根据学习的公式计算熵值
            for key in labelSet:
                pi = float(labelSet[key]) / totalNum  # 打球和不打球,的数量除以总数量
                shannonEnt -= pi * math.log(pi, 2)  ##############
            return shannonEnt
    
        def splitDataSet(self, dataSet, feat, featvalue):  # feat属性, featvalue属性值
            retDataSet = []
            for dataVec in dataSet:
                if dataVec[feat] == featvalue:
                    splitData = dataVec[:feat]
                    splitData.extend(dataVec[feat + 1:])
                    retDataSet.append(splitData)  # 除了feat对应的列之外,其余列都被纳入新数据集
            return retDataSet
    
        def infogain(self, dataSet, feaNum):
            # 输入样本,计算样本熵值。
            baseShanno = self.calcShannonEnt(dataSet)
            featList = [dataVec[feaNum] for dataVec in dataSet]
            # featList = []
            # for dataVec in dataset:
            #     featList.append(dataVec[feaNum])
            # print(featList)
            # 找出特征属性,如'天气'的属性:“晴”,“阴”,“雨”
            featList = set(featList)    # 去重
            newShanno = 0
            # 通过熵值公式计算以第i个特征进行分类后的熵值,计算子节点熵值。如“晴”在“天气”中的熵值。
            for featValue in featList:
                subDataSet = self.splitDataSet(dataSet, feaNum, featValue)
                prob = len(subDataSet) / float(len(dataSet))
                newShanno += prob * self.calcShannonEnt(subDataSet)     # 熵
            infoGain = baseShanno - newShanno
            return infoGain
    
        def chooseBestFeatToSplit(self, dataSet):
            # 计算总的特征个数,输出 4个
            featNum = len(dataSet[0]) - 1
            # 设定信息增益
            maxInfoGain = 0
            bestFeat = -1
            # 对每一个特征进行分类,找出信息增益最大的特征
            for i in range(featNum):
                infoGain = self.infogain(dataSet, i)
                # 找出信息增益值最大的对应特征
                if infoGain > maxInfoGain:
                    maxInfoGain = infoGain
                    bestFeat = i
            return bestFeat
    
        # 创建决策树
        def createDecideTree(self, dataSet, featName):
            # 数据集的分类类别,“打球”,“不打球”
            classList = [dataVec[-1] for dataVec in dataSet]
            # 等价上面
            # classList = []
            # for dataVec in dataset:
            #     classList.append(dataVec[-1])
            # 所有样本属于同一类时,停止划分,也就是说当前叶节点无法再分时,返回当前类别
            if len(classList) == classList.count(classList[0]):  # 判断 所有类别个数 是否等于不打球的个数
                return classList[0]
            # 选择最好的特征进行划分
            bestFeat = self.chooseBestFeatToSplit(dataSet)
            beatFestName = featName[bestFeat]
            print('可建分支特征有:', beatFestName)
            # 如果当前特征“bestFeat”被划分好了就将这个特征从特征集“dataLabels”中删除,不再参加计算。
            del featName[bestFeat]
            # 以字典形式表示决策树
            DTree = {beatFestName: {}}
            # 根据选择的特征,遍历该特征的所有属性值,再使用createDecideTree()函数划分叶节点。
            featValue = [dataVec[bestFeat] for dataVec in dataSet]
            # 将对应特征的属性按照信息增益大小排列,比如以天气特征排列,就输出{'晴', '雨', '阴'}
            featValue = set(featValue)
            # 将每个特征都进行以上操作,直到不能再继续分类为止。
            for value in featValue:
                subFeatName = featName[:]
                DTree[beatFestName][value] = self.createDecideTree(self.splitDataSet(dataSet, bestFeat, value), subFeatName)
            return DTree
    
        def run(self):
            myTree = self.createDecideTree(dataset, dataLabels)
            print("决策树模型:")
            print(myTree)
    
    
    if __name__ == '__main__':
        fps = bs()
        fps.run()
    

    展开全文
  • 机器学习实战(三)——决策树

    万次阅读 多人点赞 2018-03-13 22:23:50
    决策树 3.1 决策树的构造 3.1.1 信息增益 3.1.2 编写代码计算经验熵 3.1.4利用代码计算信息增益 3.2 决策树的生成和修剪 3.2.1 决策树的构建 1. ID3算法 2. C4.5的生成算法 3. 决策树的剪枝 3.2.2 ...

    决策树

    (声明:本文内容来自机器学习实战和统计学习方法,是两者的整合,并非来自单个书籍)

    决策树(decision tree):是一种基本的分类与回归方法,此处主要讨论分类的决策树。

    在分类问题中,表示基于特征对实例进行分类的过程,可以认为是if-then的集合,也可以认为是定义在特征空间与类空间上的条件概率分布。

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

    用决策树分类:从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点,此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶节点,最后将实例分到叶节点的类中。

    下图为决策树示意图,圆点——内部节点,方框——叶节点

    这里写图片描述

    • 决策树学习的目标:根据给定的训练数据集构建一个决策树模型,使它能够对实例进行正确的分类。

    • 决策树学习的本质:从训练集中归纳出一组分类规则,或者说是由训练数据集估计条件概率模型。

    • 决策树学习的损失函数:正则化的极大似然函数

    • 决策树学习的测试:最小化损失函数

    • 决策树学习的目标:在损失函数的意义下,选择最优决策树的问题。

    • 决策树原理和问答猜测结果游戏相似,根据一系列数据,然后给出游戏的答案。
      在这里插入图片描述

    上图为一个决策树流程图,正方形代表判断模块,椭圆代表终止模块,表示已经得出结论,可以终止运行,左右箭头叫做分支。

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

    3.1 决策树的构造

    决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建。

    1) 开始:构建根节点,将所有训练数据都放在根节点,选择一个最优特征,按着这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。

    2) 如果这些子集已经能够被基本正确分类,那么构建叶节点,并将这些子集分到所对应的叶节点去。

    3)如果还有子集不能够被正确的分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点,如果递归进行,直至所有训练数据子集被基本正确的分类,或者没有合适的特征为止。

    4)每个子集都被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树。

    决策树的特点:

    • 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。
    • 缺点:可能会产生过度匹配的问题
    • 适用数据类型:数值型和标称型

    首先:确定当前数据集上的决定性特征,为了得到该决定性特征,必须评估每个特征,完成测试之后,原始数据集就被划分为几个数据子集,这些数据子集会分布在第一个决策点的所有分支上,如果某个分支下的数据属于同一类型,则当前无序阅读的垃圾邮件已经正确的划分数据分类,无需进一步对数据集进行分割,如果不属于同一类,则要重复划分数据子集,直到所有相同类型的数据均在一个数据子集内。

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

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

    If so return 类标签:
    Else
         寻找划分数据集的最好特征
         划分数据集
         创建分支节点
             for 每个划分的子集
                 调用函数createBranch()并增加返回结果到分支节点中
             return 分支节点
    

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

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

    本节使用ID3算法来划分数据集,该算法处理如何划分数据集,何时停止划分数据集。

    3.1.1 信息增益

    划分数据集的大原则是:将无序数据变得更加有序,但是各种方法都有各自的优缺点,信息论是量化处理信息的分支科学,在划分数据集前后信息发生的变化称为信息增益,获得信息增益最高的特征就是最好的选择,所以必须先学习如何计算信息增益,集合信息的度量方式称为香农熵,或者简称熵。

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

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

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

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

    熵定义为信息的期望值,如果待分类的事物可能划分在多个类之中,则符号 x i x_i xi的信息定义为:
    l ( x i ) = − l o g 2 p ( x i ) l(x_i)=-log_2 p(x_i) l(xi)=log2p(xi)
    其中, p ( x i ) p(x_i) p(xi)是选择该分类的概率。

    为了计算熵,我们需要计算所有类别所有可能值所包含的信息期望值,通过下式得到:
    H = − Σ i = 1 n p ( x i ) l o g 2 p ( x i ) H=-\Sigma_{i=1}^n p(x_i)log_2 p(x_i) H=Σi=1np(xi)log2p(xi)
    其中, n n n为分类数目,熵越大,随机变量的不确定性就越大。

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

    根据此公式计算经验熵H(D),分析贷款申请样本数据表中的数据。最终分类结果只有两类,即放贷和不放贷。根据表中的数据统计可知,在15个数据中,9个数据的结果为放贷,6个数据的结果为不放贷。所以数据集D的经验熵H(D)为:
    H ( D ) = − 9 15 l o g 2 9 15 − 6 15 l o g 2 6 15 = 0.971 H(D)=-\frac{9}{15} log_2\frac{9}{15}-\frac{6}{15} log_2\frac{6}{15}=0.971 H(D)=159log2159156log2156=0.971
    经过计算可知,数据集D的经验熵H(D)的值为0.971。

    在理解信息增益之前,要明确——条件熵

    信息增益表示得知特征X的信息而使得类Y的信息不确定性减少的程度。

    条件熵 H ( Y ∣ X ) H(Y|X) H(YX)表示在已知随机变量X的条件下随机变量Y的不确定性,随机变量X给定的条件下随机变量Y的条件熵(conditional entropy) H(Y|X),定义X给定条件下Y的条件概率分布的熵对X的数学期望:
    H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X)=\sum_{i=1}^n p_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)
    其中, p i = P ( X = x i ) p_i=P(X=x_i) pi=P(X=xi)

    当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的分别为经验熵和经验条件熵,此时如果有0概率,令 0 l o g 0 = 0 0log0=0 0log0=0

    信息增益:信息增益是相对于特征而言的。所以,特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即:

    g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)

    一般地,熵H(D)与条件熵H(D|A)之差成为互信息(mutual information)。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

    信息增益值的大小相对于训练数据集而言的,并没有绝对意义,在分类问题困难时,也就是说在训练数据集经验熵大的时候,信息增益值会偏大,反之信息增益值会偏小,使用信息增益比可以对这个问题进行校正,这是特征选择的另一个标准。

    信息增益比:特征 A A A对训练数据集D的信息增益比 g R ( D , A ) g_R(D,A) gR(D,A)定义为其信息增益 g ( D , A ) g(D,A) g(D,A)与训练数据集 D D D的经验熵之比:

    g R ( D , A ) = g ( D , A ) H ( D ) g_R(D,A)=\frac{g(D,A)}{H(D)} gR(D,A)=H(D)g(D,A)

    3.1.2 编写代码计算经验熵

    在这里插入图片描述
    在编写代码之前,我们先对数据集进行属性标注。

    • 年龄:0代表青年,1代表中年,2代表老年;
    • 有工作:0代表否,1代表是;
    • 有自己的房子:0代表否,1代表是;
    • 信贷情况:0代表一般,1代表好,2代表非常好;
    • 类别(是否给贷款):no代表否,yes代表是。

    创建数据集,计算经验熵的代码如下:

    from math import log
    
    """
    函数说明:创建测试数据集
    Parameters:无
    Returns:
        dataSet:数据集
        labels:分类属性
    Modify:
        2018-03-12
    
    """
    def creatDataSet():
        # 数据集
        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
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def calcShannonEnt(dataSet):
        #返回数据集行数
        numEntries=len(dataSet)
        #保存每个标签(label)出现次数的字典
        labelCounts={}
        #对每组特征向量进行统计
        for featVec in dataSet:
            currentLabel=featVec[-1]                     #提取标签信息
            if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
                labelCounts[currentLabel]=0
            labelCounts[currentLabel]+=1                 #label计数
    
        shannonEnt=0.0                                   #经验熵
        #计算经验熵
        for key in labelCounts:
            prob=float(labelCounts[key])/numEntries      #选择该标签的概率
            shannonEnt-=prob*log(prob,2)                 #利用公式计算
        return shannonEnt                                #返回经验熵
    
    #main函数
    if __name__=='__main__':
        dataSet,features=creatDataSet()
        print(dataSet)
        print(calcShannonEnt(dataSet))
    

    结果:

    0个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.3630个特征的增益为0.2521个特征的增益为0.9182个特征的增益为0.474
    {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
    
    

    3.1.4利用代码计算信息增益

    
    from math import log
    
    """
    函数说明:创建测试数据集
    Parameters:无
    Returns:
        dataSet:数据集
        labels:分类属性
    Modify:
        2018-03-12
    
    """
    def creatDataSet():
        # 数据集
        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
    
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def calcShannonEnt(dataSet):
        #返回数据集行数
        numEntries=len(dataSet)
        #保存每个标签(label)出现次数的字典
        labelCounts={}
        #对每组特征向量进行统计
        for featVec in dataSet:
            currentLabel=featVec[-1]                     #提取标签信息
            if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
                labelCounts[currentLabel]=0
            labelCounts[currentLabel]+=1                 #label计数
    
        shannonEnt=0.0                                   #经验熵
        #计算经验熵
        for key in labelCounts:
            prob=float(labelCounts[key])/numEntries      #选择该标签的概率
            shannonEnt-=prob*log(prob,2)                 #利用公式计算
        return shannonEnt                                #返回经验熵
    
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:信息增益最大特征的索引值
    Modify:
        2018-03-12
    
    """
    
    
    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]
            #创建set集合{},元素不可重复
            uniqueVals = set(featList)
            #经验条件熵
            newEntropy = 0.0
            #计算信息增益
            for value in uniqueVals:
                #subDataSet划分后的子集
                subDataSet = splitDataSet(dataSet, i, value)
                #计算子集的概率
                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
    
    """
    函数说明:按照给定特征划分数据集
    Parameters:
        dataSet:待划分的数据集
        axis:划分数据集的特征
        value:需要返回的特征的值
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def splitDataSet(dataSet,axis,value):
        retDataSet=[]
        for featVec in dataSet:
            if featVec[axis]==value:
                reducedFeatVec=featVec[:axis]
                reducedFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reducedFeatVec)
        return retDataSet
    
    
    #main函数
    if __name__=='__main__':
        dataSet,features=creatDataSet()
        # print(dataSet)
        # print(calcShannonEnt(dataSet))
        print("最优索引值:"+str(chooseBestFeatureToSplit(dataSet)))
    
    

    结果:

    0个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.363
    最优索引值:2
    
    

    对比我们自己计算的结果,发现结果完全正确!最优特征的索引值为2,也就是特征A3(有自己的房子)。

    3.2 决策树的生成和修剪

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

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

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

    3.2.1 决策树的构建

    1. ID3算法

    ID3算法的核心是在决策树各个结点上对应信息增益准则选择特征,递归地构建决策树。

    具体方法是:

    1)从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征。

    2)由该特征的不同取值建立子节点,再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止;

    3)最后得到一个决策树。

    ID3相当于用极大似然法进行概率模型的选择

    算法步骤:

    这里写图片描述
    这里写图片描述

    分析数据:

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

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

    g ( D 2 , A 1 ) = H ( D 2 ) − H ( D 2 ∣ A 1 ) = 0.251 g(D2,A1)=H(D2)-H(D2|A1)=0.251 g(D2,A1)=H(D2)H(D2A1)=0.251
    g ( D 2 , A 2 ) = H ( D 2 ) − H ( D 2 ∣ A 2 ) = 0.918 g(D2,A2)=H(D2)-H(D2|A2)=0.918 g(D2,A2)=H(D2)H(D2A2)=0.918
    g ( D 2 , A 3 ) = H ( D 2 ) − H ( D 2 ∣ A 3 ) = 0.474 g(D2,A3)=H(D2)-H(D2|A3)=0.474 g(D2,A3)=H(D2)H(D2A3)=0.474

    根据计算,选择信息增益最大的A2作为节点的特征,由于其有两个取值可能,所以引出两个子节点:

    ①对应“是”(有工作),包含三个样本,属于同一类,所以是一个叶子节点,类标记为“是”

    ②对应“否”(无工作),包含六个样本,输入同一类,所以是一个叶子节点,类标记为“否”

    这样就生成一个决策树,该树只用了两个特征(有两个内部节点),生成的决策树如下图所示:

    在这里插入图片描述

    2. C4.5的生成算法

    与ID3算法相似,但是做了改进,将信息增益比作为选择特征的标准。

    这里写图片描述

    递归构建决策树:

    从数据集构造决策树算法所需的子功能模块工作原理如下:得到原始数据集,然后基于最好的属性值划分数据集,由于特征值可能多于两个,因此可能存在大于两个分支的数据集划分,第一次划分之后,数据将被向下传递到树分支的下一个节点,在此节点在此划分数据,因此可以使用递归的原则处理数据集。

    递归结束的条件是:

    程序完全遍历所有划分数据集的属性,或者每个分支下的所有实例都具有相同的分类,如果所有实例具有相同的分类,则得到一个叶子节点或者终止块,任何到达叶子节点的数据必然属于叶子节点的分类。

    编写ID3算法的代码

    from math import log
    import operator
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def calcShannonEnt(dataSet):
        #返回数据集行数
        numEntries=len(dataSet)
        #保存每个标签(label)出现次数的字典
        labelCounts={}
        #对每组特征向量进行统计
        for featVec in dataSet:
            currentLabel=featVec[-1]                     #提取标签信息
            if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
                labelCounts[currentLabel]=0
            labelCounts[currentLabel]+=1                 #label计数
    
        shannonEnt=0.0                                   #经验熵
        #计算经验熵
        for key in labelCounts:
            prob=float(labelCounts[key])/numEntries      #选择该标签的概率
            shannonEnt-=prob*log(prob,2)                 #利用公式计算
        return shannonEnt                                #返回经验熵
    
    """
    函数说明:创建测试数据集
    Parameters:无
    Returns:
        dataSet:数据集
        labels:分类属性
    Modify:
        2018-03-13
    
    """
    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
    
    """
    函数说明:按照给定特征划分数据集
    
    Parameters:
        dataSet:待划分的数据集
        axis:划分数据集的特征
        value:需要返回的特征值
    Returns:
        无
    Modify:
        2018-03-13
    
    """
    def splitDataSet(dataSet,axis,value):
        #创建返回的数据集列表
        retDataSet=[]
        #遍历数据集
        for featVec in dataSet:
            if featVec[axis]==value:
                #去掉axis特征
                reduceFeatVec=featVec[:axis]
                #将符合条件的添加到返回的数据集
                reduceFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reduceFeatVec)
        #返回划分后的数据集
        return retDataSet
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:信息增益最大特征的索引值
    Modify:
        2018-03-13
    
    """
    
    
    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]
            #创建set集合{},元素不可重复
            uniqueVals = set(featList)
            #经验条件熵
            newEntropy = 0.0
            #计算信息增益
            for value in uniqueVals:
                #subDataSet划分后的子集
                subDataSet = splitDataSet(dataSet, i, value)
                #计算子集的概率
                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
    
    """
    函数说明:统计classList中出现次数最多的元素(类标签)
    Parameters:
        classList:类标签列表
    Returns:
        sortedClassCount[0][0]:出现次数最多的元素(类标签)
    Modify:
        2018-03-13
    
    """
    def majorityCnt(classList):
        classCount={}
        #统计classList中每个元素出现的次数
        for vote in classList:
            if vote not in classCount.keys():
                classCount[vote]=0
                classCount[vote]+=1
            #根据字典的值降序排列
            sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
            return sortedClassCount[0][0]
    
    """
    函数说明:创建决策树
    
    Parameters:
        dataSet:训练数据集
        labels:分类属性标签
        featLabels:存储选择的最优特征标签
    Returns:
        myTree:决策树
    Modify:
        2018-03-13
    
    """
    def createTree(dataSet,labels,featLabels):
        #取分类标签(是否放贷:yes or no)
        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]
        featLabels.append(bestFeatLabel)
        #根据最优特征的标签生成树
        myTree={bestFeatLabel:{}}
        #删除已经使用的特征标签
        del(labels[bestFeat])
        #得到训练集中所有最优特征的属性值
        featValues=[example[bestFeat] for example in dataSet]
        #去掉重复的属性值
        uniqueVls=set(featValues)
        #遍历特征,创建决策树
        for value in uniqueVls:
            myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),
                                                   labels,featLabels)
        return myTree
    
    if __name__=='__main__':
        dataSet,labels=createDataSet()
        featLabels=[]
        myTree=createTree(dataSet,labels,featLabels)
        print(myTree)
    
    
    
    

    结果:

    0个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.3630个特征的增益为0.2521个特征的增益为0.9182个特征的增益为0.474
    {'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}}
    

    3. 决策树的剪枝

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

    剪枝(pruning):从已经生成的树上裁掉一些子树或叶节点,并将其根节点或父节点作为新的叶子节点,从而简化分类树模型。

    实现方式:极小化决策树整体的损失函数或代价函数来实现

    决策树学习的损失函数定义为:

    C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_\alpha(T)=\sum_{t=1}^{|T|}N_tH_t(T)+\alpha|T| Cα(T)=t=1TNtHt(T)+αT

    其中:

    参数意义
    T T T表示这棵子树的叶子节点,
    H t ( T ) H_t(T) Ht(T)表示第 t t t个叶子的熵,这里写图片描述
    N t N_t Nt表示该叶子所含的训练样例的个数,
    α \alpha α惩罚系数,
    $T

    因为:
    这里写图片描述
    所以有: C α ( T ) = C ( T ) + α ∣ T ∣ C_\alpha(T)=C(T)+\alpha|T| Cα(T)=C(T)+αT

    其中:

    参数意义
    C ( T ) C(T) C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度;
    $T
    α \alpha α参数 α &gt; = 0 \alpha&gt;=0 α>=0控制两者之间的影响,较大的 α \alpha α促使选择较简单的模型(树),较小的 α \alpha α促使选择较复杂的模型(树), α = 0 \alpha=0 α=0意味着只考虑模型与训练数据的拟合程度,不考虑模型的复杂度。

    剪枝就是当 α \alpha α确定时,选择损失函数最小的模型,即损失函数最小的子树。

    • α \alpha α值确定时,子树越大,往往与训练数据的拟合越好,但是模型的复杂度越高;
    • 子树越小,模型的复杂度就越低,但是往往与训练数据的拟合不好
    • 损失函数正好表示了对两者的平衡。

    损失函数认为对于每个分类终点(叶子节点)的不确定性程度就是分类的损失因子,而叶子节点的个数是模型的复杂程度,作为惩罚项,损失函数的第一项是样本的训练误差,第二项是模型的复杂度。如果一棵子树的损失函数值越大,说明这棵子树越差,因此我们希望让每一棵子树的损失函数值尽可能得小,损失函数最小化就是用正则化的极大似然估计进行模型选择的过程。

    决策树的剪枝过程(泛化过程)就是从叶子节点开始递归,记其父节点将所有子节点回缩后的子树为Tb(分类值取类别比例最大的特征值),未回缩的子树为 T a Ta Ta,如果 C α ( T a ) ≥ C α ( T b ) C_α(T_a)≥C_α(T_b) Cα(Ta)Cα(Tb)说明回缩后使得损失函数减小了,那么应该使这棵子树回缩,递归直到无法回缩为止,这样使用“贪心”的思想进行剪枝可以降低损失函数值,也使决策树得到泛化。

    可以看出,决策树的生成只是考虑通过提高信息增益对训练数据进行更好的拟合,而决策树剪枝通过优化损失函数还考虑了减小模型复杂度。

    公式 C α ( T ) = C ( T ) + α ∣ T ∣ C_\alpha(T)=C(T)+\alpha|T| Cα(T)=C(T)+αT定义的损失函数的极小化等价于正则化的极大似然估计,剪枝过程示意图:
    这里写图片描述

    这里写图片描述
    这里写图片描述
    这里写图片描述

    决策树算法很容易过拟合(overfitting),剪枝算法就是用来防止决策树过拟合,提高泛华性能的方法。

    剪枝分为预剪枝与后剪枝。

    预剪枝是指在决策树的生成过程中,对每个节点在划分前先进行评估,若当前的划分不能带来泛化性能的提升,则停止划分,并将当前节点标记为叶节点。

    后剪枝是指先从训练集生成一颗完整的决策树,然后自底向上对非叶节点进行考察,若将该节点对应的子树替换为叶节点,能带来泛化性能的提升,则将该子树替换为叶节点。

    那么怎么来判断是否带来泛化性能的提升那?最简单的就是留出法,即预留一部分数据作为验证集来进行性能评估。

    3.2.2 决策树可视化

    这里代码都是关于Matplotlib的,如果对于Matplotlib不了解的,可以先学习下,Matplotlib的内容这里就不再累述。可视化需要用到的函数:

    getNumLeafs:获取决策树叶子结点的数目

    getTreeDepth:获取决策树的层数

    plotNode:绘制结点

    plotMidText:标注有向边属性值

    plotTree:绘制决策树

    createPlot:创建绘制面板

    from math import log
    import operator
    from matplotlib.font_manager import FontProperties
    import matplotlib.pyplot as plt
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def calcShannonEnt(dataSet):
        #返回数据集行数
        numEntries=len(dataSet)
        #保存每个标签(label)出现次数的字典
        labelCounts={}
        #对每组特征向量进行统计
        for featVec in dataSet:
            currentLabel=featVec[-1]                     #提取标签信息
            if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
                labelCounts[currentLabel]=0
            labelCounts[currentLabel]+=1                 #label计数
    
        shannonEnt=0.0                                   #经验熵
        #计算经验熵
        for key in labelCounts:
            prob=float(labelCounts[key])/numEntries      #选择该标签的概率
            shannonEnt-=prob*log(prob,2)                 #利用公式计算
        return shannonEnt                                #返回经验熵
    
    """
    函数说明:创建测试数据集
    Parameters:无
    Returns:
        dataSet:数据集
        labels:分类属性
    Modify:
        2018-03-13
    
    """
    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
    
    """
    函数说明:按照给定特征划分数据集
    
    Parameters:
        dataSet:待划分的数据集
        axis:划分数据集的特征
        value:需要返回的特征值
    Returns:
        无
    Modify:
        2018-03-13
    
    """
    def splitDataSet(dataSet,axis,value):
        #创建返回的数据集列表
        retDataSet=[]
        #遍历数据集
        for featVec in dataSet:
            if featVec[axis]==value:
                #去掉axis特征
                reduceFeatVec=featVec[:axis]
                #将符合条件的添加到返回的数据集
                reduceFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reduceFeatVec)
        #返回划分后的数据集
        return retDataSet
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:信息增益最大特征的索引值
    Modify:
        2018-03-13
    
    """
    
    
    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]
            #创建set集合{},元素不可重复
            uniqueVals = set(featList)
            #经验条件熵
            newEntropy = 0.0
            #计算信息增益
            for value in uniqueVals:
                #subDataSet划分后的子集
                subDataSet = splitDataSet(dataSet, i, value)
                #计算子集的概率
                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
    
    """
    函数说明:统计classList中出现次数最多的元素(类标签)
    Parameters:
        classList:类标签列表
    Returns:
        sortedClassCount[0][0]:出现次数最多的元素(类标签)
    Modify:
        2018-03-13
    
    """
    def majorityCnt(classList):
        classCount={}
        #统计classList中每个元素出现的次数
        for vote in classList:
            if vote not in classCount.keys():
                classCount[vote]=0
                classCount[vote]+=1
            #根据字典的值降序排列
            sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
            return sortedClassCount[0][0]
    
    """
    函数说明:创建决策树
    
    Parameters:
        dataSet:训练数据集
        labels:分类属性标签
        featLabels:存储选择的最优特征标签
    Returns:
        myTree:决策树
    Modify:
        2018-03-13
    
    """
    def createTree(dataSet,labels,featLabels):
        #取分类标签(是否放贷:yes or no)
        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]
        featLabels.append(bestFeatLabel)
        #根据最优特征的标签生成树
        myTree={bestFeatLabel:{}}
        #删除已经使用的特征标签
        del(labels[bestFeat])
        #得到训练集中所有最优特征的属性值
        featValues=[example[bestFeat] for example in dataSet]
        #去掉重复的属性值
        uniqueVls=set(featValues)
        #遍历特征,创建决策树
        for value in uniqueVls:
            myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),
                                                   labels,featLabels)
        return myTree
    
    """
    函数说明:获取决策树叶子节点的数目
    
    Parameters:
        myTree:决策树
    Returns:
        numLeafs:决策树的叶子节点的数目
    Modify:
        2018-03-13
    
    """
    
    def getNumLeafs(myTree):
        numLeafs=0
        firstStr=next(iter(myTree))
        secondDict=myTree[firstStr]
        for key in secondDict.keys():
            if type(secondDict[key]).__name__=='dict':
                numLeafs+=getNumLeafs(secondDict[key])
            else: numLeafs+=1
        return numLeafs
    
    """
    函数说明:获取决策树的层数
    
    Parameters:
        myTree:决策树
    Returns:
        maxDepth:决策树的层数
    
    Modify:
        2018-03-13
    """
    def getTreeDepth(myTree):
        maxDepth = 0                                                #初始化决策树深度
        firstStr = next(iter(myTree))                                #python3中myTree.keys()返回的是dict_keys,不在是list,所以不能使用myTree.keys()[0]的方法获取结点属性,可以使用list(myTree.keys())[0]
        secondDict = myTree[firstStr]                                #获取下一个字典
        for key in secondDict.keys():
            if type(secondDict[key]).__name__=='dict':                #测试该结点是否为字典,如果不是字典,代表此结点为叶子结点
                thisDepth = 1 + getTreeDepth(secondDict[key])
            else:   thisDepth = 1
            if thisDepth > maxDepth: maxDepth = thisDepth            #更新层数
        return maxDepth
    
    """
    函数说明:绘制结点
    
    Parameters:
        nodeTxt - 结点名
        centerPt - 文本位置
        parentPt - 标注的箭头位置
        nodeType - 结点格式
    Returns:
        无
    Modify:
        2018-03-13
    """
    def plotNode(nodeTxt, centerPt, parentPt, nodeType):
        arrow_args = dict(arrowstyle="<-")                                            #定义箭头格式
        font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)        #设置中文字体
        createPlot.ax1.annotate(nodeTxt, xy=parentPt,  xycoords='axes fraction',    #绘制结点
            xytext=centerPt, textcoords='axes fraction',
            va="center", ha="center", bbox=nodeType, arrowprops=arrow_args, FontProperties=font)
    
    """
    函数说明:标注有向边属性值
    
    Parameters:
        cntrPt、parentPt - 用于计算标注位置
        txtString - 标注的内容
    Returns:
        无
    Modify:
        2018-03-13
    """
    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)
    
    """
    函数说明:绘制决策树
    
    Parameters:
        myTree - 决策树(字典)
        parentPt - 标注的内容
        nodeTxt - 结点名
    Returns:
        无
    Modify:
        2018-03-13
    """
    def plotTree(myTree, parentPt, nodeTxt):
        decisionNode = dict(boxstyle="sawtooth", fc="0.8")                                        #设置结点格式
        leafNode = dict(boxstyle="round4", fc="0.8")                                            #设置叶结点格式
        numLeafs = getNumLeafs(myTree)                                                          #获取决策树叶结点数目,决定了树的宽度
        depth = getTreeDepth(myTree)                                                            #获取决策树层数
        firstStr = next(iter(myTree))                                                            #下个字典
        cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/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.totalW
                plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
                plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
        plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
    
    """
    函数说明:创建绘制面板
    
    Parameters:
        inTree - 决策树(字典)
    Returns:
        无
    Modify:
        2018-03-13
    """
    def createPlot(inTree):
        fig = plt.figure(1, facecolor='white')#创建fig
        fig.clf()#清空fig
        axprops = dict(xticks=[], yticks=[])
        createPlot.ax1 = plt.subplot(111, frameon=False, **axprops)#去掉x、y轴
        plotTree.totalW = float(getNumLeafs(inTree))#获取决策树叶结点数目
        plotTree.totalD = float(getTreeDepth(inTree))#获取决策树层数
        plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0#x偏移
        plotTree(inTree, (0.5,1.0), '')#绘制决策树
        plt.show()#显示绘制结果
    
    if __name__ == '__main__':
        dataSet, labels = createDataSet()
        featLabels = []
        myTree = createTree(dataSet, labels, featLabels)
        print(myTree)
        createPlot(myTree)
    
    if __name__=='__main__':
        dataSet,labels=createDataSet()
        featLabels=[]
        myTree=createTree(dataSet,labels,featLabels)
        print(myTree)
    
    

    这里写图片描述

    3.2.3 ID3、C4.5、CART的区别

    这三个是非常著名的决策树算法。简单粗暴来说,ID3 使用信息增益作为选择特征的准则;C4.5 使用信息增益比作为选择特征的准则;CART 使用 Gini 指数作为选择特征的准则。

    一、ID3
    熵表示的是数据中包含的信息量大小。熵越小,数据的纯度越高,也就是说数据越趋于一致,这是我们希望的划分之后每个子节点的样子。

    信息增益 = 划分前熵 - 划分后熵。信息增益越大,则意味着使用属性 a 来进行划分所获得的 “纯度提升” 越大 **。也就是说,用属性 a 来划分训练集,得到的结果中纯度比较高。

    ID3 仅仅适用于二分类问题。ID3 仅仅能够处理离散属性。

    二、C4.5

    C4.5 克服了 ID3 仅仅能够处理离散属性的问题,以及信息增益偏向选择取值较多特征的问题,使用信息增益比来选择特征。信息增益比 = 信息增益 / 划分前熵 选择信息增益比最大的作为最优特征。

    C4.5 处理连续特征是先将特征取值排序,以连续两个值中间值作为划分标准。尝试每一种划分,并计算修正后的信息增益,选择信息增益最大的分裂点作为该属性的分裂点。

    三、CART

    CART 与 ID3,C4.5 不同之处在于 CART 生成的树必须是二叉树。也就是说,无论是回归还是分类问题,无论特征是离散的还是连续的,无论属性取值有多个还是两个,内部节点只能根据属性值进行二分。

    CART 的全称是分类与回归树。从这个名字中就应该知道,CART 既可以用于分类问题,也可以用于回归问题。

    回归树中,使用平方误差最小化准则来选择特征并进行划分。每一个叶子节点给出的预测值,是划分到该叶子节点的所有样本目标值的均值,这样只是在给定划分的情况下最小化了平方误差。

    要确定最优化分,还需要遍历所有属性,以及其所有的取值来分别尝试划分并计算在此种划分情况下的最小平方误差,选取最小的作为此次划分的依据。由于回归树生成使用平方误差最小化准则,所以又叫做最小二乘回归树。

    分类树种,使用 Gini 指数最小化准则来选择特征并进行划分;

    Gini 指数表示集合的不确定性,或者是不纯度。基尼指数越大,集合不确定性越高,不纯度也越大。这一点和熵类似。另一种理解基尼指数的思路是,基尼指数是为了最小化误分类的概率。

    信息增益 vs 信息增益比

    之所以引入了信息增益比,是由于信息增益的一个缺点。那就是:信息增益总是偏向于选择取值较多的属性。信息增益比在此基础上增加了一个罚项,解决了这个问题。

    Gini 指数 vs 熵

    既然这两个都可以表示数据的不确定性,不纯度。那么这两个有什么区别那?

    • Gini 指数的计算不需要对数运算,更加高效;
    • Gini 指数更偏向于连续属性,熵更偏向于离散属性。

    3.3 使用决策树进行分类

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

    from math import log
    import operator
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:经验熵
    Modify:
        2018-03-12
    
    """
    def calcShannonEnt(dataSet):
        #返回数据集行数
        numEntries=len(dataSet)
        #保存每个标签(label)出现次数的字典
        labelCounts={}
        #对每组特征向量进行统计
        for featVec in dataSet:
            currentLabel=featVec[-1]                     #提取标签信息
            if currentLabel not in labelCounts.keys():   #如果标签没有放入统计次数的字典,添加进去
                labelCounts[currentLabel]=0
            labelCounts[currentLabel]+=1                 #label计数
    
        shannonEnt=0.0                                   #经验熵
        #计算经验熵
        for key in labelCounts:
            prob=float(labelCounts[key])/numEntries      #选择该标签的概率
            shannonEnt-=prob*log(prob,2)                 #利用公式计算
        return shannonEnt                                #返回经验熵
    
    """
    函数说明:创建测试数据集
    Parameters:无
    Returns:
        dataSet:数据集
        labels:分类属性
    Modify:
        2018-03-13
    
    """
    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
    
    """
    函数说明:按照给定特征划分数据集
    
    Parameters:
        dataSet:待划分的数据集
        axis:划分数据集的特征
        value:需要返回的特征值
    Returns:
        无
    Modify:
        2018-03-13
    
    """
    def splitDataSet(dataSet,axis,value):
        #创建返回的数据集列表
        retDataSet=[]
        #遍历数据集
        for featVec in dataSet:
            if featVec[axis]==value:
                #去掉axis特征
                reduceFeatVec=featVec[:axis]
                #将符合条件的添加到返回的数据集
                reduceFeatVec.extend(featVec[axis+1:])
                retDataSet.append(reduceFeatVec)
        #返回划分后的数据集
        return retDataSet
    
    """
    函数说明:计算给定数据集的经验熵(香农熵)
    Parameters:
        dataSet:数据集
    Returns:
        shannonEnt:信息增益最大特征的索引值
    Modify:
        2018-03-13
    
    """
    
    
    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]
            #创建set集合{},元素不可重复
            uniqueVals = set(featList)
            #经验条件熵
            newEntropy = 0.0
            #计算信息增益
            for value in uniqueVals:
                #subDataSet划分后的子集
                subDataSet = splitDataSet(dataSet, i, value)
                #计算子集的概率
                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
    
    """
    函数说明:统计classList中出现次数最多的元素(类标签)
    Parameters:
        classList:类标签列表
    Returns:
        sortedClassCount[0][0]:出现次数最多的元素(类标签)
    Modify:
        2018-03-13
    
    """
    def majorityCnt(classList):
        classCount={}
        #统计classList中每个元素出现的次数
        for vote in classList:
            if vote not in classCount.keys():
                classCount[vote]=0
                classCount[vote]+=1
            #根据字典的值降序排列
            sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
            return sortedClassCount[0][0]
    
    """
    函数说明:创建决策树
    
    Parameters:
        dataSet:训练数据集
        labels:分类属性标签
        featLabels:存储选择的最优特征标签
    Returns:
        myTree:决策树
    Modify:
        2018-03-13
    
    """
    def createTree(dataSet,labels,featLabels):
        #取分类标签(是否放贷:yes or no)
        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]
        featLabels.append(bestFeatLabel)
        #根据最优特征的标签生成树
        myTree={bestFeatLabel:{}}
        #删除已经使用的特征标签
        del(labels[bestFeat])
        #得到训练集中所有最优特征的属性值
        featValues=[example[bestFeat] for example in dataSet]
        #去掉重复的属性值
        uniqueVls=set(featValues)
        #遍历特征,创建决策树
        for value in uniqueVls:
            myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeat,value),
                                                   labels,featLabels)
        return myTree
    
    
    
    """
    使用决策树进行分类
    Parameters:
        inputTree;已经生成的决策树
        featLabels:存储选择的最优特征标签
        testVec:测试数据列表,顺序对应最优特征标签
    Returns:
        classLabel:分类结果
    Modify:2018-03-13
    
    """
    def 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 classLabel
    
    if __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个特征的增益为0.0831个特征的增益为0.3242个特征的增益为0.4203个特征的增益为0.3630个特征的增益为0.2521个特征的增益为0.9182个特征的增益为0.474
    放贷
    

    3.4 决策树的存储

    构造决策树是很耗时的任务,即使处理很小的数据集,如前面的样本数据,也要花费几秒的时间,如果数据集很大,将会耗费很多计算时间。然而用创建好的决策树解决分类问题,则可以很快完成。因此,为了节省计算时间,最好能够在每次执行分类时调用已经构造好的决策树。为了解决这个问题,需要使用Python模块pickle序列化对象。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。

    假设我们已经得到决策树{'有自己的房子': {0: {'有工作': {0: 'no', 1: 'yes'}}, 1: 'yes'}},使用pickle.dump存储决策树。

    import pickle
    """
    函数说明:存储决策树
    Parameters:
        inputTree:已经生成的决策树
        filename:决策树的存储文件名
    Returns:
        无
    Modify:
        2018-03-13
    
    """
    def storeTree(inputTree,filename):
        with open(filename,'wb') as fw:
            pickle.dump(inputTree,fw)
    
    if __name__=='__main__':
        myTree={'有自己的房子':{0:{'有工作':{0:'no',1:'yes'}},1:'yes'}}
        storeTree(myTree,'classifierStorage.txt')
    
    
    
    

    运行代码,在该Python文件的相同目录下,会生成一个名为classifierStorage.txt的txt文件,这个文件二进制存储着我们的决策树。

    很简单使用pickle.load进行载入即可,编写代码如下:

    import pickle
    
    """
    函数说明:读取决策树
    
    Parameters:
        filename:决策树的存储文件名
    Returns:
        pickle.load(fr):决策树字典
    Modify:
        2018-03-13
    """
    def grabTree(filename):
        fr = open(filename, 'rb')
        return pickle.load(fr)
    
    if __name__ == '__main__':
        myTree = grabTree('classifierStorage.txt')
        print(myTree)
    

    3.5 sklearn——使用决策树预测隐形眼镜类型

    数据集下载

    步骤:

    收集数据:使用书中提供的小型数据集

    准备数据:对文本中的数据进行预处理,如解析数据行

    分析数据:快速检查数据,并使用createPlot()函数绘制最终的树形图

    训练决策树:使用createTree()函数训练

    测试决策树:编写简单的测试函数验证决策树的输出结果&绘图结果

    使用决策树:这部分可选择将训练好的决策树进行存储,以便随时使用

    这里写图片描述

    3.5.1 使用sklearn构建决策树

    官方网站

    sklearn.tree——提供了决策树模型,用于解决分类和回归问题

    class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)[source]
    

    参数说明如下:

    criterion:特征选择标准,可选参数,默认是gini,可以设置为entropy。gini是基尼不纯度,是将来自集合的某种结果随机应用于某一数据项的预期误差率,是一种基于统计的思想。entropy是香农熵,也就是上篇文章讲过的内容,是一种基于信息论的思想。Sklearn把gini设为默认参数,应该也是做了相应的斟酌的,精度也许更高些?ID3算法使用的是entropy,CART算法使用的则是gini。

    splitter:特征划分点选择标准,可选参数,默认是best,可以设置为random。每个结点的选择策略。best参数是根据算法选择最佳的切分特征,例如gini、entropy。random随机的在部分划分点中找局部最优的划分点。默认的”best”适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐”random”。

    max_features:划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况:
    如果max_features是整型的数,则考虑max_features个特征;
    如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;
    如果max_features设为auto,那么max_features = sqrt(n_features);
    如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;
    如果max_features设为log2,那么max_features = log2(n_features);
    如果max_features设为None,那么max_features = n_features,也就是所有特征都用。
    一般来说,如果样本特征数不多,比如小于50,我们用默认的”None”就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。

    max_depth:决策树最大深,可选参数,默认是None。这个参数是这是树的层数的。层数的概念就是,比如在贷款的例子中,决策树的层数是2层。如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。或者如果设置了min_samples_slipt参数,那么直到少于min_smaples_split个样本为止。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。

    min_samples_split:内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
    min_weight_fraction_leaf:叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。

    max_leaf_nodes:最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。

    class_weight:类别权重,可选参数,默认是None,也可以字典、字典列表、balanced。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。

    random_state:可选参数,默认是None。随机数种子。如果是证书,那么random_state会作为随机数生成器的随机数种子。随机数种子,如果没有设置随机数,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。

    min_impurity_split:节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点 。

    presort:数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。
    除了这些参数要注意以外,其他在调参时的注意点有:

    当样本数量少但是样本特征非常多的时候,决策树很容易过拟合,一般来说,样本数比特征数多一些会比较容易建立健壮的模型
    如果样本数量少但是样本特征非常多,在拟合决策树模型前,推荐先做维度规约,比如主成分分析(PCA),特征选择(Losso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。
    推荐多用决策树的可视化,同时先限制决策树的深度,这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。
    在训练模型时,注意观察样本的类别情况(主要指分类树),如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。
    决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。
    如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。
    sklearn.tree.DecisionTreeClassifier()提供了一些方法供我们使用,如下图所示:
    这里写图片描述

    数据预处理:将string类型的数据集进行编码:
    1)LabelEncoder:将字符串转换为增量值
    2)OneHotEncoder:使用One-of-K算法将字符串转换为整数

    为了对string类型的数据序列化,需要先生成pandas数据,这样方便我们的序列化工作。这里我使用的方法是,原始数据->字典->pandas数据,编写代码如下:

    import pandas as pd
    from sklearn.preprocessing import LabelEncoder
    
    # import pydotplus
    # from sklearn.externals.six import StringIO
    
    if __name__ == '__main__':
        # 加载文件
        with open('lenses.txt', 'r') as fr:
            # 处理文件
            lenses = [inst.strip().split('\t') for inst in fr.readlines()]
        # 提取每组数据的类别,保存在列表里
        lenses_target = []
        for each in lenses:
            lenses_target.append(each[-1])
        # 特征标签
        lensesLabels = ['age', 'prescript', 'astigmatic', 'tearRate']
        # 保存lenses数据的临时列表
        lenses_list = []
        # 保存lenses数据的字典,用于生成pandas
        lenses_dict = {}
        # 提取信息,生成字典
        for each_label in lensesLabels:
            for each in lenses:
                lenses_list.append(each[lensesLabels.index(each_label)])
            lenses_dict[each_label] = lenses_list
            lenses_list = []
            # 打印字典信息
        print(lenses_dict)
        #生成pandas.DataFrame
        lenses_pd = pd.DataFrame(lenses_dict)
        # print(lenses_pd)
        # 生成pandas.DataFrame
        lenses_pd = pd.DataFrame(lenses_dict)
        # 打印pandas.DataFrame
        print(lenses_pd)
        # 创建LabelEncoder()对象,用于序列化
        le = LabelEncoder()
        # 为每一列序列化
        for col in lenses_pd.columns:
            lenses_pd[col] = le.fit_transform(lenses_pd[col])
        print(lenses_pd)
    

    <img src="//img-blog.csdn.net/20180313202656991?watermark/2/text/Ly9ibG9nLmNzZG4ubmV0L2ppYW95YW5nd20=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"width=60%alt=""/><img src="//img-blog.csdn.net/20180313202846875?watermark/2/text/Ly9ibG9nLmNzZG4ubmV0L2ppYW95YW5nd20=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70"width=60%alt=""/>

    3.5.2 使用Graphviz可视化决策树

    1)pydotplus安装

    直接在anaconda prompt下输入:

    pip install pydotplus
    

    2)Graphviz下载

    安装步骤安装,之后win+R->sysdm.cpl
    这里写图片描述
    在“环境变量”中的“系统变量”中找到“PATH”,之后将路径C:\Anaconda\pkgs\graphviz-2.38.0-4\Library\bin添加到后面,重启Pycharm即可。

    3.6 总结

    优点:

    • 易于理解和解释,决策树可以可视化。
    • 几乎不需要数据预处理。其他方法经常需要数据标准化,创建虚拟变量和删除缺失值。决策树还不支持缺失值。
    • 使用树的花费(例如预测数据)是训练数据点(data points)数量的对数。
    • 可以同时处理数值变量和分类变量。其他方法大都适用于分析一种变量的集合。
    • 可以处理多值输出变量问题。
    • 使用白盒模型。如果一个情况被观察到,使用逻辑判断容易表示这种规则。相反,如果是黑盒模型(例如人工神经网络),结果会非常难解释。
    • 即使对真实模型来说,假设无效的情况下,也可以较好的适用。

    缺点:

    • 决策树学习可能创建一个过于复杂的树,并不能很好的预测数据。也就是过拟合。修剪机制(现在不支持),设置一个叶子节点需要的最小样本数量,或者数的最大深度,可以避免过拟合。
    • 决策树可能是不稳定的,因为即使非常小的变异,可能会产生一颗完全不同的树。这个问题通过decision trees with an ensemble来缓解。
    • 学习一颗最优的决策树是一个NP-完全问题under several aspects of optimality and even for simple concepts。因此,传统决策树算法基于启发式算法,例如贪婪算法,即每个节点创建最优决策。这些算法不能产生一个全家最优的决策树。对样本和特征随机抽样可以降低整体效果偏差。
    • 概念难以学习,因为决策树没有很好的解释他们,例如,XOR, parity or multiplexer problems.
    • 如果某些分类占优势,决策树将会创建一棵有偏差的树。因此,建议在训练之前,先抽样使样本均衡。

    决策树算法主要包括三个部分:特征选择、树的生成、树的剪枝。常用算法有 ID3、C4.5、CART。

    • 特征选择。特征选择的目的是选取能够对训练集分类的特征。特征选择的关键是准则:信息增益、信息增益比、Gini 指数;

    • 决策树的生成。通常是利用信息增益最大、信息增益比最大、Gini 指数最小作为特征选择的准则。从根节点开始,递归的生成决策树。相当于是不断选取局部最优特征,或将训练集分割为基本能够正确分类的子集;

    • 决策树的剪枝。决策树的剪枝是为了防止树的过拟合,增强其泛化能力。包括预剪枝和后剪枝。

    3.7 梯度提升决策树(GBDT)

    3.7.1 GBDT概述

    梯度提升决策树GBDT(Gradient Boosting Decision Tree)也被称为是MART(Multiple Additive Regression Tree))或者是GBRT(Gradient Boosting Regression Tree),也是一种基于集成思想的决策树模型,但是它和Random Forest有着本质上的区别。不得不提的是,GBDT是目前竞赛中最为常用的一种机器学习算法,因为它不仅可以适用于多种场景,更难能可贵的是,GBDT有着出众的准确率。这也是为什么很多人称GBDT为机器学习领域的“屠龙刀”。

    “Boosting,迭代,即通过迭代多棵树来共同决策。这怎么实现呢?难道是每棵树独立训练一遍,比如A这个人,第一棵树认为是10岁,第二棵树认为是0岁,第三棵树认为是20岁,我们就取平均值10岁做最终结论?当然不是!且不说这是投票方法并不是GBDT,只要训练集不变,独立训练三次的三棵树必定完全相同,这样做完全没有意义。之前说过,GBDT是把所有树的结论累加起来做最终结论的,所以可以想到每棵树的结论并不是年龄本身,而是年龄的一个累加量。

    GBDT的核心就在于:每一棵树学的是之前所有树结论和的残差,这个残差就是一个加预测值后能得真实值的累加量。比如A的真实年龄是18岁,但第一棵树的预测年龄是12岁,差了6岁,即残差为6岁。那么在第二棵树里我们把A的年龄设为6岁去学习,如果第二棵树真的能把A分到6岁的叶子节点,那累加两棵树的结论就是A的真实年龄;如果第二棵树的结论是5岁,则A仍然存在1岁的残差,第三棵树里A的年龄就变成1岁,继续学,如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。这就是Gradient Boosting在GBDT中的意义。

    其实从这里我们可以看出GBDT与Random Forest的本质区别,GBDT不仅仅是简单地运用集成思想,而且它是基于对残差的学习的。我们在这里利用一个GBDT的经典实例进行解释。

    在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是 f t − 1 ( x ) f_{t−1}(x) ft1(x), 损失函数是 L ( y , f t − 1 ( x ) ) L ( y , f t − 1 ( x ) ) L(y,f_{t−1}(x))L(y,f_{t−1}(x)) L(y,ft1(x))L(y,ft1(x)), 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器 h t ( x ) h_t(x) ht(x),让本轮的损失损失 L ( y , f t ( x ) = L ( y , f t − 1 ( x ) + h t ( x ) ) L(y,ft(x)=L(y,f_{t−1}(x)+h_t(x)) L(y,ft(x)=L(y,ft1(x)+ht(x))最小。也就是说,本轮迭代找到决策树,要让样本的损失尽量变得更小。

    GBDT主要的优点有:

    1. 可以灵活处理各种类型的数据,包括连续值和离散值。

    2. 在相对少的调参时间情况下,预测的准备率也可以比较高。这个是相对SVM来说的。

    3)使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。

    GBDT的主要缺点有:

    1)由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行。

    参考于梯度提升树(GBDT)原理小结
    这篇博客也很好机器学习算法GBDT

    展开全文
  • 机器学习实战这边书中,决策树章节中隐形眼镜的测试数据集
  • 机器学习实战决策树画图理解

    千次阅读 2019-04-10 16:21:14
    机器学习实战第二章决策树难点第二章决策树用matplotlib画图的理解决策树matplotlib画图代码 第二章决策树用matplotlib画图的理解 作为一个小白呢,确实对于我们来说第二章画图部分有很大的难度,个人也是花了很多...

    第二章决策树用matplotlib画图的理解

    作为一个小白呢,确实对于我们来说第二章画图部分有很大的难度,个人也是花了很多时间在CSDN上反正是各种找,最后基本上弄明白了,就是想给同样是小白的人节约一点时间,但是深入理解的时间不能少哦。

    决策树matplotlib画图代码

    结果图

    图片
    第一棵树
    第二棵树:

    具体代码

    # -*- coding: utf-8 -*-
    """
    Created on Mon Apr  1 18:56:26 2019
    
    @author: 风飘  小谭谭
    """
    
    import matplotlib.pyplot as plt
    
    
    #这里是对绘制是图形属性的一些定义
    #boxstyle为文本框的类型,sawtooth是锯齿形,fc是边框线粗细  
    decisionNode = dict(boxstyle = 'sawtooth',fc = '0.8') #定义decision节点的属性
    leafNode = dict(boxstyle='round4',fc='0.8')           #定义leaf节点的属性  
    arrow_args = dict(arrowstyle='<-')                    #定义箭头方向 与常规的方向相反   
    
    #声明绘制一个节点的函数
    '''
    annotate是关于一个数据点的文本 相当于注释的作用 
    nodeTxt:即为文本框或锯齿形里面的文本内容
    centerPt:即为子节点的坐标
    parentPt:即为父节点的坐标
    nodeType:是判断节点还是叶子节点
    '''
    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)
    '''
    #声明绘制图像函数,调用绘制节点函数
        
    def createPlot():
        fig = plt.figure(1,facecolor='white')  #新建绘画窗口  窗口名为figure1  背景颜色为白色
        fig.clf()           #清空绘图区
         #创建了属性ax1  functionname.attribute的形式是在定义函数的属性,且该属性必须初始化,否则不能进行其他操作。
        createPlot.ax1 = plt.subplot(111,frameon=False)        #创建11列新的绘图区 且图绘于第一个区域 frameon表示是否绘制坐标轴矩形 True代表绘制 False代表不绘制
        plotNode('a decision node',(0.5,0.1),(0.1,0.5),decisionNode)#调用画节点的函数
        plotNode('a leaf node',(0.8,0.1),(0.3,0.8),leafNode )
        plt.show()   #画图
    '''
    #--------鉴于python3与python2的不同  python3 dict_keys支持iterable,而不支持indexable
    #故先将myTree.keys()返回得到的dict_keys对象转化为列表
    #否则会报TypeError: 'dict_keys' object does not support indexing的错误
    
    #获得叶节点的数目
    #是一个累加的过程
    def getNumLeafs(myTree):
        numLeafs = 0                #声明叶节点的个数
    
        #python3
        firstSides = list(myTree.keys())    #得到树所有键的列表
        firstStr = firstSides[0]            #取第一个键
        #pyton2:firstStr = myTree.keys()[0]
        secondDict = myTree[firstStr]       #得到第一个键所对应的的值
    
        for key in secondDict.keys():              #循环遍历secondDict的键
            if type(secondDict[key]).__name__ == 'dict': #判断该键对应的值是否是字典类型
                numLeafs += getNumLeafs(secondDict[key]) #若是则使用递归进行计算
            else:
                numLeafs += 1                           #不是则代表当前就是叶子节点,进行加1即可
    
        return numLeafs                                 #返回叶子结点数目
    
    
    #获得叶节点的深度
    #因为某一层不一定是最深的,所以引入thisDepth
    #是一个求最值的过程
    def getTreeDepth(myTree):
        maxDepth = 0                        #声明最大深度并赋值为0
        firstSides = list(myTree.keys())
        firstStr = firstSides[0]
        secondDict = myTree[firstStr]
    
        for key in secondDict:
            if type(secondDict[key]).__name__ == 'dict':
                thisDepth =1 + getTreeDepth(secondDict[key]) 
            else:
                thisDepth = 1
            if thisDepth > maxDepth:
                maxDepth = thisDepth
    
        return maxDepth
    
    #预先存储树的信息
    def retrieveTree(i):
        listOfTrees =[{'no surfacing': {0: 'no', 1: {'flippers': {0: 'no', 1: 'yes'}}}},
                      {'no surfacing': {0: 'no', 1: {'flippers': {0: {'head': {0: 'no', 1: 'yes'}}, 1: 'no'}}}}
                      ]
        return listOfTrees[i]
    #plotTree函数
    
    #在父子节点间填充文本
    '''
    cntrPt:子节点位置坐标
    parentPt:父节点位置坐标
    txtString:文本信息即为图中的0,1
    '''
    def plotMidText(cntrPt,parentPt,txtString):
        xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]      #文本填充的x坐标
        yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]       #文本填充的y坐标
        createPlot.ax1.text(xMid,yMid,txtString)#在(xMid,yMid)位置填充txtString文本
    
    
    
    #画树的函数
    '''
    myTree: 要进行绘制的树
    parentPt:父节点位置坐标
    nodeTxt:文本内容
    
    plotTree.totalW: 整棵树的叶子节点数(常量)
    plotTree.totalD : 整棵树的深度(常量)
    
    
    '''
    def plotTree(myTree,parentPt,nodeTxt):
        numLeafs = getNumLeafs(myTree)   #求得myTree的叶子的个数  注意这可不是我们之前所说的那颗最大的树 谁调用它谁是myTree
        depth = getTreeDepth(myTree)     #求得myTree的深度 
        #python3.6的原因,与书中有两行不一样
        #-----
        firstSides = list(myTree.keys())  #即为['no surfacing']
        firstStr = firstSides[0]        #得到第一个键 也就是第一个判断节点 即myTree的根节点即为'no surfacing'
        #-----
    
        cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2/plotTree.totalW,plotTree.yOff) #计算子节点的坐标       
        plotMidText(cntrPt,parentPt,nodeTxt)  #对判断节点进行的绘制其与其父节点之间的文本信息   此处第一个节点与父节点重合(0.5,1.0)的设置 所以会没有效果 也恰好符合题意
        plotNode(firstStr,cntrPt,parentPt,decisionNode)     #绘制子节点
        secondDict = myTree[firstStr]                       #得到该节点以下的子树
        plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD #深度改变 纵坐标进行减一层
    
        #循环遍历各个子树的键值
        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.totalW
                plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)#绘制叶子节点 (plotTree.xOff,plotTree.yOff)代表叶子节点(子节点)坐标,cntrPt代表判断节点父节点坐标
                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')  #新建绘画窗口  窗口名为figure1  背景颜色为白色
        fig.clf()           #清空绘图区
        axprops = dict(xticks=[],yticks=[]) #定义横纵坐标轴
         #创建了属性ax1  functionname.attribute的形式是在定义函数的属性,且该属性必须初始化,否则不能进行其他操作。
        createPlot.ax1 = plt.subplot(111,frameon=False,**axprops)        #创建11列新的绘图区 且图绘于第一个区域 frameon表示不绘制坐标轴矩形 定义坐标轴为二维坐标轴
        plotTree.totalW = float(getNumLeafs(inTree))  #计算树的叶子数即为3
        plotTree.totalD = float(getTreeDepth(inTree)) #计算树的深度即为2
    
        plotTree.xOff = -0.5/plotTree.totalW    #赋值给绘制叶子节点的变量为-0.5/plotTree.totalW 
        plotTree.yOff = 1.0                     #赋值给绘制节点的初始值为1.0 
    
        plotTree(inTree,(0.5,1.0),'')              #调用函数plotTree 且开始父节点的位置为(0.5,1.0) 
    
        plt.show()   #画图
    

    前面代码的注释的相对较为详细,其中对于难的代码的简单解释
    1.cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2/plotTree.totalW,plotTree.yOff)
    其实plotTree.xOff第一次为-1/6,即为向右平移1/6,plotTree.yOff第一次为1,经过上面这个cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2/plotTree.totalW,plotTree.yOff)
    代码可得cntrPt =(-1/6+(1+3)*1/2 *1/3,1)即为(1/2,1)对照即可得出。
    2.parentPt的坐标已经在代码 plotTree(inTree,(0.5,1.0),’’)中得出为(0.5,1.0)后面根据代码以此类推
    整个过程相当于是已知决策树来画,就是指已经知道树节点和叶子节点的数目,根据它们的数目来划分整个画布,并且在x轴以1/叶子节点数目来表示每个叶子之间的x轴距离,为什么plotTree.xOff = -0.5/plotTree.totalW 其中的负号指向右平移,半个叶子节点的距离而叶子之间的距离计算也是以半个叶子节点的距离为单位,看第一个叶子结点的距离与第二个叶子节点的距离相差几个,即第二个就向右平移几个,在叶子节点中间的根节点,即在它们的中间。对于y轴,是根据循环的层数来决定,在同一个循环里即为同一层。

    以上解释有一点混乱,建议结合图和代码一步一步把坐标算出来基本上就理解了。谢谢!

    展开全文
  • 机器学习实战(第三章-决策树-ID3算法-所有代码与详细注解-python3.7) 机器学习实战(第三章-决策树-ID3算法-所有代码与详细注解-python3.7)
  • 作 者 :何宇健出版发行 : 北京:电子工业出版社 , 2017.06...机器学习中图法分类号 : TP311.561 ( 工业技术->自动化技术、计算机技术->计算技术、计算机技术->计算机软件 )内容提要:Python与机器学习这...
  • 机器学习实战决策树的java实现

    千次阅读 2015-10-21 21:16:24
    * @Description: [机器学习实战决策树第一个案例的数据] */ public void initTest() { ArrayList<String> ab1 = new ArrayList(); ArrayList<String> ab2 = new ArrayList(); ArrayList<String> ab3 ...
  • 机器学习实战 决策树(附数据集)

    万次阅读 2019-07-01 18:49:36
    决策树也是最经常使用的数据挖掘算法,长方形代表判断模块(decision block),椭圆形代表终止模块(terminating block),表示已经得出结论,可以终止运行。从判断模块引出的左右箭头称作分支(branch),它可以...
  • 机器学习实战决策树

    万次阅读 多人点赞 2018-03-09 19:36:20
    花了差不多一个星期,终于把《机器学习实战》这本书的第三章的决策树过了一遍,对于一个python渣渣来说,确实是着实不容易,好多代码都得一个一个的去查,所以整体上进度比较慢,再加上中间开了一篇关于自然语言处理...
  • 使用python3版本修改机器学习实战中python2的代码
  • 决策树原理:从数据集中找出决定性的特征对数据集进行迭代划分,直到某个分支下的数据都属于同一类型,或者已经遍历了所有划分数据集的特征,停止决策树算法。  每次划分数据集的特征都有很多,那么我们怎么来选择...
  • 机器学习决策树算法案例实战 构造数据 好坏比例7:3 import pandas as pd import numpy as np bad_df = pd.DataFrame(data={ "sex":['男', '男', '女', '男', '女', '男'], "status":['单身', '已婚', '已婚', '...
  • 这是以本人的笔记的形式写的,各个函数逐个来写,至于存放在那个模块大家可以看书,这里不再详细讲解。可能存在错误,有不对的的地方希望评论给予改正。多谢大家嘻嘻
  • 1、tree.py:决策树代码 2、treePlotter.py:在matplot中生成树形图的代码 3、classifierStorage.txt:生成树的测试数据 4、lenses.txt:决策树预测隐形眼镜类型所用的样本,每行前四个为特征:['age', 'prescript',...
  • 机器学习实战-决策树算法

    千次阅读 2019-03-06 18:56:55
    本篇决策树算法是依据ID3算法来的,所以...文章目录第一步:构建决策树 案例,按照属性来分辨海洋生物: 第一步:构建决策树 实例: # coding: utf-8 from math import log import operator def...
  • 分类回归树(Classification and Regression Tree,CART)是一种典型的决策树算法,CART算法不仅可以应用于分类问题,而且可以用于回归问题。
  • 搜索微信公众号:‘AI-ming3526’或者’计算机视觉这件小事’ 获取更多人工智能、机器学习干货 csdn:https://blog.csdn.net/baidu_31657889/ github:https://github.com/aimi-cn/AILearners 1、项目简介 在上一篇...
  • 在看机器学习实战时候,到第三章的对决策树画图的时候,有一段递归函数怎么都看不懂,因为以后想选这个方向为自己的职业导向,抱着精看的态度,对这本树进行地毯式扫描,所以就没跳过,一直卡了一天多,才差不多搞懂...
  • 机器学习实战决策树python实现,《统计学习方法》第五章决策树,使用python实现决策树的一个例子,详细的注释,可读性更高。 参照我的博客链接https://blog.csdn.net/u012324136/article/details/80894993
  • 机器学习实战源码解析及课件,机器学习基本思想及入门项目实战。机器学习实战学习资料之决策树源码解析及实战
  • https://www.cnblogs.com/fantasy01/p/4595902.html
  • 决策树也是有监督机器学习方法。 电影《无耻混蛋》里有一幕游戏,在德军小酒馆里有几个人在玩20问题游戏,游戏规则是一个设迷者在纸牌中抽出一个目标(可以是人,也可以是物),而猜谜者可以提问题,设迷者只能回答...
  • 机器学习实战代码,里面的test.py是运行文件,treePlotter.py是画决策树的代码,decisionTree是构造决策树的代码。直接运行test.py就可以得出结果

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,267
精华内容 5,306
关键字:

机器学习实战决策树