赞
踩
前言:本文章参考了前辈的文章并结合了自己的思考,前辈的参考链接会在文末或者文中给出。如果对本文内容有指正,欢迎留言。
决策树是一种机器学习的方法,它是一种树形结构(可以是二叉树或者非二叉树),其中每个内部节点表示一个属性上的判断,每个分支代表一个判断结果的输出,最后每个叶节点代表一种分类结果。其实决策树的分类和人在生活中的决策很相似,举个栗子:
今天我想网购台电脑,刷到一台看着挺带劲的机子,在决定买不买之前,我心路历程是这样的:
看到没,刚刚的心路历程就是一个决策过程。我通过品牌、价格、配置、差评率等属性来决定“买还是不买 ”。再举一个经典的例子:给出如下的一组数据,一共有十个样本(学生数量),每个样本有分数,出勤率,回答问题次数,作业提交率四个属性,最后判断这些学生是否是好学生。最后一列给出了人工分类结果。
然后用这一组附带分类结果的样本可以训练出多种多样的决策树,这里为了简化过程,我们假设决策树为二叉树,且类似于下图:
通过学习上表的数据,可以设置A,B,C,D,E的具体值,而A,B,C,D,E则称为阈值。当然也可以有和上图完全不同的树形,比如下图这种的:
其中非叶子节点表示判断条件,叶子节点表示决策结果,箭头表示在一个判断条件在不同情况下的决策路径。
决策树是一种十分常用的分类方法,需要监管学习(Supervised Learning),监管学习就是给出一堆样本,每个样本都有一组属性和一个分类结果,也就是分类结果已知,那么通过学习这些样本得到一个决策树,这个决策树能够对新的数据给出正确的分类。具体过程如下:
�����=������������(��)
�=������������(�)
但是决策树是怎样构造的呢?我们继续拿上图中的例子讲解,判断一个学生是不是好学生,首先看这个学生的分数是不是大于A,如果大于那就是好学生。如果不大于A,其次再看该学生的出勤率,接下来才看回答次数,最后才看作业提交率。但是为什么要把学生的分数当作第一个判断的节点呢?为什么要把作业提交率作为最后一个判断的节点呢?如果弄清这个问题,便自然而然构建出了一颗决策树。
为了弄清决策树的构建过程,需要先了解一个概念:熵。看到熵这个字大部分人会觉得很深奥,其实不难,熵表征的是一个东西的混乱程度、不确定性,该物体越混乱、越不确定,它的熵越大,越整齐、越确定,熵越小。我摘抄知乎的一个高赞回答:
举个栗子,对于这样一堆沙子,我们可以随意的更改沙堆的形状,甚至可以组成数万亿种形状,但不管哪种形状,构成沙子的“结构”不会发生任何改变,从熵的意义上讲,这个沙堆的熵值很高,因为沙堆不正确、不确定性比较强(这里的沙堆泛指一切自然形成的沙堆,大同小异)。
但是,当我们把沙堆弄成这样一个沙堡:
这个时候,让一堆沙组成图中这种有规则形状的沙堡的组合就会骤降,甚至只有几种组合能让一堆沙看起来和图中的沙堡特别相似(沙子的结构仍然不会发生任何变化)。从熵的意义上讲,这个沙堡的熵值很低,因为有了规则,不确定就很弱。
再举一个栗子,看下面两个集合,相比于 B 集合,A 集合就比较混乱、不确定性比较大。 B 集合中大部分都是 ① ,比较整齐,确定性较强,所以 B 集合的熵就比较小, A 集合混乱则熵比较大。
①②⑤⑥⑦①②④�=[ ① ② ⑤ ⑥ ⑦ ① ② ④ ]①①①①⑦①①④�=[ ① ① ① ① ⑦ ① ① ④ ]
熵的表达式为:
=�������=−∑�=1��(��)���2�(��)
其中 �(��) 为 �� 出现的概率。假如是 2 分类问题,当 A 类和 B 类各占 50% 的时候,
(�������=−(0.5∗���2(0.5)+0.5∗���2(0.5))=1
当只有 A 类,或只有 B 类的时候,
())�������=−(1∗���2(1)+0)=0
所以当 Entropy 最大为1的时候,是分类效果最差的状态,当它最小为0的时候,是完全分类的状态。
再回到刚刚 A、B 集合的例子中去。对于集合 A ,出现 ①②④⑤⑦⑥ 的概率都比较低,因为 A 中元素比较杂,当出现的概率比较低的时候 �(��) 比较小,当把每个元素出现的概率值带入熵的表达式中,得到的熵是比较大的。而对于集合 B,其中 ① 比较多,该元素出现的概率比较大,当把 B 中每个元素的出现的概率带入熵的表达式,得到的熵值比较小。
现在理解了熵的概念,我就可以使用熵的值作为一个标准来构建决策树。熵在理想状态下等于零,一般实际情况下,熵介于0和1之间。熵的不断最小化,实际上就是提高分类正确率的过程。
比如上表中的4个属性:单一地通过以下语句分类:
最后发现分数小于 70 为【不是好学生】这条分错最少,也就是说此时不确定性比较弱,既熵最小,所以应该选择这条为父节点进行树的生成,当然分数也可以选择大于 71,大于 72 等等,出勤率也可以选择小于 60,65 等等,总之会有很多类似上述 1 ~ 4 的条件,最后选择分类错最少即熵最小的那个条件。而当分裂父节点时道理也一样,分裂有很多选择,针对每一个选择,与分裂前的分类错误率比较,留下那个提高最大的选择,即熵减最大的选择。
因为构建树的基本想法是随着树的深度的增加,节点的熵迅速的降低。熵降低的速度越快越好,这样就能得到一颗较矮的决策树(能 3 步判断出来就不用 5 步判断),用一个例子讲解决策树的构建:
根据当天的状况,来决定今天出不出去玩耍。例如第一条数据,今天阳光明媚,温度比较热,空气湿度比较高,没风。今天没有出去玩耍。上面 14 条数据,我们可知新的一天打球的概率是 9/14 ,不打的概率是 5/14。此时的熵为:
−914���2914−514���2514=0.940
0.940 就是我们不构建决策树,该数据集固有的熵值:0.940,接下来我们需要一步步构建决策树,使得熵值不断减小,并且还需寻找下降最快的办法。
构建决策树第一个问题就是该把那个属性当作根节点?outlook?temperature?windy?首先,把这四个属性当作根节点分别构建四棵树,结果如下:
紧接着分别计算四种划分的熵值。outlook 当作根节点的时候,熵的计算方法为:
������=−25���225=0.971
������=−35���235=0.971
515∗0.971+414∗0+514∗0.971=0.693
这样的话,如果选择 outlook 当作根节点,熵就原始的 0.940 下降到了 0.693,信息熵增益 gain(outlook)为:0.940-0.693=0.247。
信息增益的公式为:
����(�,�)=�������(�)−∑�=1�|��||�|�������(��)
其中 D 是数据集,既 14条样本,a 是选择的属性 ( 此处 a 为 outlook ),a 中一共有 V 个取值 (此例子中 V 为等于3,分别为 sunny,humidity,temperature)。用这个 V 取值去划分数据集 D,分别得到数据集 �1 到 ��,分别求这 V 个数据集的信息熵,并将其求加权平均。两者的差得到的就是信息增益,公式中减号之前的值 �������(�) 为划分前的熵值。
同理可得:gain(temperature) = 0.029 、 gain(humidity)=0.152、gain(windy)=0.048。
其中 gain(outlook) 最大,说明熵下降的最快,该属性当作根节点能够分的更好。所以根节点就选取 outlook。
通过上面的例子选择出了根节点,紧接着可以采用相同的办法继续划分,既基于什么样的划分可以使得信息熵增益的值最大、熵下降的最快。比如现已知 outlook 作为根据点,剩余的属性还有 temperature、humidity、windy。分别以此三个属性为根节点构建决策树,取 gain 信息熵增益最大的属性,紧接着递归进行下去就可以构建出一颗决策树。
上一小节的例子中,基于信息增益的决策树构建算法叫作 ID3。比较常用还有 C4.5 和 Classification And Regression Tree ( CART ),CART 的分类效果一般优于其他决策树。在介绍 C4.5 和 CART 之前,先来看一下 ID3 有什么缺点。
上图是刚刚 ID3 构建算法用的例子,在前面加了另外一个特征 ID,值就是样本的 ID 序号。每条数据的特征有 5 个,分别是 outlook、temperature、humidity、windy、ID。如果用信息增益最大化的方法挑选根节点会发现,当 ID 此属性作为根节点的时候信息增益值是最大的,但这样划分显然不正确,因为 ID 的值和是否取 “play” 没有任何关系。
利用 ID 作为根节点会划分出很多分支,而每个分支中的值很少。这种情况的出现会导致信息增益非常大。所以用信息增益作为评判划分属性的方法有一定的缺陷的:信息增益准则对那些属性的取值比较多的属性有所偏好,也就是说,采用信息增益作为判定方法,会倾向于去选择属性取值比较多的属性。如上图中每条样本的 ID 都是不相同的,此时属性取值很多。如果用 ID 这个属性去划分原数据集,那么原数据集中有多少个样本,就会被划分为多少个子集,每个子集只有一条样本,这种极端情况下每个子集的信息熵就是0了,就是说此时每个子集都特别纯。这样的话,会导致信息增益公式的第二项 ∑�=1�|��||�|�������(��) 整体为0,这样导致的结果是,信息增益计算出来的特别大,然后决策树会用 ID 这个属性来划分原数据集D,其实这种划分毫无意义。因此,为了改变这种不良偏好带来的不利影响,提出了 C4.5 算法。
为了解决上方 ID3 提到的问题,引入了新的概念信息增益率。信息增益率:
����������(�,�)=�����(�,�)��(�)��(�)=−∑�=1�|��||�|���2|��||�|
��(�) 被称为是的“固有值”,仔细观察该式子,可以发现改值的大小取决于属性 a 的纯度,如果 a 只含有少量的取值的话,那么 a 的纯度就比较高,否则的话,a 的取值越多,a 的纯度越低,的 ��(�) 值也就越大,因此,最后得到的信息增益率就越低。
所以信息增益率是信息增益的值除以固有值,虽然以 ID 为根节点得到的信息增益非常大,但是此时固有值也非常大。除以一个非常大的值会使得比值变小,从而得到一个较小的信息增益率。如果将信息增益率作为划分标准会得到更好的划分效果。
虽然提高了分类效果,C4.5还是有一些缺点:
C4.5 生成算法基于较为复杂的熵来度量,使用的是多叉树(二叉树效率更高),只能处理分类不能处理回归。对这些问题,Classification And Regression Tree 做了改进,可以处理分类,也可以处理回归。
ID3 中基于信息增益选择特征,选择增益大的特征作为当前树的根节点。C4.5中,采用信息增益率选择特征,减少因特征值多导致信息增益大的问题。CART 分类树算法使用基尼系数来代替信息增益率,基尼系数代表了模型的不纯度,基尼系数越小,不纯度越低,特征越好。这和信息增益(比)相反。
假设K个类别,第k个类别的概率为 ��,概率分布的基尼系数表达式:
����(�)=∑�=1���(1−��)=1−∑�=1���2
如果是二分类问题,第一个样本输出概率为p,概率分布的基尼系数表达式为:
����(�)=2�(1−�)
对于样本D,个数为|D|,假设K个类别,第k个类别的数量为|��|,则样本D的基尼系数表达式:
����(�)=1−∑�=1�(∣��∣∣�∣)2
对于样本D,个数为|D|,根据特征A的某个值a,把D分成|�1|和|�2|,则在特征A的条件下,样本D的基尼系数表达式为:
����(�,�)=∣�1∣∣�∣����(�1)+∣�2∣∣�∣����(�2)
比较基尼系数和熵模型的表达式,二次运算比对数简单很多。尤其是二分类问题,更加简单。
和熵模型的度量方式比,基尼系数对应的误差有多大呢?对于二类分类,基尼系数和熵之半的曲线如下:
基尼系数和熵之半的曲线非常接近,仅在45度角附近误差稍大。因此,基尼系数可以做为熵模型的一个近似替代。CART分类树算法每次仅对某个特征的值进行二分,而不是多分,这样CART分类树算法建立起来的是二叉树,而不是多叉树。
CART分类树算法具体流程
CART分类树建立算法流程,之所以加上建立,是因为CART分类树算法有剪枝算法流程。算法输入训练集D,基尼系数的阈值,样本个数阈值。输出的是决策树T。
算法从根节点开始,用训练集递归建立CART分类树。
对生成的决策树做预测的时候,假如测试集里的样本 A 落到了某个叶子节点,而节点里有多个训练样本。则对于 A 的类别预测采用的是这个叶子节点里概率最大的类别。
如上图涉及的例子属于离散型问题,其离散的属性可以列举处理进行划分。比如 temperature 有 hot、mild、cool 而不是 30°,20°,5° 这样的连续值。所以处理离散型问题不需要考虑属性值的划分。
上方例子中,属性基于离散变量的,然而我们现实的机器学习任务中也会遇到连续属性,如何处理连续属性呢?连续属性的可取值数目是无限的,因此不能像前面处理离散属性枚举离散属性取值来对结点进行划分。因此需要连续属性离散化,常用的离散化策略是二分法,这个技术也是 C4.5 中采用的策略。下面来具体介绍下,如何采用二分法对连续属性离散化:
下面举个具体的例子,来看看到底是怎样划分的。给定数据集如下(数据集来自周志华《机器学习》)
对于数据集中的属性“密度”,决策树开始学习时,根节点包含的17个训练样本在该属性上取值均不同。我们先把“密度”这些值从小到大排序:
根据上面计算 �� 的公式,可得:
下面开始计算 t 取不同值时的信息增益:
可以发现,当 t = 0.381 时, ����(�,�,�) 最大为 0.263 因此选择此处为划分点。
首先剪枝(pruning)的目的是为了避免决策树模型的过拟合。因为决策树算法在学习的过程中为了尽可能的正确的分类训练样本,不停地对结点进行划分,因此这会导致整棵树的分支过多,也就导致了过拟合。分支过多在分类时考虑数据的信息就更为丰富(每次分支都会考虑一种特征信息),这就导致了能拟合住噪声信息。所以,我们要尽可能生成一个较矮的决策树。让树长不高,那就需要剪枝。
决策树的剪枝策略最基本的有两种:预剪枝(pre-pruning)和后剪枝(post-pruning):
通过剪枝操作,可以降低树的高度,降低过拟合的风险。如果树非常高,叶子节点可能就比较多,叶子节点过多对模型的泛化能力是有影响的,举个极端的例子,叶子的个数等于样本的个数,每个叶子节点可以划分到 1 个样本,这样叶子节点非常纯,但是也失去分类的意义。所以树矮,叶子节点少才是我们追求的目标。
衡量决策树算法的效果常常使用评价函数:
�(�)=∑�∈������⋅�(�)
我们希望此评价函数越小越好。其中 t 为叶子节点, 计算所有叶子节点的 ��⋅�(�) , 然后求和。其中 �(�) 为当前叶子计算出来的熵值或者基尼系数的值, 而 �� 是代表有几个样本归到当前叶子节点中, 作为权重。我们希望每个叶子节点划分到的样本类别是非常"纯"的,不混乱的, 这样才代表分类效果好, 分类效果稳定。所以, 越纯 �(�) 越小, �(�) 越小, 故使评价函数尽可能小是我们的目标。
��(�)=�(�)+�⋅∣�����∣
上面也是一个评价函数,该评价函数多了另一项 �⋅∣�����∣ ,其中 ∣�����∣ 代表叶子节点的个数。意思很容易明白,叶子节点个数越多,损失越大。我们目的是使得 ��(�) 越来越小, � 代表叶子个数对评价结果的影响程度。
森林:刚刚提到的是如何构建一棵树,而很多树组合在一起便是一个森林。构建出了很多决策树,综合这些决策树的决策结果得出最后的分类或者回归结果。
随机:比如我们有 10 个样本,用这些样本构造决策树。第一棵决策树建立的时候不全部使用这 10 个样本,而是使用 0.6 * 10 = 6 个样本 (0.6相当于一个参数)。在建立第二棵树是还是原始 10 个样本中进行采用 6 个样本(有放回采样)。这样一棵棵决策树构建过程中采用的 6 个样本可能是各不相同的,引入随机性可增加模型的健壮性,使得模型泛化能力更强。
刚刚是随机去除一部分样本来构建决策树,除此之外,一个样本还有很多特征,而随机剔除一些特征也能够增加模型的泛化能力。因为不知道哪些特征对模型泛化能力更有用,所以进行随机的剔除。这类似于深度学习中的 Dropout。
在Jupyter Notebook导入相关库:
from sklearn.datasets import load_iris from sklearn import tree import sys import os from IPython.display import Image import pydotplus import pandas as pd
IPython.display 的 Image 和 pydotplus是为了可视化生成的决策树。没安装 pydotpplus 可以使用 pip3 命令安装。
接着用sklearn自带的鸢尾花数据:
iris = load_iris() table = pd.DataFrame(iris['data']) table['target'] = iris['target'] # target => ['setosa', 'versicolor', 'virginica'] table.columns = ['sepal length (cm)','sepal width (cm)','petal length (cm)','petal width (cm)', 'target'] print(table)
数据集总共150个花,总共三种花,target表示了种类号,每个花有四个特征。下面利用这个数据构建决策树
# 创建决策树对象,使用信息熵作为依据 clf = tree.DecisionTreeClassifier(criterion='entropy') # fit方法分类。features为iris.data,labels为iris.target clf = clf.fit(iris.data, iris.target)
接着可视化一下这个树,看看它是什么样子的
# 可视化 dot_data = tree.export_graphviz(clf, feature_names=iris.feature_names, graph = pydotplus.graph_from_dot_data(dot_tree) img = Image(graph.create_png()) graph.write_png("out.png")
如果报错:GraphViz's executables not found
你需要使用apt或者yum安装GraphViz。Windows 需要下载一个工具,具体请百度一下。并且在代码中加入以下代码:
os.environ["PATH"] += os.pathsep + 'D:\Program files\graphviz\\release\\bin'
这就是生成的决策树,entropy表示的是熵,samples表示该节点所含样本数量,value表示不同类别的个数有多少。
这样,就完成了决策树的搭建
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。