决策树

数据分析最难的部分在数据挖掘,数据挖掘需要用到大量的分析算法

我们先来学习下决策树相关的算法,下图为一个打篮球的训练集

如果我们要出门打篮球,一般会根据“天气”、“温度”、“湿度”、“刮风”这几个条件来判断,最后得到结果:去打篮球或者不去

graph TD A[天气]-->B[温度=?] A[天气]-->C[湿度=?] A[天气]-->D[刮风=?] B-->E[打篮球] B-->F[不打篮球] C-->G[打篮球] C-->H[不打篮球] D-->I[打篮球] D-->J[不打篮球]

我们在做决策树的时候,会经历两个阶段:构造和剪枝

构造

什么是构造呢?构造就是生成一棵完整的决策树

简单来说,构造的过程就是选择什么属性作为节点的过程,那么在构造过程中,会存在三种节点

  • 根节点:就是树的最顶端,最开始的那个节点。在上图中,“天气”就是一个根节点
  • 内部节点:就是树中间的那些节点,比如说“温度”、“湿度”、“刮风”
  • 叶节点:就是树最底部的节点,也就是决策结果

节点之间存在父子关系。比如根节点会有子节点,子节点会有子子节点,但是到了叶节点就停止了,叶节点不存在子节点

在构造过程中,需要解决三个重要的问题

  • 选择哪个属性作为根节点
  • 选择哪些属性作为子节点
  • 什么时候停止并得到目标状态,即叶节点

剪枝

剪枝就是给决策树瘦身,这一步想实现的目标就是,不需要太多的判断,同样可以得到不错的结果

之所以这么做,是为了防止“过拟合”(Overfitting)现象的发生

“过拟合”是指模型的训练结果“太好了”,以至于在实际应用的过程中,会存在“死板”的情况,导致分类错误

造成过拟合的原因之一就是因为训练集中样本量较小。如果决策树选择的属性过多,构造出来的决策树一定能够“完美”地把训练集中的样本分类

但是这样就会把训练集中一些数据的特点当成所有数据的特点,但这个特点不一定是全部数据的特点,这就使得这个决策树在真实的数据分类中出现错误,也就是模型的“泛化能力”差

泛化能力指的分类器是通过训练集抽象出来的分类能力,你也可以理解是举一反三的能力

如果我们太依赖于训练集的数据,那么得到的决策树容错率就会比较低,泛化能力差

因为训练集只是全部数据的抽样,并不能体现全部数据的特点。所以我们可以对决策树进行剪枝

一般来说,剪枝可以分为“预剪枝”(Pre-Pruning)和“后剪枝”(Post-Pruning)

  • 预剪枝是在决策树构造时就进行剪枝。方法是在构造的过程中对节点进行评估
    • 如果对某个节点进行划分,在验证集中不能带来准确性的提升,那么对这个节点进行划分就没有意义,这时就会把当前节点作为叶节点,不对其进行划分
  • 后剪枝就是在生成决策树之后再进行剪枝,通常会从决策树的叶节点开始,逐层向上对每个节点进行评估
    • 如果剪掉这个节点子树,与保留该节点子树在分类准确性上差别不大,或者剪掉该节点子树,能在验证集中带来准确性的提升,那么就可以把该节点子树进行剪枝
    • 方法是:用这个节点子树的叶子节点来替代该节点,类标记为这个节点子树中最频繁的那个类
和过拟合相反的是欠拟合,是指模型复杂度低,模型在训练集上就表现很差,没法学习到数据背后的规律

简单一句话总结,就是过拟合(以偏概全),欠拟合(提取特征不足)

如何判断要不要去打篮球

训练集数据

天气 温度 湿度 刮风 是否打篮球
晴天
晴天
阴天
小雨
小雨
晴天
阴天

天气、温度、湿度、刮风,哪个作为根节点?

先了解两个指标:纯度和信息熵

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

假设有3个集合

  • 集合1 - 6次都去打篮球
  • 集合2 - 4次去打篮球、2次没去打篮球
  • 集合3 - 3次去打篮球、3次没去打篮球

所以,按照纯度来讲,集合1 > 集合2 > 集合3

我们再来了解下信息熵(shāng)的概念,英文为entropy,它表示了信息的不确定度。在信息论中,随机离散事件出现的概率存在着不确定性

为了衡量这种信息的不确定性,信息学之父香农引入了信息熵的概念,并给出了计算信息熵的数学公式

p(i|t)代表了节点t为分类i的概率,其中log2为取以2为底的对数

当不确定性越大时,它所包含的信息量也就越大,信息熵也就越高

假设有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算法)

ID3算法

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

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

在计算的过程中,我们会计算每个子节点的归一化信息熵,即按照每个子节点在父节点中出现的概率,来计算这些子节点的信息熵

