机器学习(其三)决策树

机器学习(其三) 决策树

前言

由于某些原因,前2个部分的文章还没做出来,以后会补上,但是前后并没有关联性.编号只是一个编号而已.从哪一篇开始看都是可以的.
另外文中引用了他人的文章,会在参考资料部分标明,如有版权.转载等问题我会删掉那部分内容.
最后本人也是刚刚入门机器学习这一领域,如有问题欢迎探讨,希望大家看完都能有点收获.
运行环境是Anaconda3.
其中python版本为3.6.4
skearn版本0.19.1
(感觉很多人写文章都不说运行环境,写的很用心但是别人却运行不了,这太可惜了.)

决策树是什么?

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

决策树的基本步骤

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

    ID3决策树和C4.5决策树

假设我们要对一个西瓜是不是一个好瓜进行判别,这个西瓜表示为一个向量

x = ( x , x , x )
其中有一个瓜为
x 1 = ( 绿 , , )

那么用这个瓜来构建一个决策树应该考虑些什么问题呢?最重要的是如何选择色泽,根蒂,敲声这3个属性的先后顺序.
直观的经验告诉我们,应该首先对最重要的属性进行判别.比如说我们判别一个生物是不是鱼,我们首先判断的大概是它会不会游泳,而不是能不能吃.如果会游泳再判断它是否有鱼的其他特性.
在信息论中我们用信息增益来描述这个属性的重要度.
计算信息增益首先要计算信息熵(information entropy),也叫香农熵

信息熵计算如下:

E n t ( D ) = k = 1 | y | p k l o g 2 p k

其中

D :表示当前样本集合
p k :在当前样本集D中第k类样本所占的比例为
p k ( k = 1 , 2 , . . . , | y | )

| y | :在当前样本集D中总的类别总数

信息增益(information gain)为:

G a i n ( D , a ) = E n t ( D ) v = 1 V | D v | | D | E n t ( D v )
其中
| D | 为属性a的可取值范围的全集的元素数量,一般和样本集大小一样
| D v | 为属性a的第v个取值的样本个数

列举《机器学习》中的例子来讲解

编号 色泽 根蒂 敲声 纹理 肚部 触感 好瓜
1 青绿 蜷缩 浊响 清晰 凹陷 硬滑
2 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑
3 乌黑 蜷缩 浊响 清晰 凹陷 硬滑
4 青色 蜷缩 沉闷 清晰 凹陷 硬滑
5 浅白 蜷缩 浊响 清晰 稍凹 软粘
6 青绿 稍蜷 浊响 清晰 稍凹 软粘
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘
8 乌黑 稍蜷 浊响 清晰 稍凹 硬滑
9 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑
10 青绿 硬挺 清脆 清晰 平坦 软粘
11 浅白 硬挺 清脆 模糊 平坦 硬滑
12 浅白 蜷缩 浊响 模糊 平坦 软粘
13 青绿 稍蜷 浊响 稍糊 凹陷 硬滑
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑
15 乌黑 稍蜷 浊响 清晰 稍凹 软粘
16 浅白 蜷缩 浊响 模糊 平坦 硬滑
17 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑
以上表中的数据计算色泽的信息增益,流程如下:

它有3个可能的取值:{青绿,乌黑,浅白}.如果用色泽对测试样本集进行划分可以得到3个子集分别为
D 1 ( = 绿 ) , D 2 ( = ) , D 3 ( = )

优先计算根节点的信息熵Ent(D)
从表中的西瓜数据集为例,该数据集包含17个训练数据样例,用以学习一颗能预测没剖开的是不是好瓜的决策树,显然
| y | = 2 .(好瓜,坏瓜两个类别)在决策树学习开始时,根节点包含D中的所有样例,其中正例占
p 1 = 8 17 ,反例占
p 2 = 9 17 ,根据公司计算根节点的信息熵为
E n t ( D ) = k = 1 2 p k l o g 2 p k = 8 17 l o g 2 8 17 + 9 17 l o g 2 9 17 = 0.998

其中
D 1 包含编号为{1,4,6,10,13,17}的6个样例,其中正例占3/6,反例占3/6,
D 2 包含编号为{2,3,7,8,9,15}的6个样例,其中正例,反例各占4/6,2/6,
D 3 包含编号为{5,11,12,14,16}的5个样例,其中正例,反例各占1/5,4/5,
则计算用色泽划分D时的信息熵如下:
E n t ( D 1 ) = ( 3 6 l o g 2 3 6 + 3 6 l o g 2 3 6 ) = 1.000
E n t ( D 2 ) = ( 4 6 l o g 2 4 6 + 2 6 l o g 2 2 6 ) = 0.918
E n t ( D 3 ) = ( 1 5 l o g 2 1 5 + 4 5 l o g 2 4 5 ) = 0.722

