决策树
- 一、决策树的理解
- 1.1 字面含义
- 1.2 定义
- 二、决策树的组成
- 三、决策树的实现流程
- 3.1 构造
- 3.1.1 节点的来源
- 3.1.2 决策节点的排布
- (1)纯度及其度量
- (1.1)纯度 - Purity
- (1.2)信息熵 - Information Entropy
- (1.2.1)来源
- (1.2.2)公式
- (1.3)基尼值
- (1.4)简单案例1
- (2)决策树纯度的度量
- (2.1)信息增益(information gain) - ID3算法
- (2.1.1)信息增益的计算
- (2.1.2)简单案例2
- (2.1.3)ID3的缺陷
- (2.2)信息增益率(gain ratio) - C4.5算法
- (2.2.1)信息增益率的计算
- (2.2.2)简单案例3
- (2.2.3)特点
- (2.3)基尼指数(gini index) - CART算法
- (2.3.1)基尼系数
- (2.3.2)分类与回归
- (2.3.3)基尼指数的计算
- (2.3.4)简单案例4
- 3.2 剪枝 - pruning
- 3.2.1 假性关联与过拟合现象
- 3.2.2 剪枝处理
- (1)预剪枝 - Pre-pruning
- (1.1)概念
- (1.2)方法
- (1.3)优点与缺点
- (2)后剪枝 - Post-pruning
- (1.1)概念
- (1.2)方法
- (1.3)优点与缺点
- 四、连续值与缺失值
- 4.1、连续值处理
- 4.1.1、离散属性与连续属性
- 4.1.2、二分法 - 连续属性离散化
- (1)二分法的划分
- (2)简单案例5
- 4.2、缺失值处理
- (1)处理方法
- (2)简单案例6
- 五、多变量决策树
- 六、应用场景
- 七、总结
- 八、案例
- 0、决策树可视化操作
- 1、决策树之分类树 - 鸢尾花数据集分类
- 2、决策树之回归树 - 波士顿房价数据集回归
- 3、泰坦尼克号生存者预测
- 3.1、项目分析
- (1)样本的分析
- (2)项目流程
- (3)决策树的函数使用
- 3.2、项目实践
- (1)数据获取
- (2)数据探索
- (3)数据清洗
- (4)特征选择
- (5)决策树模型
- (6)模型测试&评估
- (7)可视化决策树
- 修改时间
一、决策树的理解
决策树是一款非常有地位的分类算法。任意一场数据分析比赛,名列前茅的除了深度学习,剩下的机器学习算法中多半是选用XGBoost算法或者Lightbgm算法。然而这两个算法都是基于决策树分类算法而发明出来的。
1.1 字面含义
决策树,也称“判定树” - Decision Tree。我们将名字分为“决策”和“树”来理解。
决策:
“决策”是指“判断”,这种“判断”就像编程语言中的if-else判断结构一样:首先if + 判断条件,判断是否为真,为真则符合条件而结束判断;否则继续下一个条件判断,else后面再次介入一个if-else判断结构。
例如,举一个银行根据客户的收入、房产和家庭情况来判断是否给客户发放贷款的例子:
if salary_year > 300000:
return "可以贷款"
else
if estate:
return "可以贷款"
else
if son_or_daug == True:
return "可以贷款"
else
return "不能贷款"
... ...
树:
“树”是指数据结构中的树形结构,那么决策树则表示一类采用树形结构的算法,如二叉树和多叉树等等。在scikit-learn中,决策树算法默认使用CART算法,而它只支持二叉树,如下图所示。
当然,决策树也支持其他算法,如ID3、C4.5等等,他们既支持二叉树也支持多叉树。这些算法的具体作用,在后面的章节介绍。
1.2 定义
通过以上分析可以观察出决策树的两个特征:规则(或判决条件,if 的内容)和嵌套(else层层递进)。所以,我们可以定义:决策树表示利用一组嵌套的规则对数据集进行分类的算法。(上面例子将数据集分成可以和不可以贷款的对象)
但由于机器学习不同于普通的编程,它是模型自动根据数据集来定义规则。如果以前有使用过模型进行实践的应该不难理解,只需要调用决策树算法包中的几句简单的函数便可以轻松得到结果,而无需自己一行行码。
所以,有人定义为:决策树算法是根据给定的数据集归纳出分类规则,并采用自顶向下的递归划分 (Recursive Partitoning) 的方式,并以树的形式展现出来。
二、决策树的组成
节点是树的主体部分,它一般被分为两类:决策节点和叶子节点。
决策节点:通过条件判断而进行分支选择的节点。如:将某个样本中的属性值(特征值)与决策节点上的值进行比较,从而判断它的流向。
叶子节点:没有子节点的节点,表示最终的决策结果。下图中,叶子节点的值有两种,即可以贷款和不能贷款。
如图,决策树所有的内部节点(决策节点、非叶子结点)为矩形,叶子节点为椭圆形。
决策树的深度:定义为所有节点的最大层次数。
决策树具有一定的层次结构,根节点的层次数定为0,从下面开始每一层子节点层次数+1。根据上图来判断树的深度,则最大深度为3,说明要得到一个决策结果,最多经过3次判定。
三、决策树的实现流程
如何像上面一个例子中展现的图一样实现一个的决策树?
完整决策树的实现需要经历两个阶段:构造和剪枝。
3.1 构造
通过决策树的组成得知,决策树主要由决策节点和叶子节点构成。
于是产生了两个问题:
问题一:决策节点和叶子节点从何而来?
问题二:决策节点如何排布?
以前面银行借贷的决策树为例,对于问题一,那些if判断条件,以及判断结果是依据什么而来的?,对于问题二,为什么年薪的优先级最高,房产其次,最后是家庭情况,这样的排布有什么道理呢?
3.1.1 节点的来源
决策树主要由节点构成,而节点由决策条件和决策结果构成,而决策条件和结果的依据来源于样本的属性特征。由此可知,决策树的构造为:选择样本特征作为节点的过程。那么样本的特征为决策树节点的来源。
样本的属性特征
决策树的构造离不开样本的属性特征,只有知道研究的对象具备什么特性,才能构造出合理的决策树。这里列举了数据集的样本。
ID | 年薪 | 是否有房产 | 是否有子女 | 类别 |
1 | 350000 | 无 | 无 | 可以借贷 |
2 | 100000 | 无 | 无 | 不能借贷 |
第二张决策树图中每一个if条件是根据第一张样本图中的每一列属性来进行构造的,样本中的每一列称作属性(Attribute) 或特征或特征维度。(多个属性被称作属性集或者特征维度集)
对于特征也有不同的分类。年收入的结果与其他三项有明显区别,它为数值型,可以比较大小,一般为整数或者实数,这种列属性则称为数值特征。而是否有房产和子女则不能比较大小,只有有和没有两种情况,这种列则称为类别特征。
3.1.2 决策节点的排布
决策节点如何排布?如何构造出最优属性划分?通过纯度这个指标,一般而言,我们希望每次判别节点能将样本进行最低错误率的划分,该节点正确率也就越高,同时也表明纯度也越高,该节点也就越适合优先排布。
(1)纯度及其度量
纯度是衡量节点优劣的判断指标,纯度越大的决策树越优,结果的分歧越小。
纯度的计算需要信息熵或者基尼值,他们可以用来度量某样本的纯度。
(1.1)纯度 - Purity
每一层决策节点都可以将样本划分为两个子集,而每一次划分都不能保证完全将样本划分分类正确,因为总有一些决策树规则之外的样本,在进行划分的时候会出现错误,比如ID=5的样本,在有子女的情况下,应该是可以借贷的,但是类别是不能借贷。
这可能是样本的属性不完全,导致决策树规则生成不完整,从而使分类出现异常,但是,世界上不可找到最全的样本,也就不可能生成最完美的决策树模型,我们能做的就是在有限的条件下,最小化误差。
以“年薪>300000”的属性为判别节点进行划分,值为“是”时,类别全部为“可以借贷”,说明纯度很高,而值为“否”时,类别既有“可以借贷”也有“不能借贷”,表明纯度相对较低。
ID | 年薪>300000 | 是否有房产 | 是否有子女 | 类别 |
1 | 是 | 无 | 无 | 可以借贷 |
2 | 否 | 无 | 无 | 不能借贷 |
3 | 否 | 有 | 无 | 可以借贷 |
4 | 是 | 无 | 有 | 可以借贷 |
5 | 否 | 无 | 有 | 不能借贷 |
纯度表示结果分歧大小,决策树的构造过程也可以叫做寻找纯净划分的过程。
(1.2)信息熵 - Information Entropy
(1.2.1)来源
信息熵源于热力学中的熵(Entropy),最早由德国物理学家克劳修斯提出,用来表述某个个体的混乱程度。
在信息论中,随机离散事件出现的概率存在着不确定性。为了衡量这种信息的不确定性,美国数学家信息学之父,香农引入了信息熵的概念。它代表了一个给定数据集中的不确定性或者随机性程度的度量。
比如:当一个数据集中的记录全部为同一类时,则没有不确定性,此时熵为0.因此我们可以把熵看成是信息的数学期望。
(1.2.2)公式
若要求出熵的大小,则先要计算信息:
设某个事物具有N种相互独立的可能结果,则信息的定义为:
之所以是以2为底的对数,可能和信息中的最小单位比特有关 (一个比特只有0或1,两种状态),其中 表示第 个分类, 表示第 个分类的概率函数, 中
并且每个概率之和为1:
于是,计算信息熵的数学公式可以表示为:
求和( 每种属性概率 * 每种属性的信息 )
且 表示以节点 为分类
对于一个简单的二元分类,此时n=2,那么其整体熵为:
该公式表示一种度量帮我们反映出这个信息的不确定度。当不确定性越大时,它所包含的信息量也就也大,信息熵也就越高。
算法会根据所有样本-信息熵的变化 (信息增益) 来选择最佳分类。因而信息熵就是决策树方法中分支产生的衡量标准之一。
(1.3)基尼值
基尼值同样也是一种度量节点纯度的方法,与信息熵计算公式十分相似
- 作用类似于信息熵中的 ,但省去了对数运算,更加方便计算。
(1.4)简单案例1
同样以银行借贷为例子,假设某个样本集中有12个客户,在某次决策树的构造过程中,对该样本集按照属性“是否能借贷”进行二元划分来计算纯度值,划分成D1和D2,其中D1有5个可以借贷的客户,1个不能借贷的客户;D2有一半可以借贷和不能借贷。
结果如下图,我们的目的是计算各个节点的信息熵和基尼值。
根据公式可得:
信息熵:
D: Entropy(D) =
D1:Entropy(D1) =
D2:Entropy(D2) =
基尼值:
D:Gini(D) = = 0.44
D1:Gini(D1) =
D2:Gini(D2) =
无论是信息熵还是基尼值,值越大,纯度越低。当所有类型的样本均匀混合时,如D2,信息熵和基尼值最大,纯度最低。
这种纯度的度量是基于自身单个节点,而对决策树纯度的度量是包括一个父节点和几个子节点,节点之间有相互关联。
(2)决策树纯度的度量
决策树的构建会基于前面纯度的度量,有三种不同决策树节点纯度的度量规则:信息增益、信息增益率和基尼指数。基于决策树的信息度量的不同方式,将决策树划分为三种最著名的算法,它们分别是:ID3、C4.5和CART。
(2.1)信息增益(information gain) - ID3算法
ID - Iterative Dichotomiser 迭代二分器的简称
ID3算法通过比较样本划分前后的信息熵的增减来衡量节点的纯度,即该节点的提纯能力的好坏。
(2.1.1)信息增益的计算
信息增益 父节点(划分前的样本)的信息熵
在计算的过程中:
- 代表子样本的信息熵
- 代表样本 选择属性 划分子样本时的信息增益。
- 符号 不是求绝对值的意思,而是求样本 的个数。如 是求样本 的个数, 求第
- 表示按照属性 划分后共有 个子样本。如划分后产生5个子样本,则此时
- 表示第
通过计算和比较不同属性的信息增益,值越大的,则提纯的效果越好,则优先级越高。
(2.1.2)简单案例2
题目内容同简单案例1,按照某方式划分后得到一个决策树,我们的目的是计算父节点D的信息增益
以及简单案例1中对各个节点信息熵的计算,可得:
根据信息增益的公式,将父节点信息熵减去子节点信息熵的占比,可得:
对父节点不同的划分,计算出的结果,信息增益越大,说明纯度的增加越大,优先级越高。
(2.1.3)ID3的缺陷
ID3算法有个特点:倾向于选择,值的种类较多的属性为高优先级节点。因为节点是按照信息增益的大小来进行划分与选择,节点属性值的种类越多,越容易将不同样本清晰地划分开来,子节点的信息熵越容易低,纯度越容易高,信息增益越容易大。
但是像属性"ID"这样每一行样本都具有不同种类,划分时,每一个ID为一个子节点,纯度最大,但却是无关属性,对后面的样本无法进行有效的预测。
像ID这样类似的、却不易观察的属性有一些,不过,这种缺陷发生的概率很小,大部分情况下都能生成效果较好的决策树。但是为了追求完美,改进版的ID3算法 - C4.5算法应运而生。
(2.2)信息增益率(gain ratio) - C4.5算法
为了减少样本某个属性的值的种类较多所带来的不利影响,C4.5算法在信息增益的基础上求得信息增益率来选择最优属性划分。
(2.2.1)信息增益率的计算
公式:
- - 固有值(intrinsic value),有的地方也叫属性熵
属性a的划分越多,k越大,则IV(a)的值通常会越大
- 为子集的个数
(2.2.2)简单案例3
题目内容同简单案例1,我们的目的是计算父节点D的信息增益率
直接计算固有值IV
于是:
(2.2.3)特点
- 采用信息增益率
- 采用悲观剪枝
- 离散化处理连续属性
- 处理缺失值
(2.3)基尼指数(gini index) - CART算法
Cart算法全称:Classification And Regression Tree - 分类回归树。
ID3与C4.5算法与信息熵有关,而基尼指数与基尼值有关。
(2.3.1)基尼系数
基尼指数用来衡量决策树纯度的一种指标,而经济学中有基尼系数(Gini index、Gini Coefficient),是一个用来衡量一个国家或地区居民收入差距的常用指标。
(2.3.2)分类与回归
正如CART的名字,它既可以做分类树也可以做回归树。如何区别两者呢?
假设给定下列数据集,用来构造决策树。
构造完成后的树,用来预测某个测试集的“是否能借贷”属性时,为分类树。分类树用来处理离散型数据,也就是数据种类有限的数据,它输出的是样本的类别。
用该树预测某个测试集的“年龄”属性时,为回归树。回归树用来处理连续型数据,也就是数据在某段区间内的取值,输出是一个数值。
ID | 年薪>300000 | 是否有房产 | 是否有子女 | 是否能借贷 | 年龄 |
1 | 是 | 无 | 无 | 可以借贷 | 30 |
2 | 否 | 无 | 无 | 不能借贷 | 20 |
3 | 否 | 有 | 无 | 可以借贷 | 26 |
4 | 是 | 无 | 有 | 可以借贷 | 34 |
5 | 否 | 无 | 有 | 不能借贷 | 18 |
(2.3.3)基尼指数的计算
- 表示根节点的子节点的基尼值
- 表示子节点的样本数占根节点总样本数的比例
(2.3.4)简单案例4
假设某个样本集中有12个客户,在某次决策树的构造过程中,对该样本集按照属性“是否能借贷”进行二元划分来计算纯度值,划分成D1和D2,其中D1有6个可以借贷的客户,D2有一半可以借贷和不能借贷。
结果如下图,我们的目的是计算根节点D的基尼指数。
通过观察基尼指数的公式,首先计算子样本D1和D2的基尼值,即
再通过公式,可以直接计算,每个子样本各占总样本一半比例
Gini_index(D) =
3.2 剪枝 - pruning
3.2.1 假性关联与过拟合现象
一般情况下,为了尽最大可能正确地分类训练样本,所以决策树会将样本的所有属性当做标准(标答)来进行构造。
但训练集有时会出现“假性关联”问题。训练集的样本属性真的是标准吗?真的可以反映其他数据集或者测试样本的属性吗?当然不是,这项标准可能只适合该训练集本身而已,所以会导致用训练样本得出的结果过好而出现过拟合现象,过拟合的决策树使用其他数据集测试时会突然出现结果不好的现象。
此时,就需要去掉决策树的部分分支,即去掉样本的部分属性,去掉那些与样本整体分类实际上不存在关联的属性,以此来降低过拟合的风险,提高模型的泛化能力。
3.2.2 剪枝处理
如何对决策树进行剪枝?同决策树算法一样,剪枝的算法也有很多款,但根据剪枝的触发时机的不同,基本分为两种:预剪枝和后剪枝
如何评估剪枝前后的性能呢?通过使用测试集对划分前后的精准度进行比较。
(1)预剪枝 - Pre-pruning
(1.1)概念
预剪枝:在决策树生成的过程中,每个决策节点原本是按照信息增益、信息增益率或者基尼指数等纯度指标,按照值越大优先级越高来排布节点。由于预剪枝操作,所以对每个节点在划分之前要对节点进行是否剪枝判断,如何判断?即:使用测试集按照该节点的划分规则得出结果。若验证集精度提升,则不进行裁剪,划分得以确定;若验证集精度不变或者下降,则进行裁剪,并将当前节点标记为叶子节点。
(1.2)方法
限定树的高度、限定节点的训练样本数、限定划分带来的纯度的提升的阈值
(1.3)优点与缺点
预剪枝使得决策树很多相关性不大的分支都没有展开,这不仅仅降低了过拟合的风险,还显著减少了决策树的训练时间开销和测试时间开销。
但另一方面,有些分支的当前划分虽不能提升泛化能力,甚至可能导致泛化能力暂时下降,但是在其基础上进行的后续划分却有可能提高性能。预剪枝基于“贪心”本质禁止这些分支展开,给预剪枝决策树带来了欠拟合的风险。
(2)后剪枝 - Post-pruning
(1.1)概念
后剪枝:已经通过训练集生成一颗决策树,然后自底向上地对决策节点(非叶子结点)用测试集进行考察,若将该节点对应的子树替换为叶子节点能提升测试集的精确度,则将该子树替换成叶子节点,该决策树泛化能力提升。
(1.2)方法
降低错误剪枝 - Reduced-Error Pruning, REP
悲观错误剪枝 - Pesimistic-Error Pruning, PEP
代价-复杂度剪枝 - Cost-Complexity Pruning, CCP
C4.5采用PEP,CART采用CCP
(1.3)优点与缺点
后剪枝决策树通常比预剪枝决策树保留了更多的分支。一般情况下,后剪枝决策树的欠拟合风险很小,泛化能力往往优于预剪枝决策树。
但是后剪枝过程是在生成完决策树之后进行的,并且要自底向上地对树中的所有决策节点进行逐一考察,因此其训练时间开销比未剪枝的决策树和预剪枝决策树都要大得多。
四、连续值与缺失值
4.1、连续值处理
决策树一般为分类树,用来处理具有离散属性的样本,但是现实项目中,会遇到很多连续属性,对它的处理方法有所不同。
4.1.1、离散属性与连续属性
前面案例中的属性都是通过离散属性来生成决策树,比如“是否贷款”,结果通常都是类别判定的有限集合,通常构造的的决策树为分类树。
然而,有些属性比如说前面案例样本中的“年龄”属性,值为:22、30和27等等,基本只要是实数就行,结果通常是实数型的无限集合,这样的属性为连续属性,构造的决策树为回归树。
4.1.2、二分法 - 连续属性离散化
原来划分离散属性时,如“是否贷款”,可以直接按照属性值来对节点进行划分,如:可以贷款与不能贷款。
然是对于连续属性,如“年龄”,则不可能也按照属性值来对节点进行划分的规则,如:1, … …, 100。没办法通过某些值进行划分,种类过多且数量密集,但是可以将这些值划分成几个不同类型的区间,于是连续属性离散化技术便应运而生。
连续属性离散化技术有很多,但是最简单的策略则是二分法(bi-partition),这也是C4.5决策树算法中采用的机制。
(1)二分法的划分
给定样本集D和连续属性a,假设a在D中出现了n个不同的取值,将这些值从小到大进行排序,记为。
划分点选取公式为:
- 。n个值取划分点,除去第一个点的左侧和最后一个点的右侧不能选择,中间一共有n-1个划分点。
拥有n-1个划分节点后,需要选取最佳划分点,则又可以像离散属性那样考察这些划分点。比如计算信息增益:
- Gain(D, a, t)表示样本集D基于划分点t二分后的信息增益,前面的max表示选择使结果最大化的划分点
- t 为划分点,中 取+和-代表划分点的右侧集合和左侧集合
- ,表示划分点 t 属于上一个公式中的取值之一
(2)简单案例5
西瓜书中的一个案例改编而来,假设对于某种水果有一批样本,它们的密度和含糖量以及质量分别为:
ID | density | qualify |
1 | 0.697 | good |
2 | 0.774 | good |
3 | 0.634 | good |
4 | 0.608 | good |
5 | 0.556 | good |
6 | 0.403 | good |
7 | 0.481 | good |
8 | 0.437 | good |
9 | 0.666 | bad |
10 | 0.243 | bad |
11 | 0.245 | bad |
12 | 0.343 | bad |
13 | 0.639 | bad |
14 | 0.657 | bad |
15 | 0.360 | bad |
16 | 0.593 | bad |
17 | 0.719 | bad |
目的:求密度的信息增益。
密度:
对密度数据从小到大排序( + 与 - 表示好与不好):
0.243-、0.245-、0.343-、0.360-、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-
计算中位数:
0.244、0.294、0.3515、0.3815、0.420、0.459、0.5185、0.5745、0.6005、0.621、0.6365、0.648、0.6615、0.6815、0.708、0.7465
计算信息增益:
划分前,8个好样本,9个差样本
划分后,分成两个子集,按照划分点来一个个计算:
由于篇幅过大,不一一计算,经过验证,第四个中位数为最佳划分点,具体计算如下
:第四个和第五个点中间划分开,统计结果如下:
左右子集 | good | bad |
0 | 4 | |
8 | 5 |
决策树对连续节点的划分则按照“密度 0.3815” 为决策节点,分为左侧4个元素的左子集,右侧13个元素的右子集。
然而不同于离散属性的是,连续属性可以继续将子集作为父节点,继续划分子集,比如:在13个元素的右子集中继续进行“密度 0.621”的划分。
4.2、缺失值处理
在样本获得的过程中,难免会因为某些原因,比如隐私和成本问题,致使最后拿到的样本集出现某些属性数据的缺失。
当缺失的数据非常少时,一般直接舍弃掉那些缺失的数据;而当缺失的数据较多时,简单舍弃则是对样本的极大浪费,则按照一定的方法
(1)处理方法
对信息增益的计算公式进行修改:
其中:
- 表示整个样本,表示不包含缺失值的样本
- 表示完整度,为
- 表示划分的个数, 从 到
- 类似于以前公式中的 ,当默认为1时,两者相同;但是在每个样本的权值有差异的情况下,按照权值来计算比例。
- 为对子集内不同分类结果的频率,即不同分类结果的数量占该子集元素总数的比值。
C4.5算法在ID3算法的基础上进行缺失值处理的改进,就使用了此方法。
(2)简单案例6
下列是路人对某出行方式的便利程度的数据收集。如果简单的去除所有含缺失值的数据则只有4、6、8、9、12、15可以使用,损失了一大半数据。
目标:将出行按照飞机、高铁和汽车划分成三类,求出行的信息增益。(各样例的权值均为1)
ID | 出行 | 消费 | 时间 | 方便 |
1 | - | 高 | 早晨 | 是 |
2 | 飞机 | 高 | - | 是 |
3 | 飞机 | - | 早晨 | 是 |
4 | 高铁 | 高 | 晚上 | 是 |
5 | - | 中等 | 晚上 | 是 |
6 | 高铁 | 中等 | 早晨 | 是 |
7 | 飞机 | - | 下午 | 是 |
8 | 飞机 | 低 | 下午 | 是 |
9 | 飞机 | 高 | 下午 | 否 |
10 | 高铁 | 低 | - | 否 |
11 | 汽车 | - | 下午 | 否 |
12 | 汽车 | 低 | 下午 | 否 |
13 | - | 低 | 下午 | 否 |
14 | 汽车 | 高 | - | 否 |
15 | 飞机 | 低 | 晚上 | 否 |
16 | 汽车 | - | 下午 | 否 |
17 | 高铁 | 低 | - | 否 |
通过观察得知:
type | good | bad | sum |
飞机 | 4 | 2 | 6 |
高铁 | 2 | 2 | 4 |
汽车 | 0 | 4 | 4 |
sum | 6 | 8 | 14 |
通过对含有缺失值的样本的计算公式得知:
飞机的信息熵:
飞机所占比例:
高铁的信息熵:
高铁所占比例:
汽车的信息熵:
汽车所占比例:
带入完整公式:
五、多变量决策树
前面研究的都是单变量决策树,即每个决策节点都只针对一个属性进行判别。
例如(参照西瓜书中的案例):
对于多变量决策树(,multivatiate decision tree),每一个决策节点,都是一个合适的线性分类器,即多个属性组合成的一组分类规则。
多变量决策树指的是一类树,同样也有很多不同的算法,它是在单变量决策树的基础上改进而来,关键是对决策节点的改变,比如前面的将决策节点改成线性分类器.除此之外,还有在决策树的每个叶子节点上嵌入神经网络而形成感知机树(Perception tree)
六、应用场景
决策树算法的应用:
适用于需要“决策”的领域,如商业决策、管理决策等等,应用领域非常广。
实例:
Kinect是微软公司在2010年6月发布的XBOX 360体感交互外设,这是一种较为新颖的人机交互显示技术,使用者在Kinect镜头前所做的动作将实时呈现在游戏的画面中。为了实现这一效果,Kinect正是利用决策树分类算法,对镜头捕获的玩家行为图像进行分类,从而实现了效果良好的人及行为识别功能。
七、总结
决策树的含义: 类似于if-then-else的树形判断(二叉树则没有then)
决策树构成: 决策节点 + 叶子节点
决策树流程:
1.构造:
决策树算法:基于信息熵:ID3、C4.5;基于基尼值:基尼指数
2.剪枝:
类别 | 优点 | 缺点 |
预剪枝 | 去除无关分支降低过拟合;减少训练和测试时间开销 | 基于贪心算法,可能去除无关分支下的有关分支而导致欠拟合 |
后剪枝 | 保留更多分支,减少欠拟合风险 | 需要先生成决策树,训练开销较大 |
连续值处理: 连续值划分成区间值,每个区间值为一个类别,从而又形成离散值。
缺失值处理: 按照缺失数据占整个数据的比例来更改算法。
多变量决策树: 决策节点的判断标准由单个属性变成多个属性。
决策树算法的训练流程:
Train DecisionTree(D) //D为本节点的训练样本集
if(样本集无法再划分 | 达到最大树深度 | D的样本数小于指定阈值)
leafNode = CalcLeafValue(D); //无法再分裂,设置为叶子节点,计算其值
return leafNode; //返回创建的叶子节点
else
(split, D1, D2) = FindBestSplit(D) //寻找最佳分裂split,将训练集D分为D1额D2
node = CreateTreeNode(); //创建当前节点
node -> split=split; //设置节点的分裂规则
FinSurrogatesplit(D); //寻找替代分裂,加入到节点的分裂规则列表
node -> leftChild = TrainDecisionTree(D1); //递归训练左子树
node -> rightChild = TrainDecisionTree(D2); //递归训练右子树
return node; //返回训练的树节点
end if
决策树分类算法的优点:
- 分类逻辑清晰易懂
- 采用树形结构进行分类,便于可视化,在需要演示和讲解的场景下可直观展现决策树整个分类过程
决策树分类算法的缺点:
- 最大的问题就是容易过拟合,减少和解决这个问题是该算法和改进的类似算法的热门研究方向。目前认为最有效解决这个问题的方法是剪枝操作。
- 特征维度之间若存在关联,则可能对预测结果有影响。ID3算法、C4.5算法和CART算法都是用选择了统计学指标作为特征维度,这些指标都有一个默认的假设,认为特征维度之间都是彼此独立的。
八、案例
目前sklearn的决策树模型只实现了ID3和CART。
默认情况下,sklearn中决策树算法默认使用gini作为标准(criterion),也就是使用CART决策树,同时也是二叉树。而指定标准为entropy时,则是使用ID3决策树,实际与C4.5差别不大。
0、决策树可视化操作
决策树可视化后更加直观易懂,但是需要使用训练好的决策树来进行可视化。可以使用 Graphviz 可视化工具帮我们把决策树呈现出来。
下载地址:http://www.graphviz.org/download/
需要先在电脑上安装graphviz添加到环境变量 PATH 中,再在python环境中pip install graphviz
安装插件
from sklearn.tree import export_graphviz
import graphviz
# clf为决策树参数名字
dot_data = export_graphviz(clf, out_file=None)
graph = graphviz.Source(dot_data)
# 同级目录生成PDF文件
graph.render('iris_clf')
# 直接打印输出决策树
graph
1、决策树之分类树 - 鸢尾花数据集分类
目标:使用自带的iris数据集,构造一棵分类树
导包:
# 导入决策树分类算法
from sklearn.tree import DecisionTreeClassifier
# 导入分离训练集和测试集的方法
from sklearn.model_selection import train_test_split
# 导入评估分类结果准确率的方法
from sklearn.metrics import accuracy_score
# 导入数据集
from sklearn.datasets import load_iris
数据探索与准备:
# 读取数据集
iris = load_iris()
# 获取特征和标签
features = iris.data
labels = iris.target
# 划分总数据集为测试集和训练集,训练集占比33%
train_features, test_features, train_lables, test_labels = train_test_split(features, labels, test_size=0.33, random_state=1)
构造决策树 - CRAT分类树:
# 创建决策树对象
clf = DecisionTreeClassifier(criterion='gini')
# 训练决策树
clf = clf.fit(train_features, train_labels)
应用决策树 - 预测结果:
# 预测测试集的标签
test_labels_predict = clf.predict(test_features)
# 将预测后的结果与实际结果进行对比
score = accuracy_score(test_labels, test_labels_predict)
# 打印输出
print("CART分类树的准确率 %.4lf" % score)
# 结果
CART分类树的准确率为: 0.9600
2、决策树之回归树 - 波士顿房价数据集回归
目标:使用自带的Boston数据集,构造一棵回归树
导包:
# 导入回归树
from sklearn.tree import DecisionTreeRegressor
# 导入训练集和测试集划分方法
from sklearn.mode_selection import train_test_split
# 导入评估回归结果准确率的方法
# 绝对值偏差,二乘偏差
from sklearn.metrics import mean_absolute_error, mean_squared_error
from sklearn.metrics import r2_score
# 导入数据集
from sklearn.datasets import load_boston
数据探索与准备:
# 导入数据集
boston = load_boston()
# 选取特征属性和标签
features = boston.data
prices = boston.target
# 划分数据集合测试集
train_features, test_features, train_prices, test_prices = train_test_split(features, prices, test_size=0.33)
构造决策树 - CART回归树:
# 创建决策树对象
dtr= DecisionTreeRegressor()
# 训练决策树
dtr= dtr.fit(train_features, train_prices)
应用决策树 - 预测结果
# 预测结果
test_prices_predict = dtr.predict(test_features)
# 结果评分
print("回归树二乘偏差均值:", mean_squared_error(test_prices, test_prices_predict))
print("回归树绝对值偏差均值:", mean_absolute_error(test_prices, test_prices_predict)
# 结果
回归树二乘偏差均值: 32.30946107784431
回归树绝对值偏差均值: 3.525748502994012
3、泰坦尼克号生存者预测
3.1、项目分析
(1)样本的分析
Titanic数据集有两个:
train.csv - 包含特征信息和是否存活的标签
test.csv - 只包含特征信息
结果为判断测试集test.csv中的乘客是否存活,使用分类树。
样本的信息为:
train.csv
test.csv
(2)项目流程
准备阶段: 我们首先需要对训练集、测试集的数据进行探索,分析数据质量,并对数据进行清洗,然后通过特征选择对数据进行降维,方便后续分类运算;
分类阶段: 首先通过训练集的特征矩阵、分类结果得到决策树分类器,然后将分类器应用于测试集。然后我们对决策树分类器的准确性进行分析,并对决策树模型进行可视化。
(3)决策树的函数使用
使用ID3分类树,它的函数及参数为:
DecisionTreeClassifier(class_weight=None, criterion='entropy', max_depth=None,
max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort=False, random_state=None,
splitter='best')
各参数如下,一般除了criterion进行设置外,不需要进行其他手动设定,默认就行,即不会限制决策树的最大深度,不限制叶子节点数,所有分类权重都相等等等。
构造完决策树(分类器)后,使用fit方法对分类器进行拟合训练,然后使用predict方法对测试集进行预测,得到预测的结果后,可以使用score方法对分类器的准确率进行评估。下面是对应的函数方法:
3.2、项目实践
按照项目的流程来完成整个项目
(1)数据获取
# 导入文件读取和分析包-pandas
import pandas as pd
# 读取训练集和测试集文件
test = pd.read_csv('./test.csv')
train = pd.read_csv('./train.csv')
(2)数据探索
数据探索对分类器没实质性的作用,但有助于对数据特性的了解,帮我我们做数据清洗和特征选择。
一些用于数据探索的函数:
函数 | 解释 |
info() | 数据表的基本情况:行数、列数、每列的数据类型、数据完整度等 |
describe() | 数据表的统计情况:总数、平均值、标准差、最小值、最大值、上四分位值、下四分位值等等 |
describe(include=[“O”]) | 查看字符串(非数字)类型数据的整体情况 |
head(n=5) | 查看最前n行数据 |
tail(n=5) | 查看最后n行数据 |
# 展示列属性名
display(test.columns, train.columns)
# 结果
Index(['PassengerId', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch',
'Ticket', 'Fare', 'Cabin', 'Embarked'],
dtype='object')
Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp',
'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'],
dtype='object')
# 查看数据整体信息
display(train.info(), test.info())
# 结果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Name 418 non-null object
Sex 418 non-null object
Age 332 non-null float64
SibSp 418 non-null int64
Parch 418 non-null int64
Ticket 418 non-null object
Fare 417 non-null float64
Cabin 91 non-null object
Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
None
None
# 查看数据的统计情况
train.describe()
# 查看字符串类型数据
train.describe(include=['O'])
# 查看训练集前几个样本
train.head(n=5)
# 查看训练集后几个样本
train.tail(n=5)
(3)数据清洗
通过数据探索,发现Age和Fare字段有所缺失。
补齐空值:
Age是年龄字段,是数值型,可以通过平均值补齐。
Fare是票价字段,是数值型,可以通过平均值补齐。
# 补齐年龄
train['Age'].fillna(train['Age'].mean(), inplace=True)
test['Age'].fillna(test['Age'].mean(), inplace=True)
# 补齐标价
test['Fare'].fillna(test['Fare'].mean(), inplace=True)
Cabin表示船舱,缺失值过多,达到77%和78%,无法补齐。
Embarked为登陆港口,含有少量缺失值,使用最多值来填充
# S港口最多,占到了73%,所以用S来填充
train['Embarked'].value_counts()
# 结果
S 644
C 168
Q 77
Name: Embarked, dtype: int64
train['Embarked'].fillna('S', inplace=True)
(4)特征选择
特征选择是分类器的关键,选择不同则得到的分类器也不同。但并不是所有属性都对分类结果有作用,所以需要选择对结果关联性大的属性特征。
通过数据探索可以发现:
PassengerId 为乘客编号,对分类没有作用,可以放弃;
Name 为乘客姓名,对分类没有作用,可以放弃;
Cabin 字段缺失值太多,可以放弃;Ticket 字段为船票号码,杂乱无章且无规律,可以放弃。
其余的字段包括:Pclass、Sex、Age、SibSp、Parch 和 Fare,这些属性分别表示了乘客的船票等级、性别、年龄、亲戚数量以及船票价格,可能会和乘客的生存预测分类有关系。具体是什么关系,我们可以交给分类器来处理。
因此我们先将 Pclass、Sex、Age 等这些其余的字段作特征,放到特征向量 features 里。
# 特征选择
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
train_features = train[features]
train_labels = train['Survived']
test_features = test[features]
某些非数字型特征,如Sex,有male和female两种字符串;Embarked有S、C 和Q 三种字符串,我们使用Sklearn特征选择中的 DictVectorizer 类,用它将可以处理符号化的对象,将符号转成数字 0/1 进行表示。将Sex转变成Sex=male和Sex=female,使用0和1进行表示。
具体方法如下:
# 将符号转成 0 / 1 进行表示
from sklearn.feature_extraction import DictVectorizer
dvec = DictVectorizer(sparse=False)
train_features = dvec.fit_transform(train_features.to_dict(orient='record'))
fit_transform 函数可以将特征向量转化为特征值矩阵,转化后的
dvec.feature_names_
# 结果
['Age',
'Embarked=C',
'Embarked=Q',
'Embarked=S',
'Fare',
'Parch',
'Pclass',
'Sex=female',
'Sex=male',
'SibSp']
原本是一列的 Embarked,变成了“Embarked=C”“Embarked=Q”“Embarked=S”三列。Sex 列变成了“Sex=female”“Sex=male”两列。
train_features 特征矩阵就成了包括 10 个特征值(列),以及 891 个样本(行),即 891 行,10 列的特征矩阵。
(5)决策树模型
现在我们使用 ID3 算法,即在创建 DecisionTreeClassifier 时,设置 criterion=‘entropy’,然后使用 fit 进行训练,将特征值矩阵和分类标识结果作为参数传入,得到决策树分类器。
# 导入分类树
from sklearn.tree import DecisionTreeClassifier
# 构造ID3决策树
clf = DecisionTreeClassifier(criterion='entropy')
# 训练决策树
clf.fit(train_features, train_labels)
# 结果
DecisionTreeClassifier(ccp_alpha=0.0, class_weight=None, criterion='entropy',
max_depth=None, max_features=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, min_impurity_split=None,
min_samples_leaf=1, min_samples_split=2,
min_weight_fraction_leaf=0.0, presort='deprecated',
random_state=None, splitter='best')
(6)模型测试&评估
test_features = dvec.transform(test_features.to_dict(orient='record'))
# 决策树预测
pred_labels = clf.predict(test_features)
由于测试集文件test.csv中缺少标签,即缺少真实结果,所以无法比较结果的准确率。只能使用训练集中数据进行模型评估,可以使用决策树自带的 score 函数计算下得到的结果:
# 得到决策树准确率
acc_decision_tree = round(clf.score(train_features, train_labels), 6)
print(u'score 的准确率为:%.4lf'% acc_decision_tree)
# 结果
score准确率为 0.9820
因为我们没有测试集的实际结果,因此无法用测试集的预测结果与实际结果做对比。如果我们使用 score 函数对训练集的准确率进行统计,正确率会接近于 100%(如上结果为 98.2%),无法对分类器的在实际环境下做准确率的评估。
# K折交叉验证
import numpy as np
from sklearn.model_selection import cross_val_score
print(u'cross_val_score准确率为 %.4lf'% np.mean(cross_val_score(clf, train_features, train_labels, cv=10)))
这里可以使用 K 折交叉验证的方式,交叉验证是一种常用的验证分类准确率的方法,原理是拿出大部分样本进行训练,少量的用于分类器的验证。
K 折交叉验证,就是做 K 次交叉验证,每次选取 K 分之一的数据作为验证,其余作为训练。轮流 K 次,取平均值。
K 折交叉验证的原理是这样的:
1.将数据集平均分割成 K 个等份;
2.使用 1 份数据作为测试数据,其余作为训练数据;
3.计算测试准确率;
4.使用不同的测试集,重复 2、3 步骤。
在 sklearn 的 model_selection 模型选择中提供了 cross_val_score 函数。cross_val_score 函数中的参数 cv 代表对原始数据划分成多少份,也就是我们的 K 值,一般建议 K 值取 10,因此我们可以设置 CV=10,我们可以对比下 score 和 cross_val_score 两种函数的正确率的评估结果:
cross_val_score(clf, train_features, train_labels, cv=10)
# 结果
array([0.67777778, 0.7752809 , 0.69662921, 0.80898876, 0.84269663,
0.71910112, 0.80898876, 0.71910112, 0.83146067, 0.80898876])
(7)可视化决策树
from sklearn.tree import export_graphviz
import graphviz
dot_data = export_graphviz(clf, out_file=None)
graph = graphviz.Source(dot_data)
graph