一、决策树模型简介
决策树模型是机器学习的各种算法模型中比较好理解的一种模型, 它的基本原理是通过对一系列问题进行if/else的推导,最终实现相关决策。
决策树(decision tree)是一个树结构(可以是二叉树或非二叉树)。其每个非叶节点表示一个特征属性上的测试,每个分支代表这个特征属性在某个值域上的输出,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
构造决策树的关键步骤是分裂属性。所谓分裂属性就是在某个节点处按照某一特征属性的不同划分构造不同的分支,其目标是让各个分裂子集尽可能地“纯”。尽可能“纯”就是尽量让一个分裂子集中待分类项属于同一类别。
分裂属性分为三种不同的情况:
1、属性是离散值且不要求生成二叉决策树。此时用属性的每一个划分作为一个分支。
2、属性是离散值且要求生成二叉决策树。此时使用属性划分的一个子集进行测试,按照“属于此子集”和“不属于此子集”分成两个分支。
3、属性是连续值。此时确定一个值作为分裂点split_point,按照>split_point和<=split_point生成两个分支。
构造决策树的关键性内容是进行属性选择度量,属性选择度量是一种选择分裂准则,是将给定的类标记的训练集合的数据划分D“最好”地分成个体类的启发式方法,它决定了拓扑结构及分裂点split_point的选择。
属性选择度量算法有很多,一般使用自顶向下递归分治法,并采用不回溯的贪心策略。决策树的构建算法主要有ID3、C4.5、CART三种,其中ID3和C4.5是分类树(结果是类别),CART是分类回归树(结果是确定的数值)。其中ID3是决策树最基本的构建算法,而C4.5和CART是在ID3的基础上进行优化的算法。
二、决策树的优点
(1)简单直观,生成的决策树很直观。
(2)基本不需要预处理,不需要提前归一化,处理缺失值。
(3)使用决策树预测的代价是O(log2m)。 m为样本数。
(4)既可以处理离散值也可以处理连续值。很多算法只是专注于离散值或者连续值。
(5)可以处理多维度输出的分类问题。
(6)相比于神经网络之类的黑盒分类模型,决策树在逻辑上可以得到很好的解释
(7)可以交叉验证的剪枝来选择模型,从而提高泛化能力。
(8) 对于异常点的容错能力好,健壮性高。
三、决策树的局限性
(1)决策树算法非常容易过拟合,导致泛化能力不强。可以通过设置节点最少样本数量和限制决策树深度来改进。
(2)决策树会因为样本发生一点点的改动,就会导致树结构的剧烈改变。这个可以通过集成学习之类的方法解决。
(3)寻找最优的决策树是一个NP难的问题,我们一般是通过启发式方法,容易陷入局部最优。可以通过集成学习之类的方法来改善。
(4)有些比较复杂的关系,决策树很难学习,比如异或。这个就没有办法了,一般这种关系可以换神经网络分类方法来解决。
(5)如果某些特征的样本比例过大,生成决策树容易偏向于这些特征。这个可以通过调节样本权重来改善。
四、构建决策树
决策树构建的3个步骤:
(1)特征选择
(2)决策树生成
(3)决策树剪枝
构建步骤如下:
1.构建根节点,所有的数据起初都在根节点上。
2.选取最优特征,通过这一特征将训练数据分割成子集。分割后,如果这一子集已经被较好的分类,则该子集形成叶节点;反之,则对该子集进一步分类,该子集只作为中间节点存在。
3.递归至所有样本都被较好的分类。(分类优劣标准见后)
五、决策树模型的建树依据
决策树模型的建树依据主要用到的是基尼系数的概念。基尼系数 (gini)用于计算一个系统中的失序现象,即系统的混乱程度。基尼系数越高,系统的混乱程度就越高,建立决策树模型的目的就是降低系统 的混乱程度,从而得到合适的数据分类效果。基尼系数的计算公式如 下。

其中
p
i
为类别
i
在样本
T
中出现的频率,即类别为
i
的样本占总样本个 数的比率;Σ
为求和符号,即对所有的 进行求和。 例如,一个全部都是离职员工的样本中只有一个类别——
离职员 工,其出现的频率是100%
,所以该系统的基尼系数为
1-1
2
=
0
,表示该 系统没有混乱,或者说该系统的“
纯度
”
很高。而如果样本中一半是离职 员工,另一半是未离职员工,那么类别个数为2
,每个类别出现的频率 都为50%
,所以其基尼系数为
1-
(0.5 2
+
0.5
2
)=
0.5
,即其混乱程度很 高。 当引入某个用于分类的变量(如“
满意度<
5”
)时,分类后的基尼 系数公式如下