则信息增益为
G a i n ( D , ) = E n t ( D ) v = 1 3 | D v | | D | E n t ( D v )
= 0.998 ( 6 17 1.000 + 6 17 0.918 + 5 17 0.722 )
= 0.109
类似的,计算出其他属性的信息增益:
Gain(D,根蒂)=0.143
Gain(D,敲声)=0.141
Gain(D,纹理)=0.381
Gain(D,脐部)=0.289
Gain(D,触感)=0.006

信息增益大的纹理有什么特点?
纹理清晰的瓜有很大概率是好瓜,纹理稍糊或模糊的瓜很大概率是坏瓜,所以用纹理区分时他的效果很明显,所以他的信息增益大
那么设想纹理有17个取值,在这个样本集里面每个样本取一种值.这样的信息增益会比以前更大,此时用纹理作为首要区分条件是否比之前的更好?

答案是否定的,

信息增益表征了这个属性对分类的帮助有多大.这个属性的信息熵增益大表示这个属性对分类的帮助越大.但是并不是信息增益越大就越好.如果这个属性的取值有101种,比如说我们要划分考试成绩,满分和非满分,那么0-99(中的整数)都为非满分,100的时候为满分.此时的决策树树在这个属性上的分支就已经有101种,显然这个树形结构会变得太大了.

因为我们更希望用更少的分类区别出更多的种类,那么显然要消除属性可取值范围大的影响,在信息论里面这个概念称为信息增益率(gain ratio)

G a i n r a t i o ( D , a ) = G a i n ( D , a ) I V ( a )

I V ( a ) = v = 1 V | D v | | D | l o g 2 | D v | | D |
其中
| D | 为属性a的可取值范围的全集的元素数量,一般和样本集大小一样
| D v | 为属性a的第v个取值的样本个数

下式称为属性a的固有值(intrinsic value),属性a的可能取值数目越多(即V越大),则IV(a)的值通常会越大,计算表中的属性对应的固有值

IV(触感)=0.875(V=2)
IV(色泽)=1.580(V=3)
IV(编号)=4.088(V=17)
实际使用中,我们并不直接取增益率最高的属性作为首要划分属性.因为增益率准则对于可取值数目较少的属性有所偏好.

一般我们从候选划分属性中找出信息增益高于平均水平的属性,再从中选择增益率最高的.,这个算法称为C4.5决策树算法[Quinlan,1993]
而采用信息增益最高的为最优划分属性的算法称为ID3决策树.

下面用csdn的一个例子讲解一下计算信息增益的代码实现,摘自

Python3《机器学习实战》学习笔记(二):决策树基础篇之让我们从相亲说起
情景如下:

现有15个样本作为训练集,根据对象的年龄,有否工作,有否房子,信贷情况来判别是否给个贷的案例.

ID 年龄 有工作 有自己的房子 信贷情况 类别(是否个给贷款)
1 青年 一般
2 青年
3 青年
4 青年 一般
5 青年 一般
6 中年 一般
7 中年
8 中年
9 中年 非常好
10 中年 非常好
11 老年 非常好
12 老年
13 老年
14 老年 非常好
15 老年 一般

# -*- coding: UTF-8 -*-
from math import log

"""
函数说明:计算给定数据集的经验熵(香农熵)

Parameters:
    dataSet - 数据集
Returns:
    shannonEnt - 经验熵(香农熵)
Author:
    Jack Cui
Modify:
    2017-03-29
"""
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] = 0
        labelCounts[currentLabel] += 1                #Label计数
    shannonEnt = 0.0                                #经验熵(香农熵)
    for key in labelCounts:                            #计算香农熵
        prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率
        shannonEnt -= prob * log(prob, 2)            #利用公式计算
    return shannonEnt                                #返回经验熵(香农熵)

"""
函数说明:创建测试数据集

Parameters:
    无
Returns:
    dataSet - 数据集
    labels - 分类属性
Author:
    Jack Cui
Modify:
    2017-07-20
"""
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:
    无
Author:
    Jack Cui
Modify:
    2017-03-30
