决策树(decision tree)是一种基本的分类和回归方法,由于其采用的是一种树形的结构,因此,具有很强的解释性和计算速度,也正是因为这些特点,使得决策树在很多行业都得到了应用,比如风控行业等。决策树的建模过程一般分为三个步骤:特征选择、决策树的生成和决策树的剪枝,根据这三个步骤所采用的规则,衍生出了很多不同的模型,比较经典的有Quinlan在1986年提出的ID3算法和1993年提出的C4.5算法,以及Breiman等人在1984年提出的CART算法,本文将以分类决策树为例,对这几个算法分别进行介绍,并用python进行实现。
决策树是由结点和有向边组成的树形结构,其中,结点包含两种类型:内部结点和叶结点。内部结点表示一个特征或者属性,叶结点则表示一个类。如下图所示,其中每个圆圈表示内部结点,每个正方形表示叶结点。
对于给定的训练数据集 D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } D=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\} D={(x1,y1),(x2,y2),⋯,(xN,yN)},其中, x i = ( x i ( 1 ) , x i ( 2 ) , ⋯   , x i ( n ) ) T x_{i}=\left(x_{i}^{(1)}, x_{i}^{(2)}, \cdots, x_{i}^{(n)}\right)^{\mathrm{T}} xi=(xi(1),xi(2),⋯,xi(n))T表示输入的特征向量, n n n为特征的个数, y i ∈ { 1 , 2 , ⋯   , K } y_{i} \in\{1,2, \cdots, K\} yi∈{1,2,⋯,K}为类别标记, K K K表示类别的个数, N N N表示训练集的大小。决策树的思想大致如下:
这样一来,当对一个实例 x x x进行预测时,会根据决策树的分支情况,将实例 x x x划分到其归属的叶结点,并将该叶结点对应的类别作为实例 x x x的预测类别,从而达到分类的效果。下面,我们将根据决策树的三个步骤,对各个算法的思想进行介绍和对比。
适用场景:特征和目标变量都是离散型
特征选择是指决策树在每一次分支时,从所有的特征中选择能够对当前数据集具有最优分类能力的特征,这样可以提高模型的学习效率。ID3决策树的特征选择采用的是信息增益的方法。在介绍信息增益的概念之前,需要先介绍一下熵和条件熵的概念。
在信息论中,熵表示随机变量不确定性的度量,设 X X X是一个取有限个值的离散随机变量,则其熵的计算公式如下:
H ( X ) = − ∑ i = 1 n p i log p i H(X)=-\sum_{i=1}^{n} p_{i} \log p_{i} H(X)=−i=1∑npilogpi其中, p i = P ( X = x i ) , i = 1 , 2 , ⋯   , n p_{i}=P\left(X=x_{i}\right), \quad i=1,2, \cdots, n pi=P(X=xi),i=1,2,⋯,n表示 X X X取某个类别时的概率,当 p i = 0 p_{i}=0 pi=0时,定义 0 log 0 = 0 0 \log 0=0 0log0=0,由于熵只依赖于 X X X的分布,因此,也可以将 X X X的熵记作 H ( p ) H(p) H(p),即
H ( p ) = − ∑ i = 1 n p i log p i H(p)=-\sum_{i=1}^{n} p_{i} \log p_{i} H(p)=−i=1∑npilogpi从熵的计算公式可以发现,当随机变量的不确定性越大时,熵的值会越大,其取值范围为:
0 ⩽ H ( p ) ⩽ log n 0 \leqslant H(p) \leqslant \log n 0⩽H(p)⩽logn
条件熵则表示在已知随机变量 X X X的条件下随机变量 Y Y Y的不确定性,其计算公式如下:
H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y | X)=\sum_{i=1}^{n} p_{i} H\left(Y | X=x_{i}\right) H(Y∣X)=i=1∑npiH(Y∣X=xi)其中, p i = P ( X = x i ) , i = 1 , 2 , ⋯   , n p_{i}=P\left(X=x_{i}\right), \quad i=1,2, \cdots, n pi=P(X=xi),i=1,2,⋯,n。
信息增益则表示在得知特征 X X X的信息而使得类 Y Y Y的信息的不确定性减少的程度。其计算公式如下:
g ( Y , X ) = H ( Y ) − H ( Y ∣ X ) g(Y, X)=H(Y)-H(Y | X) g(Y,X)=H(Y)−H(Y∣X)当信息增益越大时,表示给定 X X X后,对 Y Y Y进行分类后的不确定性越低,也就是说 X X X对 Y Y Y的分类能力越强。因此,ID3在每一次分支时,采用信息增益作为每个结点特征选择的规则。
ID3算法构造决策树的思想大致如下:首先从根结点开始,对结点,对结点计算所有可能特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点,再对子结点递归地调用以上方法,构建决策树,直到所有特征的信息增益均很小或没有特征可以选择为止,最终得到一个决策树。其具体的算法步骤如下:
以上就是ID3决策树的构造过程,但是该过程构建的决策树往往会出现过拟合,因此,需要对树进行剪枝。
决策树的剪枝是指从已生成的树上裁剪掉一些子树或子结点,并将其根结点或者父结点作为新的叶结点,从而简化决策树模型。决策树的剪枝往往通过极小化决策树整体的的损失函数或代价函数来实现。设树 T T T的 ∣ T ∣ |T| ∣T∣, t t t是树 T T T的某个叶结点,该叶结点有 N t N_t Nt个样本点,其中 k k k类的样本点有 N t k N_{tk} Ntk个, k = 1 , 2 , ⋯   , K k=1,2, \cdots, K k=1,2,⋯,K, H t ( T ) H_{t}(T) Ht(T)为叶结点 t t t上的经验熵,\alpha \geqslant 0为惩罚参数,则决策树的损失函数可以定义为:
C α ( T ) = ∑ t = 1 ∣ T ∣ N t H t ( T ) + α ∣ T ∣ C_{\alpha}(T)=\sum_{t=1}^{|T|} N_{t} H_{t}(T)+\alpha|T| Cα(T)=t=1∑∣T∣NtHt(T)+α∣T∣其中,经验熵为:
H t ( T ) = − ∑ k N t k N t log N t k N t H_{t}(T)=-\sum_{k} \frac{N_{t k}}{N_{t}} \log \frac{N_{t k}}{N_{t}} Ht(T)=−k∑NtNtklogNtNtk将损失函数中的第一项记作:
C ( T ) = ∑ i = 1 ∣ T ∣ N t H t ( T ) = − ∑ t = 1 ∣ T ∣ ∑ k = 1 K N t k log N t k N t C(T)=\sum_{i=1}^{|T|} N_{t} H_{t}(T)=-\sum_{t=1}^{|T|} \sum_{k=1}^{K} N_{t k} \log \frac{N_{t k}}{N_{t}} C(T)=i=1∑∣T∣NtHt(T)=−t=1∑∣T∣k=1∑KNtklogNtNtk则损失函数可以表达为:
C α ( T ) = C ( T ) + α ∣ T ∣ C_{\alpha}(T)=C(T)+\alpha|T| Cα(T)=C(T)+α∣T∣其中, C ( T ) C(T) C(T)表示模型对训练数据的预测误差,即模型对训练数据的拟合程度, ∣ T ∣ |T| ∣T∣表示模型的复杂度,参数 α \alpha α则表示惩罚参数,当 α \alpha α越大时,则会选择越简单的树,反之,则选择较复杂的树。可以看出,决策树的剪枝不仅考虑模型的拟合程度,还考虑模型的复杂度,因此,相比决策树的构造过程,决策树的剪枝过程是一个全局优化的过程。决策树的剪枝步骤具体如下:
以上就是关于ID3算法的整个过程,下面介绍一个与ID3算法非常接近的决策树算法,即C4.5。
适用场景:特征和目标变量都是离散型
ID3选择的信息增益是一个绝对值的概念,对于不同的数据集,信息增益值往往不一样,对于分类问题困难时,其经验熵比较大,对应的信息增益值也会比较大,反之则比较小,因此,为了克服这个问题,C4.5对ID3算法的特征选择准则进行了改进。C4.5选取的特征选择准则是信息增益比,其定义就是将信息增益 g ( D , A ) g(D, A) g(D,A)与训练数据集 D D D的经验熵 H ( D ) H(D) H(D)之比,其计算公式如下:
g R ( D , A ) = g ( D , A ) H ( D ) g_{R}(D, A)=\frac{g(D, A)}{H(D)} gR(D,A)=H(D)g(D,A)
C4.5算法构造决策树的过程与ID3类似,只是将特征选择准则改为信息增益比,其他的都是一样的。
C4.5决策树的剪枝与ID3算法的一样,这里不再具体赘述。
适用场景:支持数值型和离散型变量,支持分类和回归问题
CART算法与ID3和C4.5不同,CART构造的是一棵二叉树,即在每次分支时,会将每个特征划分为两个区域,左分支是取值为“是”的分支,右分支是取值为“否”的分支。对于回归树,CART一般采用平方误差最小化作为特征选择的准则,对于分类树,CART一般采用基尼指数作为特征选择的准则,假设有 K K K个类,样本点属于第 k k k类的概率为 p k p_{k} pk,则概率分布的基尼指数的定义为:
Gini ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 \operatorname{Gini}(p)=\sum_{k=1}^{K} p_{k}\left(1-p_{k}\right)=1-\sum_{k=1}^{K} p_{k}^{2} Gini(p)=k=1∑Kpk(1−pk)=1−k=1∑Kpk2如果样本集合 D D D根据特征 A A A是否取某一可能值 a a a被分割成 D 1 D_1 D1和 D 2 D_2 D2两部分,即:
D 1 = { ( x , y ) ∈ D ∣ A ( x ) = a } , D 2 = D − D 1 D_{1}=\{(x, y) \in D | A(x)=a\}, \quad D_{2}=D-D_{1} D1={(x,y)∈D∣A(x)=a},D2=D−D1则在特征 A A A的条件下,集合 D D D的基尼指数定义为:
Gini ( D , A ) = ∣ D 1 ∣ ∣ D ∣ Gini ( D 1 ) + ∣ D 2 ∣ ∣ D ∣ Gini ( D 2 ) \operatorname{Gini}(D, A)=\frac{\left|D_{1}\right|}{|D|} \operatorname{Gini}\left(D_{1}\right)+\frac{\left|D_{2}\right|}{|D|} \operatorname{Gini}\left(D_{2}\right) Gini(D,A)=∣D∣∣D1∣Gini(D1)+∣D∣∣D2∣Gini(D2)当基尼指数越大时,表示数据的不确定性越大,因此,CART分类树每次分支时,选择当前基尼指数 Gini ( D , A ) \operatorname{Gini}(D, A) Gini(D,A)最小的特征作为当前结点的特征选择。
(一) 回归树的构造
对于回归树的构造,假设 X X X与 Y Y Y分别为输入和输出变量,并且 Y Y Y是连续变量,给定训练数据集:
D = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯   , ( x N , y N ) } D=\left\{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right\} D={(x1,y1),(x2,y2),⋯,(xN,yN)}则回归树在每次分支时,会依次从特征集中选择第 j j j个变量 x ( j ) x^{(j)} x(j)和它取的值 s s s,作为切分变量和切分点,并定义两个区域:
R 1 ( j , s ) = { x ∣ x ( j ) ⩽ s } R 2 ( j , s ) = { x ∣ x ( j ) > s } R_{1}(j, s)=\left\{x | x^{(j)} \leqslant s\right\} \\ R_{2}(j, s)=\left\{x | x^{(j)}>s\right\} R1(j,s)={x∣x(j)⩽s}R2(j,s)={x∣x(j)>s}然后计算两个区域中 Y Y Y的均值分别作为两个区域的预测值 c 1 c_1 c1和 c 2 c_2 c2,接着,计算两个区域的平方误差和,并从中选择可以平方误差和最小的变量和切分点作为当前的最优切分变量 j j j和最优切分点 s s s,具体地,求解:
min j , s [ min c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + min c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] \min_{j,s}[\min _{c_{1}} \sum_{x_{i} \in R_{1}(j, s)}\left(y_{i}-c_{1}\right)^{2}+\min _{c_{2}} \sum_{x_{i} \in R_{2}(j, s)}\left(y_{i}-c_{2}\right)^{2}] j,smin[c1minxi∈R1(j,s)∑(yi−c1)2+c2minxi∈R2(j,s)∑(yi−c2)2]重复以上划分过程,直到满足停止条件为止,这样便形成了一棵回归树,这样的回归树通常称为最小二乘回归树。具体的算法步骤如下:
(二) 分类树的构造
分类树则采用基尼指数选择最优特征,其算法步骤如下:
CART决策树的剪枝主要包含两个步骤:首先从生成的决策树 T 0 T_0 T0底端开始不断剪枝,直到 T 0 T_0 T0的根结点,形成一个子树序列 { T 0 , T 1 , ⋯   , T n } \left\{T_{0}, T_{1}, \cdots, T_{n}\right\} {T0,T1,⋯,Tn};接着,通过交叉验证法在独立验证集上对子树序列进行测试,从中选择最优子树。
从前面ID3算法我们可以知道,剪枝时的损失函数不仅考虑决策树对训练集的拟合程度,还考虑模型的复杂度,具体的公式如下:
C α ( T ) = C ( T ) + α ∣ T ∣ C_{\alpha}(T)=C(T)+\alpha|T| Cα(T)=C(T)+α∣T∣CART首先从整体树 T 0 T_0 T0开始剪枝,对 T 0 T_0 T0的任意内部结点 t t t,以t为单结点树的损失函数是:
C α ( t ) = C ( t ) + α C_{\alpha}(t)=C(t)+\alpha Cα(t)=C(t)+α以 t t t为根结点的子树 T t T_t Tt的损失函数是:
C α ( T t ) = C ( T t ) + α ∣ T t ∣ C_{\alpha}\left(T_{t}\right)=C\left(T_{t}\right)+\alpha\left|T_{t}\right| Cα(Tt)=C(Tt)+α∣Tt∣当 C α ( T t ) = C α ( t ) C_{\alpha}\left(T_{t}\right)=C_{\alpha}(t) Cα(Tt)=Cα(t)时,即 α = C ( t ) − C ( T t ) ∣ T t ∣ − 1 \alpha=\frac{C(t)-C\left(T_{t}\right)}{\left|T_{t}\right|-1} α=∣Tt∣−1C(t)−C(Tt)时, T t T_{t} Tt与 t t t有相同的损失函数值,而 t t t的结点少,因此 t t t比 T t T_t Tt更可取,对 T t T_t Tt进行剪枝。
因此,CART在剪枝时,对于 T 0 T_0 T0中每一内部结点 t t t,计算:
g ( t ) = C ( t ) − C ( T t ) ∣ T t ∣ − 1 g(t)=\frac{C(t)-C\left(T_{t}\right)}{\left|T_{t}\right|-1} g(t)=∣Tt∣−1C(t)−C(Tt)在 T 0 T_0 T0中减去 g ( t ) g(t) g(t)最小的 T t T_t Tt,将得到的子树作为 T 1 T_1 T1,同时将最小的 g ( t ) g(t) g(t)设为 α 1 \alpha_1 α1, T 1 T_1 T1为区间 [ α 1 , α 2 ) \left[\alpha_{1}, \alpha_{2}\right) [α1,α2)的最优子树,如此剪枝下去,直至根结点,在这一过程中,不断地增加 α \alpha α值,产生新的区间。
最后,利用独立的验证数据集,测试子树序列 T 0 , T 1 , ⋯   , T n T_{0}, T_{1}, \cdots, T_{n} T0,T1,⋯,Tn中各棵子树的平方误差或基尼指数,平方误差或基尼指数最小的子树即为最优的决策树,其所在的 α \alpha α区间即为最终 α \alpha α的取值。具体的剪枝算法步骤如下:
python中sklearn主要支持的是CART决策树,因为CART可适用的场景更广,不过,特征选择的准则sklearn也提供了两种选择,一种是“entropy”,对应本文介绍的信息增益,另一种是“gini”,对应本文的基尼指数,本文直接继承了sklearn.tree中的DecisionTreeClassifier,增加了对决策树的绘制函数,python绘制决策树需要安装graphviz,安装后如果出现中文乱码的话,可以参考这篇文章《graphviz Windows中文乱码》。具体的代码如下:
import os
from sklearn.tree import DecisionTreeClassifier, export_graphviz
class DecisionTreeClassifier(DecisionTreeClassifier):
def draw_tree(self, model, feature_names, save_path):
"""
绘制决策树
:param model: 决策树模型
:param feature_names: 结点名称. [list]
:param save_path: 文件保存路径
:return:
"""
# 生成决策树的路径dot文件,保存到save_path
export_graphviz(model, out_file=save_path,
feature_names=feature_names,
filled=True, rounded=True,
special_characters=True)
# 替换dot文件中的字体为Microsoft YaHei,以防止中文乱码
with open(save_path, 'r', encoding='utf-8') as f:
dot_data = f.read()
dot_data = dot_data.replace('fontname=helvetica', 'fontname="Microsoft YaHei"')
with open(save_path, 'w', encoding='utf-8') as f:
f.write(dot_data)
# 生成决策树图像,格式默认为png格式
os.system('dot -Tpng {0} -o {1}'.format(save_path, save_path.replace('dot', 'png')))
绘制的决策树图形大致如下:
具体的项目代码还是参考本人的github地址:
决策树由于其解释性强,计算速度快,非线性能力强,在一些对模型解释性强的行业得到了很多的应用,比如风控行业。另外,由于特征选择、决策树生成和决策树剪枝的不同,决策树衍生出了很多的算法,每个算法都有其对应的优缺点,因此,在使用时需要加以鉴别,比如ID3算法,由于不是二叉树的形式,因此该算法往往更倾向于选择类别多的特征。