其中
S
1
、
S
2
为划分后的两类各自的样本量,
gini
(
T
1
)、
gini
(
T
2
)
为两类各自的基尼系数。 例如,一个初始样本中有1000
个员工,其中已知有
400
人离职,
600
人不离职。划分前该系统的基尼系数为
1-
(0.4 2
+
0.6
2
)=
0.48
,下面采 用两种方式决定根节点:一是根据“
满意度<
5”
进行分类;二是根据
“
收
入<
10000
元
”
进行分类。 划分方式1
:以
“
满意<
5”
为根节点进行划分,如下图所示,划分 后的基尼系数为0.3
,计算过程如下。

T
1
的基尼系数:
gini
(
T
1
)=
1-
(1 2
+
0
2
)=
0
T
2
的基尼系数:
gini
(
T
2
)=
1-
(0.25 2
+
0.75
2
)=
0.375
划分方式
2
:以
“
收入<
10000
元
”
为根节点进行划分,如下图所示, 划分后的基尼系数为0.45
,计算过程如下。
可以看到,划分前的基尼系数为
0.48
,以
“
满意度<
5”
为根节点进行 划分后的基尼系数为0.3
,而以
“
收入<
10000
元
”
为根节点进行划分后的 基尼系数为0.45
。基尼系数越低表示系统的混乱程度越低,区分度越 高,越适合用于分类预测,因此这里选择“
满意度<
5”
作为根节点。 上面演示了如何利用基尼系数来选择根节点,根节点下面的节点也 是用类似方法来选择。例如,对于变量“
收入
”
来说,是选择
“
收入< 10000元
”
还是选择
“
收入<
100000
元
”
作为划分依据,同样通过计算这两 种情况下划分后的基尼系数来进行判断。若还有其他变量,如“
工 龄”“
月工时
”
等,也是通过类似方法计算划分后的基尼系数,再根据基 尼系数判断如何划分节点,从而搭建出一个较为完善的决策树模型。采 用基尼系数进行运算的决策树也称为CART
决策树。
六、信息熵
除了基尼系数,还有另一种衡量系统混乱程度的经典手段
——
信息 熵。下面简单介绍信息熵的计算与应用,供感兴趣的读者参考。 在搭建决策树模型时,信息熵的作用和基尼系数是基本一致的,都 可以帮助我们合理地划分节点。信息熵H
(
X
)的计算公式如下。
其中
X
表示随机变量,随机变量的取值为
X
1
,
X
2
,
X
3
…
,在
n
分类 问题中便有n
个取值,例如,在员工离职预测模型中,
X
的取值就是
“
离 职”
与
“
不离职
”
两种;
p
i
表示随机变量
X
取值为
X
i
的发生频率,且有
Σp
i
= 1。需要注意的是,公式中的对数函数是以
2
为底的。
举例来说,一个全部都是离职员工的样本中只有一个类别
——
离职 员工,其出现的频率是100%
,所以该系统的信息熵为
-1×log
2
1
=
0
,表 示该系统没有混乱。而如果样本中一半是离职员工,另一半是未离职员 工,那么类别个数为2
,每个类别出现的频率都为
50%
,所以该系统的
信息熵为
-
(0.5×log2
0.5
+
0.5×log
2
0.5
)=
1
,表示该系统混乱程度很 高。
当引入某个用于进行分类的变量
A
(如
“
满意度<
5”
),则根据变量 A划分后的信息熵又称为条件熵,其计算公式如下。
其中
S
1
、
S
2
为划分后的两类各自的样本量,
H
(
X
1
)、
H
(
X
2
)为 两类各自的信息熵。
为了衡量不同划分方式降低信息熵的效果,还需要计算分类后信息 熵的减少值(原系统的信息熵与分类后系统的信息熵之差),该减少值 称为熵增益或信息增益,其值越大,说明分类后的系统混乱程度越低, 即分类越准确。信息增益的计算公式如下。
继续用前面讲解基尼系数时的例子来讲解信息熵的应用。初始样本 有1000
个员工,其中已知有
400
人离职,
600
人不离职。划分前该系统的 信息熵为-
(0.4×log2
0.4
+
0.6×log
2
0.6
)=
0.97
,混乱程度较高。下面采 用两种方式决定根节点:一是根据“
满意度<
5”
进行分类;二是根据
“
收 入<10000
元
”
进行分类。 划分方式1
:以
“
满意度<
5”
为根节点进行划分,如下图所示,划分
后的信息熵为
0.65
,信息增益为
0.32
,计算过程如下。