"""
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                                      #返回划分后的数据集

"""
函数说明:选择最优特征

Parameters:
    dataSet - 数据集
Returns:
    bestFeature - 信息增益最大的(最优)特征的索引值
Author:
    Jack Cui
Modify:
    2017-03-30
"""
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)))

代码结果
结果如上图

CART决策树

CART是Classification and Regression Tree的缩写,可用于回归和分类,选用基尼指数(Gini index)来选定划分属性.数据集D的纯度(purity)可用基尼值来度量:
基尼指数是指国际上通用的、用以衡量一个国家或地区居民收入差距的常用指标。基尼系数介于0-1之间,基尼指数越大,表示不平等程度越高.
在本例中,基尼指数最小即为最优划分属性.

数据集的纯度可用基尼值来度量:
G i n i ( D ) = 1 k = 1 | y | p k 2
p k 为正例(反例)的概率,即
| D v | | D |
直观来说,
G i n i ( D ) 反应了从数据集D中随机抽取两个样本,其类别标记不一致的概率,因此,Gini(D)越小,则数据集D的纯度越高
定义属性a的基尼指数为
G i n i i n d e x ( D , a ) = v = 1 V | D v | | D | G i n i ( D v )
| D | 为属性a的可取值范围的全集的元素数量,一般和样本集大小一样
| D v | 为属性a的第v个取值的样本个数
于是,我们再候选属性集合A中,选择哪个使的划分后基尼指数最小的属性作为最优划分属性,即

a = a r g m i n a A G i n i i n d e x ( D , a )

经济学中的 基尼指数 (注释1) ,连续值的基尼指数计算可以参照注释的算法来算.非连续值的基尼指数按照上式计算

创建一棵决策树

# coding:utf-8
# -*- coding: UTF-8 -*-
from math import log
import operator

"""
函数说明:计算给定数据集的经验熵(香农熵)

Parameters:
    dataSet - 数据集
Returns:
    shannonEnt - 经验熵(香农熵)
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-24
"""
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] = 0
        labelCounts[currentLabel] += 1                #Label计数
    shannonEnt = 0.0                                #经验熵(香农熵)
    for key in labelCounts:                            #计算香农熵
        prob = float(labelCounts[key]) / numEntires    #选择该标签(Label)的概率
        shannonEnt -= prob * log(prob, 2)            #利用公式计算
    return shannonEnt                                #返回经验熵(香农熵)

"""
函数说明:创建测试数据集

Parameters:
    无
Returns:
    dataSet - 数据集
    labels - 特征标签
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-20
"""
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:
    无
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-24
"""
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                                      #返回划分后的数据集

"""
函数说明:选择最优特征

Parameters:
    dataSet - 数据集
Returns:
    bestFeature - 信息增益最大的(最优)特征的索引值
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-20
"""
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                                             #返回信息增益最大的特征的索引值


"""
函数说明:统计classList中出现此处最多的元素(类标签)

Parameters:
    classList - 类标签列表
Returns:
    sortedClassCount[0][0] - 出现此处最多的元素(类标签)
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-24
"""
def majorityCnt(classList):
    classCount = {}
    for vote in classList:                                        #统计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]                                #返回classList中出现次数最多的元素

"""
函数说明:创建决策树

Parameters:
    dataSet - 训练数据集
    labels - 分类属性标签
    featLabels - 存储选择的最优特征标签
Returns:
    myTree - 决策树
Author:
    Jack Cui
Blog:
    http://blog.csdn.net/c406495762
Modify:
    2017-07-25
"""
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:                                    #遍历完所有特征时返回出现次数最多的类标签
        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 myTree

if __name__ == '__main__':
    dataSet, labels = createDataSet()
    featLabels = []
    myTree = createTree(dataSet, labels, featLabels)
    print(myTree)

代码结果
代码结果如图

决策树的剪枝处理

剪枝(pruning)是决策树学习算法对付过拟合的主要手段.在决策树学习中,为了尽可能正确分类训练样本,节点划分过程将不断重复,有时候会造成决策树分支过多,这时就可能因训练样本学得“太好”了,以致于把训练集自身的一些特点当做所有数据都具有的一般性质而导致过拟合.因此,可通过主动去掉一些分支来降低过拟合的风险.
剪枝分为预剪枝(prepruning)和后剪枝(postpruning).

关于过拟合