所以信息增益的公式可以表示为:

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

假设:天气=晴的时候,会有5次去打篮球,5次不打篮球。其中D1刮风=是,有2次打篮球,1次不打篮球

D2刮风=否,有3次打篮球,4次不打篮球。那么a代表节点的属性,即天气=晴

graph TD A[D=5次打篮球5次不打篮球]-->B[D1=2次打篮球1次不打篮球] A[D=5次打篮球5次不打篮球]-->C[D2=3次打篮球4次不打篮球]

根据假设套用ID3公式,计算如下

$entropyD = -(1 / 2 * log(2, 1 / 2) + 1 / 2 * log(2, 1 / 2));
$entropyD1 = -(2 / 3 * log(2, 2 / 3) + 1 / 3 * log(2, 1 / 3));
$entropyD2 = -(3 / 7 * log(2, 3 / 7) + 4 / 7 * log(2, 4 / 7));
$gainDa = $entropyD - (3 / 10 * $entropyD1 + 7 / 10 * $entropyD2);
var_dump($gainDa);

现在我们回到训练集数据,基于ID3的算法规则,完整地计算下我们的训练集

import math

'''
先以天气作为属性划分,分为:晴天、阴天、小雨
我们以+号表示去打篮球,-号表示不去打篮球,如下:

D1(天气=晴天)={1-,2-,6+}    # 第1行不打篮球、第2行不打篮球、第6行打篮球
D2(天气=阴天)={3+,7-}       # 第3行去打篮球、第7行不打篮球
D3(天气=小雨)={4+,5-}       # 第4行去打篮球、第5行不打篮球
'''

# 天气
entropyD = -(3 / 7 * math.log2(3 / 7) + 4 / 7 * math.log2(4 / 7))
entropyD1 = -(1 / 3 * math.log2(1 / 3) + 2 / 3 * math.log2(2 / 3))
entropyD2 = -(1 / 2 * math.log2(1 / 2) + 1 / 2 * math.log2(1 / 2))
entropyD3 = -(1 / 2 * math.log2(1 / 2) + 1 / 2 * math.log2(1 / 2))
gainDa = entropyD - (3 / 7 * entropyD1 + 2 / 7 * entropyD2 + 2 / 7 * entropyD3)
print(gainDa)

'''
同理我们再分别计算出温度、湿度、刮风为属性的根节点的信息增益
D1(温度=高)={1-,2-,3+,4+}
D2(温度=中)={6+,7-}
D3(温度=低)={5-}

D1(湿度=高)={3+,4+,5-,7-}
D2(湿度=中)={1-,2-,6+}

D1(刮风=是)={2-,6+,7-}
D2(刮风=否)={1-,3+,4+,5-}
'''


def entropy(data: list):
    res = 0
    for i in data:
        res = res + i * math.log2(i)
    return -1 * res


# 温度
entropyD1Temp = entropy([1 / 2, 1 / 2])
entropyD2Temp = entropy([1 / 2, 1 / 2])
entropyD3Temp = entropy([1])
gainDaTemp = entropyD - (4 / 7 * entropyD1Temp + 2 / 7 * entropyD2Temp + 1 / 7 * entropyD3Temp)
print(gainDaTemp)

# 湿度
entropyD1Hum = entropy([1 / 2, 1 / 2])
entropyD2Hum = entropy([2 / 3, 1 / 3])
gainDaHum = entropyD - (4 / 7 * entropyD1Hum + 3 / 7 * entropyD2Hum)
print(gainDaHum)

# 刮风
entropyD1Wind = entropy([2 / 3, 1 / 3])
entropyD2Wind = entropy([1 / 2, 1 / 2])
gainDaWind = entropyD - (3 / 7 * entropyD1Wind + 4 / 7 * entropyD2Wind)
print(gainDaWind)

通过计算,我们发现温度作为属性的信息增益最大

所以我们将信息增益最大的节点作为父节点,这样可以得到纯度高的决策树,所以我们将温度作为根节点

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

经过多轮计算,我们得到最终的决策树,如下图:

ID3的算法规则相对简单,可解释性强。同样也存在缺陷,比如我们会发现ID3算法倾向于选择取值比较多的属性

如果我们把“编号”作为一个属性(一般情况下不会这么做,这里只是举个例子),那么“编号”将会被选为最优属性

但实际上“编号”是无关属性的,它对“打篮球”的分类并没有太大作用

所以ID3有一个缺陷就是,有些属性可能对分类任务没有太大作用,但是他们仍然可能会被选为最优属性

这种缺陷不是每次都会发生,只是存在一定的概率。在大部分情况下,ID3都能生成不错的决策树分类

针对可能发生的缺陷,后人提出了新的算法进行改进,它就是大名鼎鼎的C4.5算法

results matching ""

    No results matching ""