划分方式
2
:以
“
收入<
10000
元
”
为根节点进行划分,如下图所示, 划分后的信息熵为0.924
,信息增益为
0.046
,计算过程如下。

根据方式
1
划分后的信息增益为
0.32
,大于根据方式
2
划分后的信息 增益0.046
,因此选择根据方式
1
进行决策树的划分,这样能更好地降低 系统的混乱程度。这个结论和之前用基尼系数计算得到的结论是一样 的。 基尼系数涉及平方运算,而信息熵涉及相对复杂的对数函数运算, 因此,目前决策树模型默认使用基尼系数作为建树依据,运算速度会较 快。 商业实战中的数据量通常很大,不同情况下的基尼系数或信息熵的 计算是人力难以完成的,因此需要利用机器不停地训练来找到最佳的分 裂节点。在Python
中,可以用
Scikit-Learn
库(
Anaconda
自带)来快速建 立一个决策树模型。下面就来讲解决策树模型的简单代码实现。
七、决策树模型的代码实现
决策树模型既能做分类分析(即预测分类变量值),又能做回归分 析(即预测连续变量值),对应的模型分别为分类决策树模型 (DecisionTreeClassifier
)和回归决策树模型 (DecisionTreeRegressor
)。
1
.分类决策树模型(
DecisionTreeClassifier
)
分类决策树模型的演示代码如下。

第
1
行代码引入分类决策树模型
DecisionTreeClassifier
。
第
2
行代码中的
X
是特征变量,共有
5
个训练数据,每个数据有
2
个
特征,如数据
[1
,
2]
,它的第
1
个特征的数值为
1
,第
2
个特征的数值为 2。
第
3
行代码中的
y
是目标变量,共有
2
个类别
——0
和
1
。
第
4
行代码引入模型并设置随机状态参数
random_state
为
0
,这里的
0
没有特殊含义,可换成其他数字。它是一个种子参数,可使每次运行结 果一致,将在“
补充知识点
”
讲解。
第
5
行代码用
fit()
函数训练模型。
第
6
行代码用
predict()
函数进行预测。
运行后得到的预测结果如下。

可以看到,数据
[5
,
5]
被预测为类别
0
。
如果要同时预测多个数据,可以使用如下代码。

预测结果如下。
1 [0 0 1]
为便于理解,这里利用
5.2.3
小节会讲到的决策树可视化知识将决策 树可视化,效果如下图所示,图中各个量的含义如下:

·value
表示各分类的样本数,例如,根节点中的
[2
,
3]
表示分类为
0 的样本数为2
,分类为
1
的样本数为
3
; ·class表示该区块被划分为的类别,它是由
value
中样本数较多的类
别决定的,例如,根节点中分类为
1
的样本数(3)大于分类为
0
的样本
数(2),所以该节点的分类为
1
,依此类推。 根节点以X[1]
是否小于等于
7
作为节点划分依据,如果满足该条件 (即True
),则划分到左边的子节点,否则(即
False
)划分到右边的子
节点。以数据
[5
,
5]
为例,在根节点时,它满足
X[1]
(即第
2
个特征数 值)小于等于7
的条件,所以被划分到左边的子节点。在该子节点又进 行一次判断,判断X[0]
是否小于等于
2
,因为
X[0]
为
5
,不满足该条件, 所以划分到该子节点的右边的子节点,而该子节点的类别class
为
0
,所
以
[5
,
5]
在该决策树模型中被预测为类别
0
。
八、补充知识点:random_state参数的作用
决策树模型会优先选择使整个系统的基尼系数下降最大的划分方式 来进行节点划分,但是有可能根据不同的划分方式获得的基尼系数下降 是一样的,如果不设置random_state
参数,就会导致程序每次运行时会 获得不同的决策树。下图所示为不设置random_state
参数时多次运行后 获得的两棵不同的决策树。
可以看到,两棵决策树的节点划分方式不同,但它们产生的基尼系 数下降却是一样的,均为0.48-
(0.6×0.444+
0.4×0
)=
0.2136
。这两棵 决策树的节点划分方式都是合理的,但对相同的数据进行预测却会得到 不同的结果。例如,数据[7
,
7]
在左边的决策树中会被预测为类别
0
,而 在右边的决策树中会被预测为类别1
。 这种现象在数据量较少时容易出现,当数据量较大时出现的概率则 较小。为了避免这种现象,保证程序每次运行时都能采用相同的节点划 分方式,以获得相同的决策树,就需要设置random_state
参数。该参数 其实是一个随机数生成器的种子,可以设置为0
、
1
、
123
等任意数字。
九、
回归决策树模型(
DecisionTreeRegressor
)
决策树除了能进行分类分析,还能进行回归分析,即预测连续变 量,此时的决策树称为回归决策树。回归决策树模型的简单演示代码如 下。
1 from sklearn.tree import DecisionTreeRegressor
2 X = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
3 y = [1, 2, 3, 4, 5]
4 model = DecisionTreeRegressor(max_depth=2, random_state=0)
5 model.fit(X, y)
6 print(model.predict([[9, 9]]))
第
2
行代码中的
X
是特征变量,共有
2
个特征;第
3
行代码中的
y
是目 标变量,为一个连续变量;第4
行代码引入模型,并设置决策树最大深 度参数max_depth
为
2
,随机状态参数
random_state
为
0
;第
5
行代码用
fit() 函数训练模型;第6
行代码用
predict()
函数对数据
[9
,
9]
进行预测,预测
结果如下。
1 [4.5]
可以看到,数据
[9
,
9]
的预测拟合值为
4.5
。 回归决策树模型的概念和分类决策树模型基本一致,最大的不同就 是其划分标准不是基尼系数或信息熵,而是均方误差MSE
,其计算公式
如下
其中
n
为样本数量,
y
(i)
为实际值, 为拟合值。 为便于理解,这里利用5.2.3小节会讲到的决策树可视化知识将决策 树可视化,如下图所示。

图中的
X[0]
表示数据的第
1
个特征,
X[1]
表示数据的第
2
个特征; mse表示该节点的均方误差;
samples
表示该节点的样本数;
value
表示该 节点的拟合值,在回归决策树中,节点的拟合值是节点中所有数据的均 值,最终的叶子节点的拟合值就是最终的回归模型预测值。 举例来说,根节点中一共有5
个数据,其拟合值 =(1+2+3+4+5)
/5=3
,其均方误差
MSE
的计算过程如下所示,结果为 2,和程序获得的结果是一致的