关于过拟合(overfit),以以下一个图演示过拟合
红色叉叉点是样本集,蓝色曲线把所有的点都纳入到了自己身上,而绿色的直线在样本点中间穿过去了.假设样本数据和真值的差距可以用下式表示
V = V V
V r e a l = V o b s e r v e V e r r o r
显然我们的样本集的值是观察值.根据误差的产生方式可以分为偶然误差(random errors)和系统误差(systematic errors)
偶然误差是偶然情况下发生的误差,因为是无规律的随机分布,可以通过增大样本数量求均值让偶然误差正负抵消.
系统误差是我们测量方法上造成的不可避免的误差.比如说用尺子测量一个木板的长度,假设尺子是标准的,(符合米原器的度量规格),但是由于温度的关系产生了冷缩热胀的现象.此时造成的误差称为系统误差,每一种系统误差都是固定的.可以推算出来的,假设我们知道了温度,那么我们可以通过查表得到由于热胀冷缩造成的测量误差值.
假设绿色线才是反应真实值的回归直线,红色叉叉点是由于偶然误差产生的偏离真实值的有误差数据.这个时候拟合出来的蓝色曲线称为过拟合(overfit).过拟合是一种对样本学习过度的情况,把包括误差在内的所有样本都作为样本的特征进行学习.也就是学习了不必要的内容(误差)
反之,假设蓝色曲线为真实值的回归曲线,而我们回归时使用了绿色的直线,此时绿色的直线称为欠拟合(underfit).欠拟合就是一种对样本学习不充分的情况.
在决策树中过拟合的表现可能如下:
决策树过拟合
左图是决策树的图形化的图像,右图是结合样本作出的用决策树进行训练的图像,在rule3中仅有1个样本为正类,在可能的情况我们应尽可能验证相同情况下,rule3中的样本是否是正例.造成这个的原因可能有3个

原因1:噪声、样本冲突,即错误的样本数据。

原因2:特征即属性不能完全作为分类标准。

原因3:巧合的规律性,数据量不够大。

但是这里讨论的是消除误差,所以假设rule3的区间仅是由于误差造成的,这个误差在机器学习中用一个术语来描述——噪声(noise)
现在描述一下情形:
现实中本来不可能获得x<2&y<2的点,即不存在rule3的区域.但是由于误差,噪声的存在使得部分样本落在rule3的区域内.
在决策树算法里面,决策树会穷尽样本的所有特征对样本进行分类.这样不具备代表性的样本导致决策树走向过拟合.
面对这种情况,我们的对策是剪枝.
判断出样本不可能出现x<2&y<2的情况,从而删除这个枝干,这个操作为预剪枝.这个判断情形可能是多样的

  1. 一种最为简单的方法就是在决策树到达一定高度的情况下就停止树的生长。
  2. 到达此结点的实例具有相同的特征向量,而不必一定属于同一类, 也可停止生长。
  3. 到达此结点的实例个数小于某一个阈值也可停止树的生长。
  4. 还有一种更为普遍的做法是计算每次扩张对系统性能的增益,如果这个增益值小于某个阈值则不进行扩展。

决策树分类结束后,从右图中查看分类结果,发现可能的噪声样本,然后删除效果甚微,或者错误的枝干的操作为后剪枝.
但是这个例子似乎没有充分说明过拟合的风险,那么我们再次假设我们的例子中出现了错误,如下图:
决策树过拟合2
现在我们把rule2区域中的一个样本改成反例,显然绿色区域就变成了rule5区域.但是这个rule5从一般而言极有可能是错误的样本,因为他是符合rule2的条件的,如果我们建立的决策树把rule5区域作为他的一个叶节点纳入模型的话,这就是过拟合.
过拟合在机器学习的过程中是比较常见的问题.他的表现的现实映射为,当前的最好方案不是长期来看的最好方案.这个在整个AI领域都是比较棘手的问题.比如说写出一个围棋的AI程序,当前的最佳一手未必是从5步来考虑的最佳的一手,也未必是从整局比赛中最优的一手.又比如说语义识别的问题,结合上下文的识别和当前句子的识别出现的结果显然不会一样.而欠拟合就是当前的方案不够好,还不足以解决问题.

剪枝方法

剪枝的方法很多.比如说对比删减枝干前后的泛化能力.删除能提高泛化能力的枝干.又或者是删除对泛化能力小与一个阈值的枝干,这些枝干对结果影响很小,但是却比较占用运算资源.
包括但不仅限于以下几种方法:
1. REP-错误率降低剪枝
2. PEP-悲观剪枝
3. CCP-代价复杂度剪枝
4. MEP-最小错误剪枝
5. ….
详见谢小娇包教包会决策树之决策树剪枝
以及决策树剪枝算法
原则上应该保留最有判断力的属性的分支,删除有反效果以及效果不明显的分支.