回归决策树的目的是使最终系统的均方误差最小,其节点划分也是 基于这个理念。上图中的根节点是根据“X[1]≤5.0”
进行划分的,这使得 系统的均方误差下降值最大。系统的均方误差下降值为2-
(0.4×0.25+0.6×0.667)
=1.5
。 如果不限制决策树的深度,那么决策树将一直往下延伸,直到所有 叶子节点的均方误差MSE
都等于
0
为止。这里因为设置了树的最大深度 参数max_depth
为
2
,所以决策树在根节点往下共有
2
层,如果不设置这 一参数,那么右下角的节点还将继续分裂,直到所有叶子节点的均方误差
MSE
都为
0
为止。这里设置该参数一是为了方便演示拟合效果(拟合 结果是4.5
而不是一个整数,显示的是回归结果,而不是分类结果), 二是为了防止模型出现过拟合现象(参见3.2.2
小节的
“
补充知识点
”
), 在实战中通常也会设置该参数来防止过拟合。 至于数据[9
,
9]
的拟合结果为什么是
4.5
,大家看上面的图应该就一 目了然了。 在商业实战中,分类决策树模型用得相对较多,不过分类决策树模 型和回归决策树模型都很重要,第8
~
10
章将讲到的随机森林模型、 AdaBoost模型、
GBDT
模型、
XGBoost
与
LightGBM
模型都是基于决策树
模型进行搭建的。
十、案例实战:员工离职预测模型
1.
模型搭建
通过已有的员工信息和离职表现来搭建相应的员工离职预测模型, 可以预测之后的员工是否会离职。
1
.数据读取与预处理
首先读取员工信息和离职表现(即是否离职的记录),代码如下。
1 import pandas as pd
2 df = pd.read_excel('
员工离职预测模型
.xlsx')
通过打印
df.head()
查看表格的前
5
行,结果如右图所示,
“
离职
”
列中
的数字
1
代表离职,数字
0
代表未离职。
该表格共有
15000
组历史数据,前
3571
组为离职员工数据,后
11429 组为非离职员工数据。我们的目的就是根据这些历史数据搭建决策树模 型来预测之后员工的离职可能性。 原始数据中的“
工资
”
数据被分为
“
高
”“
中
”“
低
”3
个等级,而
Python
数 学建模中无法识别这种文本内容,所以“
工资
”
列的内容需要进行数值化 处理。这里用pandas
库中的
replace()
函数将文本
“
高
”“
中
”“
低
”
分别替换为
数字
2
、
1
、
0
。
1 df = df.replace({'
工资
': {'
低
': 0, '
中
': 1, '
高
': 2}})
2 df.head()
除了利用
replace()
函数进行文本数据处理外,在
11.1
节还将介绍
Get_dummies
哑变量处理及
Label Encoding
编号处理两种文本数据处理手 段,感兴趣的读者可以提前阅读。 将表格中的“
离职
”
列作为目标变量,剩下的字段作为特征变量,通 过一个员工的特征来判断其是否会离职。为便于演示,这里只选取了6 个特征变量,在商业实战中选取的特征变量会更多。接下来进行决策树 模型的搭建,这是大部分机器学习模型搭建的常规步骤。
2
.提取特征变量和目标变量
首先将特征变量和目标变量分别提取出来,代码如下。
1 X = df.drop(columns='
离职
')
2 y = df['
离职
']
第
1
行代码用
drop()
函数删除
“
离职
”
列,将剩下的数据作为特征变量
赋给变量
X
;第
2
行代码用
DataFrame
提取列的方式提取
“
离职
”
列作为目 标变量,并赋给变量y
。
3
.划分训练集和测试集
提取完特征变量和目标变量后,还需要将原来的
15000
组数据划分 为训练集和测试集。顾名思义,训练集用于进行模型训练,而测试集用 于检验模型训练的结果。代码如下。
1 from sklearn.model_selection import train_test_split
2 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=123)
第
1
行代码从
Scikit-Learn
库中引入
train_test_split()
函数。
第
2
行代码用
train_test_split()
函数划分训练集和测试集,其中
X_train
、
y_train
为训练集数据,
X_test
、
y_test
为测试集数据。
train_test_split()
函数的前两个参数
X
和
y
就是之前提取的特征变量和 目标变量;test_size
是测试集数据所占的比例,这里设置为
0.2
,表示选 取20%
的数据用于测试。如果数据量较多,也可以将其设置为
0.1
,即分 配更少比例的数据用于测试,分配更多比例的数据用于训练。 因train_test_split()
函数每次划分数据都是随机的,所以如果想让 每次划分数据的结果保持一致,可以设random_state
参数,这里设置 的数字123
没有特殊含义,它只是一个随机数种子,也可以设置成其他 数字。 划分后的数据如下图所示。
4
.模型训练及搭建
划分好训练集和测试集之后,就可以从
Scikit-Learn
库中引入决策树
模型进行模型训练了,代码如下。
1 from sklearn.tree import DecisionTreeClassifier
2 model = DecisionTreeClassifier(max_depth=3, random_state=123)
3 model.fit(X_train, y_train)
第
1
行代码从
Scikit-Learn
库中引入分类决策树模型
DecisionTreeClassifier
。
第
2
行代码将决策树模型赋给变量
model
,同时设置树的最大深度参 数max_depth
为
3
(该参数在
5.3
节的参数调优部分会讲解),并设置随机 状态参数random_state
为数字
123
,使每次程序运行的结果保持一致。
第
3
行代码用
fit()
函数进行模型训练,传入的参数就是前面划分出的
训练集数据。 至此,一个决策树模型便搭建完成了,相关代码汇总如下。1 # 1
.数据读取与预处理
2 import pandas as pd
3 df = pd.read_excel('
员工离职预测模型
.xlsx')
4 df = df.replace({'
工资
': {'
低
': 0, '
中
': 1, '
高
': 2}})
5
6 # 2
.提取特征变量和目标变量
7 X = df.drop(columns='
离职
')
8 y = df['
离职
']
9
10 # 3
.划分训练集和测试集
11 from sklearn.model_selection import train_test_split
12 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
random_state=123)
13
14 # 4
.模型训练及搭建
15 from sklearn.tree import DecisionTreeClassifier
16 model = DecisionTreeClassifier(max_depth=3, random_state=123)
17 model.fit(X_train, y_train)
模型搭建完成后,就可以利用前面划分出的测试集来进行预测并评 估模型的预测效果。