连续值和缺失值处理

连续值处理

假设我们要对一个西瓜进行判断,判断他是不是一个好瓜,这个西瓜有一个属性是密度.根据决策树算法,我们会把所有不同密度的西瓜分别分成一类,显然从来没有人对一个西瓜进行这样的分类.此时我们需要一个连续值的处理.
其中简单有效的一个方法是二分法(bi-partition),这是C4.5决策树使用的算法.假设西瓜密度有17个取值,从小到大排列分别是
T d e n s i t y = { 0.243 , 0.245 , 0.343 , 0.36 , 0.403 , 0.437 , 0.481 , 0.556 , 0.593 , 0.608 , 0.634 , 0.639 , 0.657 , 0.666 , 0.697 , 0.719 , 0.774 }
为了使取值更加连续,我们取前后2个值的均值作为我们的候选划分点即这16个取值
T d e n s i t y = { 0.244 , 0.294 , 0.351 , 0.381 , 0.420 , 0.459 , 0.518 , 0.574 , 0.600 , 0.621 , 0.636 , 0.648 , 0.661 , 0.681 , 0.708 , 0.746 }
此时我们把这16个取值分成分别对样本进行划分
以从0.574为边界把数据集划分成2个子集,可得
T 1 = { 0.243 , 0.245 , 0.343 , 0.36 , 0.403 , 0.437 , 0.481 , 0.556 } ,
T 2 = { 0.593 , 0.608 , 0.634 , 0.639 , 0.657 , 0.666 , 0.697 , 0.719 , 0.774 } 两个子集
他们对应的标签是
L 1 = { 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 }
L 2 = { 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 1 }
其中1表示好瓜,0表示坏瓜.根据C4.5决策树算法的基本思路,找到信息增益最大的划分点,根据信息增益最大的原则,我们应该找出最能区别出好坏瓜的转折点,即1尽可能在一个子集中,0尽可能在另一个子集中.经过16次划分,最后发现0.381作为划分点时信息增益最大.此时的两个子集的标签分别为
L 1 = { 0 , 0 , 0 , 0 }
L 2 = { 1 , 1 , 1 , 1 , 0 , 1 , 1 , 0 , 0 , 0 , 1 , 0 , 1 }
L 1 子集有4个坏瓜的标签,0个好瓜的标签
L 2 子集有5个坏瓜的标签,8个好瓜的标签
是最能划分好坏瓜的转折点
总结为数学公式有:
G a i n ( D , a ) = m a x t T a E n t ( D ) λ { , + } | D t λ | | D | E n t ( D t λ )
其中
T a 为上例中的划分点候选值的集合
D t , D t + 分别表示划分后的子集即上例中的
T 1 , T 2
λ 表示当前划分点
分别计算划分点为0.244,0.294,0.351…0.746的信息增益值,得

划分点 信息增益值
0.244 0.056
0.294 0.118
0.351 0.186
0.381 0.262
0.420 0.093
0.459 0.030
0.518 0.004
0.574 0.002
0.600 0.002
0.621 0.004
0.636 0.030
0.648 0.006
0.661 0.001
0.681 0.024
0.708 0.000
0.746 0.056

最大的信息增益为0.262,对应的划分点为0.381.同之前的结论一致.

缺失值处理

我们获取数据的时候并不一定非常理想,数据获取可能不完全,如下表:

编号 色泽 根蒂 敲声 纹理 肚部 触感 好瓜
1 - 蜷缩 浊响 清晰 凹陷 硬滑
2 乌黑 蜷缩 沉闷 清晰 凹陷 -
3 乌黑 蜷缩 - 清晰 凹陷 硬滑
4 青色 蜷缩 沉闷 清晰 凹陷 硬滑
5 - 蜷缩 浊响 清晰 稍凹 软粘
6 青绿 稍蜷 浊响 清晰 - 软粘
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘
8 乌黑 稍蜷 浊响 - 稍凹 硬滑
9 乌黑 - 沉闷 稍糊 稍凹 硬滑
10 青绿 硬挺 清脆 - 平坦 软粘
11 浅白 硬挺 清脆 模糊 平坦 -
12 浅白 蜷缩 - 模糊 平坦 软粘
13 - 稍蜷 浊响 稍糊 凹陷 硬滑
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑
15 乌黑 稍蜷 浊响 清晰 - 软粘
16 浅白 蜷缩 浊响 模糊 平坦 硬滑
17 青绿 - 沉闷 稍糊 稍凹 硬滑

显然放弃缺失了部分特征的数据是不可取的,这是一种对数据的浪费.此时的处理方式是平均值作为缺失值的取值来处理.
举个例子,有数据集{1,2,3,4,5,-,7,8,9,10},”-“表示缺失值,此时缺失值用这组数据的平均值来表示
V m i s s i n g = 1 n n m i s s i n g D v / ( n n m i s s i n g )
其中
V m i s s i n g 为缺失值,如果有多个缺失值,只要都从属于同一个属性,即为独立同分布,则其值相同
n 为数据集样本量
n m i s s i n g 为缺失值总量
D v 为第v个样本的属性取值
带入公式得缺失值为
V m i s s i n g = ( 1 + 2 + 3 + 4 + 5 + 7 + 8 + 9 + 10 ) / 9 = 5.4
我们平时算平均值的时候,把所有样本的权值都计算成一样的
这种做法就是把缺失值的权值降为0,把他的权重分配到其他所有样本中
此时可以把我们的信息增益计算公式推广为
G a i n ( D , a ) = ρ ( E n t ( D ~ ) v = 1 V γ ~ E m t ( D v ~ ) )
其中
ρ 表示无缺失值样本所占的比例
γ ~ 表示无缺失值样本中在属性a上取值
a v 的样本所占的比例

决策树存储

显然,我们要把决策树投入应用的话,就需要把划分好的决策树保存起来.
可以发现,决策树的结构和python里面的字典结构比较接近,我们在python中描述一个决策树的时候可以采取一个字典的形式,比如
{‘有自己的房子’: {0: {‘有工作’: {0: ‘no’, 1: ‘yes’}}, 1: ‘yes’}},这个字典表示的决策树如下图.如果有自己的房子,读取字典的时候读取{“有自己的房子”:{1:’yes’}},没有自己的房子的时候读取为{‘有自己的房子’:{0:{…}}}此时会读取出一个新的字典,在这里就是’有工作’的决策,对应下图的’有工作’的内部节点.

借贷例子

字典在保存和传输上都有很大应用,可以采用json/bson/pickle模块来完成这个工作.

# 保存决策树
def storeTree(inputTree, filename):
    import pickle
    fw = open(filename, 'w')
    pickle.dump(inputTree, fw)
    fw.close()

# 取出决策树
def grabTree(filename):  
    import pickle  
    fr = open(filename)  
    return pickle.load(fr)

sklearn的决策树实现

[sklearn英文文档]http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html
案例详见Python3《机器学习实战》学习笔记(三):决策树实战篇之为自己配个隐形眼镜

注释

1.基尼系数:

表征数据内差异大小的指标.计算如下:
假设一个国家有100个人,计算这个国家的基尼系数分为下面四步:1.将这100人按收入从低往高排列,第一名是收入最低的,第一百名是收入最高的;2.画一个边长为一的正方形,并将左下角与右上角的对角线相连;3.依次计算前十名,前二十名,前三十名……一直到前九十名所拥有的收入占整个100人的收入的比值;4.以正方形的左下角为原点,用水平边标记累计人口,垂直边标记累积的收入比,将在上面计算出的累计收入比值,在正方形中标出.然后,将这些点同原点以及正方形的右上角连接,就可以得到一条曲线.这条曲线被称为劳伦斯曲线(Lorenze Cruve).基尼系数就是对角线与劳伦斯曲线之间的面积,与对角线以下的三角形的面积之比.如果收入是均匀分布的,劳伦斯曲线就和对角线重合,基尼系数就是零,如果收入是极端不平均的,比如前99人收入为零,劳伦斯曲线就和正方形的右边线重合,基尼系数就是1.
作出图形类似的如下:
洛伦兹曲线

参考资料:

谢小娇包教包会决策树之决策树剪枝
Python3《机器学习实战》学习笔记(三):决策树实战篇之为自己配个隐形眼镜
Python3《机器学习实战》学习笔记(二):决策树基础篇之让我们从相亲说起
决策树之剪枝原理与CART算法
《机器学习》 周志华
sklearn英文文档

猜你喜欢

转载自blog.csdn.net/negineko/article/details/80546749