机器学习笔记(八)——决策树

一、初识决策树

决策树是一个非常有意思的模型,它的建模思路是尽可能模拟人做决策的过程。因此决策树几乎没有任何抽象,完全通过生成决策规则来解决分类和回归问题。因为它的运行机制能很直接地被翻译成人类语言,即使对建模领域完全不了解的非技术人员也能很好地理解它。因此在学术上被归为白盒模型(white box model)

(一)决策树的思想

1.什么是决策树

决策树是一种常见的机器学习算法,它的思想十分朴素,类似于我们平时利用选择做决策的过程。它是类似流程图的结构,其中每个内部节点表示一个测试功能,即类似做出决策的过程(动作),每个叶节点都表示一个类标签,即在计算所有特征之后做出的决定(结果)。标签和分支表示导致这些类标签的功能的连接。从根到叶的路径表示分类规则。比如下面这个“相亲决策树”:
在这里插入图片描述
由此我们可以看到,决策树的思想还是非常直观的。
用决策树分类:从根节点开始,对实例的某一特征进行测试,根据测试结果将实例分配到其子节点,此时每个子节点对应着该特征的一个取值,如此递归的对实例进行测试并分配,直到到达叶节点,最后将实例分到叶节点的类中。

2.决策树与条件概率

在前面已经从直观上了解决策树,及其构造步骤了。现在从统计学的角度对决策树进行定义能够能好地帮助我们理解模型。

决策树表示给定特征条件下,类的条件概率分布,这个条件概率分布表示在特征空间的划分上,将特征空间根据各个特征值不断进行划分,就将特征空间分为了多个不相交的单元,在每个单元定义了一个类的概率分布,这样,这条由根节点到达叶节点的路径就成了一个条件概率分布。

假设X表示特征的随机变量,Y表示类的随机变量,那么这个条件概率可以表示为 P ( Y ∣ X ) P(Y|X) P(YX),其中X取值于给定划分下单元的集合,Y取值于类的集合。各叶结点(单元)上的条件概率往往偏向某一个类。根据输入的测试样本,由路径找到对应单元的各个类的条件概率,并将该输入测试样本分为条件概率最大的一类中,就可以完成对测试样本的分类。

下图a,表示了特种空间的一个划分。大正方形表示特征空间。这个大正方形被若干个小矩形分割,每个小矩形表示一个单元。特征空间划分上的单元构成了一个集合,X取值为单元的集合。假设只有两类正类负类,Y=+1 OR -1;小矩形中的数字表示单元的类。
在这里插入图片描述
下图b表示特征空间(图a)划分确定时,特征(划分单元)给定条件下类的条件概率分布。图b中的条件概率分布对应于图a的划分;当某个单元C的条件概率满足 P ( Y = + 1 ∣ X = C ) > 0.5 P(Y=+1|X=C)>0.5 P(Y=+1X=C)>0.5时,即认为该类属于正类,落在该单元的实例都视为正例。
在这里插入图片描述
下图c表示了根节点到各个叶子结点上不同划分的条件分布。
在这里插入图片描述
在理解了相关概念之后,提出两个问题:

  • 特征空间的划分是如何确定的?(根据一系列的评价系数确认分类特征?)
  • 该条件概率分布的概率值是如何确定的?(根据各点数据集归纳出的分类规则?)

(二)决策树的学习

1 学习目标与本质

假设给定训练数据集 D = ( ( x 1 , y 1 ) , ( x 2 , y 2 ) , … … ( x n , y n ) ) D=((x_1,y_1),(x_2,y_2),……(x_n,y_n)) D=((x1,y1),(x2,y2),xn,yn),其中 x i = ( x i ( i ) , x i ( i ) , … … x i ( n ) ) T x_i=(x_i^{(i)},x_i^{(i)},……x_i^{(n)})^T xi=(xi(i),xi(i),xi(n))T为输入实例(特征向量),n为特征个数, y i ∈ { 1 , 2 , … … K } y_i\in \left \{ 1,2, …… K\right \} yi{1,2,K}为类标记(label), i = 1 , 2 , 3 , … … N i=1,2,3,……N i=1,2,3,N,N为样本容量。

学习目标:根据给定的训练数据集构建一个决策模型,使它能够对实例进行正确的分类。

决策树学习本质上是从训练数据集中归纳出一组分类规则。与训练数据集不相矛盾的决策树(即能对训练数据进行正确分类的决策树)可能是0个或多个。我们需要找到一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。

从另一个角度看,决策树学习是由训练数据集估计条件概率模型。基于特征空间划分的类的条件概率模型有无穷多个。我们选择的条件概率模型应该不仅对训练数据有很好地拟合,而且对未知数据有很好地预测。

2 决策树损失函数

与其他模型相同,决策树学习用损失函数表示这一目标。决策树学习的损失函数通常是正则化的极大似然函数。决策树学习的策略是以损失函数为目标函数的最小化。

关于极大似然函数:极大似然法是属于数理统计范畴,旨在由果溯因。把“极大似然估计”拆成三个词:极大(最大的概率)、似然(看起来是这个样子的)、估计(就是这个样子的),连起来就是:大概率看起来是这样的,那就是这样。

比如扔一枚骰子(骰子每个面上只标记1或2),现在告诉你扔了n次骰子其中有k次朝上的是1;然后问你这个骰子标记为1的面所占的比例w是多少?极大似然法的思想就是估计当w取值为多少的时候,k次朝上的可能性最大。具体计算方法就是对表达式求最大值,得到参数值估计值:一般就是对这个表达式求一阶导=0(二阶导<0);
这就是极大似然估计方法的原理:用使概率达到最大的那个概率值w来估计真实参数w。决策树生成的过程可以理解成对决策树模型的参数估计(就是基于特征空间划分的类的概率模型),根据训练数据的特征分布,选择使得模型最契合当前样本分布空间时的条件概率模型。

当损失函数确定以后,**学习问题就变为在损失函数意义下选择最优决策树的问题。**因为从所有可能的决策树中选取最优决策树是NP完全问题,所以现实中决策树学习算法通常采用启发式方法,近似求解这一最优化问题。这样得到的决策树是次最优的。

(三)3 决策树的构建

决策树通常有三个步骤:

1. 特征选择
2. 决策树的生成
3. 决策树的修剪

决策树学习的算法通常是一个递归地选择最优特征,并根据该特征对训练数据进行分割,使得对各个子数据集有一个最好的分类的过程。这一过程对应着对特征空间的划分,也对应着决策树的构建。

这一过程对应着对特征空间的划分,也对应着决策树的构建。

  1. 开始:构建根节点,将所有训练数据都放在根节点,选择一个最优特征,按照这一特征将训练数据集分割成子集,使得各个子集有一个在当前条件下最好的分类。
  2. 如果这些子集已经能够被基本正确分类,那么构建叶节点,并将这些子集分到所对应的叶子节点去。
  3. 如果还有子集不能够被正确的分类,那么就对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点,如此递归进行,直至所有训练数据子集被基本正确的分类,或者没有合适的特征为止。
  4. 每个子集都被分到叶节点上,即都有了明确的类,这样就生成了一颗决策树。

以上方法就是决策树学习中的特征选择和决策树生成,这样生成的决策树可能对训练数据有很好的分类能力,但对未知的测试数据却未必有很好的分类能力,即可能发生过拟合现象。我们需要对已生成的树自下而上进行剪枝,将树变得更简单,从而使其具有更好的泛化能力。具体地,就是去掉过于细分的叶结点,使其回退到父结点,甚至更高的结点,然后将父结点或更高的结点改为新的叶结点,从而使得模型有较好的泛化能力。。

决策树生成和决策树剪枝是个相对的过程,决策树生成旨在得到对于当前子数据集最好的分类效果(局部最优),而决策树剪枝则是考虑全局最优,增强泛化能力。

在对此有一定了解之后,我们先看看,如何在sklearn中将决策树用起来。然后再学习其中的细节。

(四)sklearn中使用决策树

1 数据引入及可视化


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

iris = datasets.load_iris()
X = iris.data[:,2:] # iris有四个特征,这里取后两个,形成一个坐标点
y = iris.target
# 绘图
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

在这里插入图片描述

2 进行分类

rom sklearn.tree import DecisionTreeClassifier
# 创建决策树对象,最大深度max_depth为2层,criterion评判标准为entropy(熵)
dt_clt = DecisionTreeClassifier(max_depth=2,criterion='entropy')
# 将训练数据送给模型
dt_clt.fit(X,y)

# 绘制决策边界
def plot_decision_boundary(model, axis): # model是模型,axis是范围
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
        np.linspace(axis[2], axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1),
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]

    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
    
    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)

# 数据可视化    
plot_decision_boundary(dt_clt, axis=[0.5,7.5,0,3])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.scatter(X[y==2,0],X[y==2,1])
plt.show()

在这里插入图片描述

3.总结&问题

对于上图的数据可视化结果,我们可以直观地看到。y=1.75这条直线为一条决策边界,y大于1.75属于A类;y小于1.75的区域,被y=0.8划分,y<1.75||y>0.8属于B类;y<0.8属于C类。

决策树是一个非参数的决策算法,决策树可以解决分类问题,且天然支持多分类问题。决策树也可以解决回归问题,按照树的路径追踪到叶子结点,最终叶子节点对应一个数值,且回归问题的结果是一个具体的数值,就可以落在叶子结点的所有样本的平均值,作为回归的预测结果。

并且决策树具有非常好的可解释性。

那么提出一个问题:在构建决策树,进行特征选择划分时,究竟选择哪个特征更好些?

这就要求确定选择特征的准则。直观上,如果**一个特征具有更好的分类能力,或者说,按照这一特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。**比如身高、长相、收入等。在找到特征维度之后,还要确定划分阈值,如收入定在多少,作为划分标准比较合适?

因此,首先找到一个维度,然后在维度上找到一个阈值。然后以这个维度的这个阈值为依据进行划分。核心问题是:

每个节点在哪个维度做划分?选好了维度,如何确定阈值呢?

二、特征选择中的相关概念

  • 信息熵(information entropy)
  • 条件熵(conditional entropy)
  • 信息增益(information gain)
  • 信息增益率(information gain ratio)
  • 基尼指数(Gini index)

(一)信息熵

1. 什么是信息熵

熵是热力学中的概念,表示混乱程度。熵越大,热力系统中粒子无规则的运动越剧烈;熵越小,粒子越趋近于静止的状态。

引申到信息论和概率统计中,信息熵表示随机变量的不确定度。对于一组数据来说,越随机、不确定性越高,信息熵越大;不确定性越低,信息熵越小。

为了计算熵,我们需要计算所有类别所有可能值所包含的信息期望值,著名的香农公式:
H = − ∑ i = 1 k p i l o g ( p i ) H=-\sum_{i=1}^kp_ilog(p_i) H=i=1kpilog(pi)
在一个系统中,有k类的信息, p i p_i pi其中是选择该分类的概率(n/k),再乘p的对数,求和后加上负号。这是因为概率 p i p_i pi是小于1的数, l o g ( p i ) log(p_i) log(pi)是小于0的数,我们要求得到的熵是大于0的。

下面构造三个例子:假设有三组,每组为三类的信息,每组每类的概率为:
{ 1 3 , 1 3 , 1 3 } , { 1 10 , 2 10 , 7 10 } , { 1 , 0 , 0 } \{\frac {1}{3},\frac {1}{3},\frac {1}{3}\},\{\frac {1}{10},\frac {2}{10},\frac {7}{10}\},\{1,0,0\} {31,31,31},{101,102,107},{1,0,0}
按照公式,分别计算信息熵:
{ 1 3 , 1 3 , 1 3 } : H = 1.0986 \{\frac {1}{3},\frac {1}{3},\frac {1}{3}\} : H=1.0986 {31,31,31}:H=1.0986
{ 1 10 , 2 10 , 7 10 } : H = 0.8018 \{\frac {1}{10},\frac {2}{10},\frac {7}{10}\} : H=0.8018 {101,102,107}:H=0.8018
{ 1 , 0 , 0 } : H = 0.0000 \{1,0,0\} : H=0.0000 {1,0,0}:H=0.0000

我们看到,信息熵H值越小,说明数据的不确定性越小。第一个式子,每种分类情况都是均等的;第二个式子,数据有70%的概率是落在第三类中,因此要比第一个式子更稳定;第三个式子,干脆只有一个类,因此熵最小为0(特别稳定)。

如果在二分类的情况下,信息熵公式也可以写为下面的形式:

H = − ∑ i = 1 k p i l o g ( p i ) = − x l o g ( x ) − ( 1 − x ) l o g ( 1 − x ) H=-\sum_{i=1}^kp_ilog(p_i)=-xlog(x)-(1-x)log(1-x) H=i=1kpilog(pi)=xlog(x)(1x)log(1x)

2 计算二分类信息熵并可视化


import numpy as np
import matplotlib.pyplot as plt

# p可以传递数值,也可以传递向量。因此使用np.log
def entropy(p):
    return -p * np.log(p) - (1-p) * np.log(1-p)
    
# linspace生成向量x,从0到1均匀取值,绘制出x在不同值时对应的信息熵
x = np.linspace(0.01,0.99,100)

plt.plot(x,entropy(x))
plt.show()

在这里插入图片描述
形似抛物线,以0.5为对称轴。当x=0.5时,曲线取到最大值,也就是说对于信息熵来说,只有两个类别,其中一个类别是0.5,另一个类别是1-0.5时,此时信息熵是最大的,也就是最不确定的。如果x偏向于某一类,确定性变高了,信息熵变低了。

(二) 条件熵

1.条件熵的定义

设有随机变量(X,Y)。条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性。

随机变量X给定的条件下随机变量Y的条件熵H(Y|X)定义为X给定条件下,Y的条件概率分布的熵对X的数学期望:

H ( Y ∣ X ) = ∑ i = 1 n p i H ( H ∣ X = x i ) H(Y|X)=\sum_{i=1}^np_iH(H|X=x_i) H(YX)=i=1npiH(HX=xi)
其中, p i = P ( X = x i ) , i = 1 , 2 , … … , n p_i=P(X=x_i),i=1,2,……,n pi=P(X=xi),i=1,2,,n

注意,与信息熵不同的是,条件熵是数学期望,而不是变量的不确定性。

2 信息熵和条件熵的区别

下面通过一个例子来讲一下信息熵和条件熵的区别。
在这里插入图片描述
在上面这棵“相亲决策树”中,对于结果(叶子结点),有随机变量Y={见,不见}。我们可以统计出,见的个数占2/6=1/3;不见的个数占4/6=2/3。那么变量Y的熵,可以根据公式计算得到: H ( Y ) = − 1 3 l o g ( 1 3 ) − 2 3 l o g ( 2 3 ) H(Y)=-\frac{1}{3}log(\frac {1}{3})-\frac{2}{3}log(\frac {2}{3}) H(Y)=31log(31)32log(32)

对于条件熵来说,我们还需要增加一个变量X,假设是年龄这一特征。那么在年龄<=30的情况下,有五种结果,见的个数占2/5;不见的个数占3/5。在年龄大于30的情况下,只有一种结果,见为0,不见为1.

那么此时,可以得到如下的式子:
H ( Y ∣ X = ( 年 龄 ≤ 30 ) ) = − 2 5 l o g ( 2 5 ) − 3 5 l o g ( 3 5 ) H(Y|X=(年龄\leq 30))=-\frac {2}{5}log(\frac {2}{5})-\frac {3}{5}log(\frac {3}{5}) H(YX=(30))=52log(52)53log(53)
p ( X = ( 年 龄 ≤ 30 ) ) = 5 6 H ( Y ∣ X = ( 年 龄 > 30 ) ) = − 0 l o g 0 − l o g 1 = 0 p(X=(年龄\leq 30))=\frac {5}{6}H(Y|X=(年龄>30))=-0log0-log1=0 p(X=(30))=65H(YX=(>30))=0log0log1=0
p ( X = ( 年 龄 ≤ 30 ) ) = 1 6 p(X=(年龄\leq 30))=\frac {1}{6} p(X=(30))=61
然后我们终于可以计算条件熵:
随机变量X给定的条件下随机变量Y的条件熵H(Y|X)定义为X给定条件下,Y的条件概率分布的熵对X的数学期望:
H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X)=\sum_{i=1}^np_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)
其中 p i = P ( X = x i ) , i = 1 , 2 , … … , n p_i=P(X=x_i),i=1,2,……,n pi=P(X=xi),i=1,2,,n
现在计算已知年龄的条件下的条件熵,以30为界有两种情况,然后将上述结果带入公式中,求得期望。

其实条件熵意思是按一个新的变量的每个值对原变量进行分类,比如上面这个题把“见与不见”按“年龄”分成了两类。

然后在每一个小类里面,都计算一个小熵,然后每一个小熵乘以各个类别的概率,然后求和。

所谓小类,就是不包含当前所选特征的其他维度,即当前的特征是给定的条件,在其他维度下求熵,是条件下的。各类别的概率,是当前这个小类别(年龄>30)下的样本量除以总的样本量。

我们用另一个变量对原变量分类后,原变量的不确定性就会减小了,因为新增了Y的信息,可以感受一下。不确定程度减少了多少就是信息的增益。

(三)信息增益

1. 什么是信息熵增益

在划分数据集前后信息发生的变化称为信息增益,获得信息增益最高的特征就是最好的选择。

信息增益就是:

以某特征划分数据集前后的熵的差值

划分前,样本集合D的熵(也称经验熵)是为H(D);使用某个特征A划分数据集D,计算划分后的数据子集(给定特征A的情况下,数据集D)的条件熵(经验条件熵)H(D|A)。则公式为:
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)
在计算过程中,使用所有特征划分数据集D,得到多个特征划分数据集D的信息增益(列表)。从这些信息增益中选择最大的,因而当前结点的划分特征便是使信息增益最大的划分所使用的特征。

2. 信息熵增益的使用

通过对信息增益的进一步理解,我们发现:对于待划分的数据集D,其经验熵H(D)是不变的,但是划分之后得到的条件熵H(D|A)是变化的(特征A的选择不同)。

条件熵H(D|A)越小,说明使用此特征划分得到的子集的不确定性越小(也就是纯度越高),因为得到的信息增益就越大。说明在决策树构建的过程中我们总是希望集合往最快到达纯度更高的子集合方向发展,因此我们总是选择使得信息增益最大的特征来划分当前数据集D。

信息增益偏向取值较多的特征。

原因:当特征的取值较多时,根据此特征划分更容易得到纯度更高的子集,因此划分之后的熵更低,由于划分前的熵是一定的,因此信息增益更大,因此信息增益比较偏向取值较多的特征。

(四)信息增益率

1. 为什么需要信息增益率

我们已经知道,选取信息增益大的特征,可以得到更好的划分。那么为什么要用信息增益比呢?信息增益比优于信息增益的地方在哪呢?

这是因为,信息增益偏向于选择取值较多的特征,容易过拟合。

假设在信用卡逾期风险预测场景中,有如下数据:

信用级别工资级别是否逾期
11
21
32
42

那么此时我们分别计算“信用级别”和“工资级别”条件下“预期”的条件熵。

A = H(是否逾期|信用级别) = p(信用等级=1)H(是否逾期|信用等级=1) + p(信用等级=2)H(是否逾期|信用等级=2) + p(信用等级=3)H(是否逾期|信用等级=3) + p(信用等级=4)H(是否逾期|信用等级=4)=0

B = H(是否逾期|工资级别) = p(工资级别=1)H(是否逾期|工资级别=1) + p(工资级别=2)H(是否逾期|工资级别=2)

很显然 B > A,也就是说,对于增益信息:g(D|信用级别) > g(D|工资级别)。很明显,信息增益偏向于选择取值较多的特征,但是根据熵的公式可知,特征越多,熵越大。

那么有什么办法呢?是在信息增益的基础之上乘上一个惩罚参数,对树分支过多的情况进行惩罚,抵消了特征变量的复杂程度,避免了过拟合的存在。

2.信息增益率的定义

特征A对训练数据集D的信息增益比定义为:其信息增益g(D,A)与训练数据集D关于特征A的值的熵HA(D)之比,即:
g R ( D , A ) = g ( D , A ) H A ( D ) g_R(D,A)=\frac {g(D,A)}{H_A(D)} gR(D,A)=HA(D)g(D,A)
注意,其中的 H A ( D ) H_A(D) HA(D)是:对于样本集合D,将当前特征A作为随机变量(取值是特征A的各个特征值),求得的经验熵。(之前是把集合类别(年龄、长相)作为随机变量,现在把某个特征作为随机变量,按照此特征的特征取值对集合D进行划分,计算熵 H A ( D ) H_A(D) HA(D)

其中, H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g ∣ D i ∣ ∣ D ∣ H_A(D)=−\sum_{i=1}^n \frac {|D_i|}{|D|}log\frac{|D_i|}{|D|} HA(D)=i=1nDDilogDDi ,n是特征A取值的个数。注意区别H(D|A)和HA(D)。

信息增益比本质:是在信息增益的基础之上乘上一个惩罚参数。特征个数较多时,惩罚参数较小;特征个数较少时,惩罚参数较大。

信息增益比 = 惩罚参数 * 信息增益

所谓惩罚参数,是数据集D以特征A作为随机变量的熵的倒数,即:将特征A取值相同的样本划分到同一个子集中(之前所说数据集的熵是依据类别进行划分的)。

信息增益比的缺点是:偏向取值较少的特征。原因:当特征取值较少时HA(D)的值较小,因此其倒数较大,因而信息增益比较大。因而偏向取值较少的特征。

基于以上特点,在使用增益信息比时,并不是直接选择信息增益率最大的特征,而是现在候选特征中找出信息增益高于平均水平的特征,然后在这些特征中再选择信息增益率最高的特征

(五)基尼系数

1 基尼系数的定义

基尼系数(Gini),也被称为基尼不纯度,表示在样本集合中一个随机选中的样本被分错的概率。

Gini系数越小表示集合中被选中的样本被分错的概率越小,也就是说集合的纯度越高,反之,基尼指数集合越不纯。

即:基尼指数(基尼不纯度)= 样本被选中的概率 * 样本被分错的概率

有如下公式:

G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p)=\sum_{k=1}^Kp_k(1-p_k)=1-\sum_{k=1}^Kp_k^2 Gini(p)=k=1Kpk(1pk)=1k=1Kpk2
对上述公式进行说明:

  • p k p_k pk表示选中的样本属于k类别的概率,则这个样本被分错的概率是 ( 1 − p k ) (1-p_k) (1pk)
  • 因为样本集合中有k个类别,一个随机选中的样本可以属于这k个类别中的任意一个,因而累加所有的k个类别。
  • 当二分类时, G = 2 p ( 1 − p ) G=2p(1-p) G=2p(1p)

样本集合D的基尼系数:假设集合中有K个类别,每个类别的概率是 ∣ C k ∣ ∣ D ∣ \frac{|C_k|}{|D|} DCk,其中 ∣ C k ∣ |C_k| Ck表示类别k的样本个数, ∣ D ∣ {|D|} D表示样本总数,则:
G i n i ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D)=1-\sum_{k=1}^K(\frac{|C_k|}{|D|})^2 Gini(D)=1k=1K(DCk)2

2. 特征A划分样本集合D之后的基尼指数

一般来说,我们在使用中,用某个特征划分样本集合只有两个集合(CART):

  1. 等于给定的特征值的样本集合D1
  2. 不等于给定的特征值的样本集合D2

这样就可以对拥有多个取值的特征的二值处理。

举个例子:

假设现在有特征 “学历”,此特征有三个特征取值:“本科”,“硕士”, “博士”,

当使用“学历”这个特征对样本集合D进行划分时,划分值分别有三个,因而有三种划分的可能集合,划分后的子集如下:

划分点:“本科”,划分后的子集合 :{本科},{硕士,博士}
划分点:“硕士”,划分后的子集合 :{硕士},{本科,博士}
划分点:“硕士”,划分后的子集合 :{博士},{本科,硕士}
对于上述的每一种划分,都可以计算出基于划分特征=某个特征值将样本集合D划分为两个子集的纯度:

G i n i ( D , A ) = ∣ D 1 ∣ D G i n i ( D 1 ) + ∣ D 2 ∣ D G i n i ( D 2 ) Gini(D,A)=\frac{|D_1|}{D}Gini(D_1)+\frac{|D_2|}{D}Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)
因而对于一个具有多个取值(超过2个)的特征,需要计算以每一个取值作为划分点,对样本D划分之后子集的纯度Gini(D,Ai),(其中Ai表示特征A的可能取值)。然后从所有的可能划分的Gini(D,Ai)中找出Gini指数最小的划分,这个划分的划分点,便是使用特征A对样本集合D进行划分的最佳划分点。

(六)小结

特征选择也就是选择最优划分属性,从当前数据的特征中选择一个特征作为当前节点的划分标准。我们希望在不断划分的过程中,决策树的分支节点所包含的样本尽可能属于同一类,即节点的“纯度”越来越高。
一系列的概念:信息熵、条件熵、信息增益、信息增益率、基尼系数等,是决策树特征选择的基础。

三、决策树特征选择之寻找最优划分

决策树算法的三个步骤:特征选择、决策树生成、决策树剪枝。其中特征选择要解决的核心问题就是:

  • 每个节点在哪个维度上做划分?
  • 某个维度在哪个值上做划分?

划分的依据是: 要让数据划分成两部分之后,系统整体的信息熵降低。

具体方法是: 对所有的划分可行性进行搜索。我们模拟在一个节点上进行搜索,找到一个节点上信息熵的最优划分。

那么问题来了: 我们如何找到各个特征/节点上的最优划分呢?

(一) 信息熵的最优划分

1.模拟贷款申请

现在我们以银行贷款申请业务为例,模拟四个特征,分别是:年龄、有工作、有房子、信贷情况。
在这里插入图片描述
下面就通过代码实现,找到当前应该在哪个特征维度上的哪个值进行最优划分:

2.代码实现


import numpy as np
from collections import Counter
from math import log

# 每列:['年龄','有工作','有自己的房子','信贷情况','是否申请贷款']
dataSet=np.array([[0, 0, 0, 0, 0],
                  [0, 0, 0, 1, 0],
                  [0, 1, 0, 1, 1],
                  [0, 1, 1, 0, 1],
                  [0, 0, 0, 0, 0],
                  [1, 0, 0, 0, 0],
                  [1, 0, 0, 1, 0],
                  [1, 1, 1, 1, 1],
                  [1, 0, 1, 2, 1],
                  [1, 0, 1, 2, 1],
                  [2, 0, 1, 2, 1],
                  [2, 0, 1, 1, 1],
                  [2, 1, 0, 1, 1],
                  [2, 1, 0, 2, 1],
                  [2, 0, 0, 0, 0]])
featList = ['年龄','有工作','有自己的房子','信贷情况']

"""
函数说明:计算给定标签的经验熵(信息熵)
Parameters:
    y:使用标签y计算信息熵,,此时传递y是多维数组
    计算信息熵需要每种类别出现的概率p,因此传入包含分类信息的标签y
Returns:
    entropy:经验熵
"""
def calEntropy(y):
    # 计数器,统计y中所有类别出现的次数
    # 扁平化,将嵌套的多维数组变成一维数组
    counter = Counter(y.flatten())
    entropy = 0
    for num in counter.values():
        p = num / len(y)
        entropy += -p * log(p)
    return entropy

"""
函数说明:根据传递进来的特征维度及值,将数据划分为2类
Parameters:
    X,y,featVec,value:特征向量、标签、特征维度、值
Returns:
    返回划分为两类的后的数据
"""
def split(X, y, featVec, value):
    # 使用维度featVect上的value,将数据划分成左右两部分
    # 得到的布尔向量,传入array中做索引,即可找出满足条件的相应数据(布尔屏蔽)
    index_a = (X[:,featVec] <= value)
    index_b = (X[:,featVec] > value)
    return X[index_a], X[index_b], y[index_a], y[index_b]
    

"""
函数说明:寻找最优划分
Parameters:
    X,y:特征向量、标签
Returns:
    返回最优熵,以及在哪个维度、哪个值进行划分
"""
def try_split(X, y):
    # 搞一个熵的初始值:正无穷
    best_entropy = float('inf')
    best_featVec = -1    # 特征向量
    best_value = -1
    # 遍历每一个特征维度(列)
    for featVec in range(X.shape[1]):
        # 然后需要找到每个特征维度上的划分点。
        # 找出该维度上的每个两个样本点的中间值,作为候选划分点。
        # 为了方便寻找候选划分点,可以对该维度上的数值进行排序,
        # argsort函数返回的是数组值从小到大的索引值(不打乱原来的顺序)
        sort_index = np.argsort(X[:,featVec])        
        for i in range(1, len(X)):
            if X[sort_index[i-1], featVec] != X[sort_index[i], featVec]:
                value = (X[sort_index[i-1], featVec] + X[sort_index[i], featVec]) / 2
                X_l, X_r, y_l, y_r = split(X, y, featVec, value)
                # 要求最优划分,需要看在此划分下得到的两个分类数据集的熵之和是否是最小的
                entropy = calEntropy(y_l) + calEntropy(y_r)
                if entropy < best_entropy:
                    best_entropy, best_featVec, best_value = entropy, featVec, value
    return best_entropy, best_featVec, best_value      
    
best_entropy, best_featVec, best_value = try_split(X, y)
print("最优熵:", best_featVec)
print("在哪个维度熵进行划分:", best_featVec)
print("在哪个值上进行划分:", best_value)

(二)信息增益&信息增益率最优划分

1. 信息增益最优划分实现

import numpy as np
from collections import Counter
from math import log

# 每列:['年龄','有工作','有自己的房子','信贷情况','是否申请贷款'],其中'是否申请贷款'是label
dataSet=np.array([[0, 0, 0, 0, 0],
                  [0, 0, 0, 1, 0],
                  [0, 1, 0, 1, 1],
                  [0, 1, 1, 0, 1],
                  [0, 0, 0, 0, 0],
                  [1, 0, 0, 0, 0],
                  [1, 0, 0, 1, 0],
                  [1, 1, 1, 1, 1],
                  [1, 0, 1, 2, 1],
                  [1, 0, 1, 2, 1],
                  [2, 0, 1, 2, 1],
                  [2, 0, 1, 1, 1],
                  [2, 1, 0, 1, 1],
                  [2, 1, 0, 2, 1],
                  [2, 0, 0, 0, 0]])
X = dataSet[:,:4]
y = dataSet[:,-1:]
strs = ['年龄','有工作','有自己的房子','信贷情况','是否申请贷款']


"""
函数说明:计算经验熵
Parameters:
    dataSet:样本数据集D
Returns:
    entory:经验熵
"""
def calEntropy(dataSet):
    #返回数据集行数
    numEntries=len(dataSet)
    #保存每个标签(label)出现次数的字典:<label:出现次数>
    labelCounts={}
    #对每组特征向量进行统计
    for featVec in dataSet:
        #提取标签信息
        currentLabel=featVec[-1]
        #如果标签没有放入统计次数的字典,添加进去
        if currentLabel not in labelCounts.keys():
            labelCounts[currentLabel]=0
        #label计数
        labelCounts[currentLabel]+=1
    
    entory=0.0
    #计算经验熵
    for key in labelCounts:
        #选择该标签的概率
        prob=float(labelCounts[key])/numEntries 
        #利用公式计算
        entory-=prob*log(prob,2)
    return entory 


"""
函数说明:得到当前特征条件下的小类的所有样本集合(即不包含当前特征的特征样本集)
Parameters:
    dataSet:样本数据集D
    curtFeatIndex:当前用来划分数据集的特征A的位置
    categories:特征A所有可能分类的集合
Returns:
    otherFeatSets:不包含当前特征的特征样本集
"""
def currentConditionSet(dataSet, curtFeatIndex, categroy):
    otherFeatSets = []
    # 对于数据集中的所有特征向量,抛去当前特征后拼接好的集合
    for featVec in dataSet:
        if featVec[curtFeatIndex] == categroy:
            otherFeatSet = np.append(featVec[:curtFeatIndex],featVec[curtFeatIndex+1:])
            otherFeatSets.append(otherFeatSet) 
    return otherFeatSets


"""
函数说明:在选择当前特征的条件下,计算熵,即条件熵
Parameters:
    dataSet:样本数据集D
    curtFeatIndex:当前用来划分数据集的特征A的位置
    categories:特征A所有可能分类的集合
Returns:
    conditionalEnt:返回条件熵
"""
def calConditionalEnt(dataSet, curtFeatIndex, categories):
    conditionalEnt = 0
    # 对于每一个分类,计算选择当前特征的条件下条件熵
    # 比如在选择“年龄”这一特征下,共有“老中青”三个小分类
    for categroy in categories:
        # 得到当前特征条件下的小类的所有样本集合,即不包含当前特征的特征样本集
        # 如得到在选择“青年”这个小类下一共有5个样本,且不包含“年龄”这一特征
        cdtSetCategroy = currentConditionSet(dataSet, curtFeatIndex, categroy)
        # 计算当前特征条件下的小分类,占总分类的比例
        prob = len(cdtSetCategroy) / float(dataSet.shape[0])
        # 累加得到条件熵
        conditionalEnt += prob * calEntropy(cdtSetCategroy)
    return conditionalEnt


"""
函数说明:计算信息增益
Parameters:
    baseEntropy:划分样本集合D的熵是为H(D),即基本熵
    dataSet:样本数据集D
    curtFeatIndex:当前用来划分数据集的特征A的位置
Returns:
    infoGain:信息增益值
"""
def calInfoGain(baseEntropy,dataSet,curtFeatIndex):
    
    conditionalEnt = 0.0
    
    # categories是所有特征向量中当前特征的对应值的set集合(去重复)
    # 相当于该特征一共有几种分类,如“年龄”这一特征,分为“老中青”三类
    categories = set(dataSet[:,curtFeatIndex])
    
    # 计算划分后的数据子集(给定特征A的情况下,数据集D)的条件熵(经验条件熵)H(D|A)
    conditionalEnt = calConditionalEnt(dataSet,curtFeatIndex,categories)
    
    # 计算信息增益:g(D,A)=H(D)−H(D|A)
    infoGain = baseEntropy - conditionalEnt
    
    #打印每个特征的信息增益
    print("第%d个特征的增益为%.3f" % (curtFeatIndex, infoGain))
    return infoGain


"""
函数说明:寻找最优划分
Parameters:
    dataSet:数据集
Returns:
    打印最优划分结果
"""
def optimalPartition(dataSet):
    bestInfoGain = -1   # 最佳信息增益初始值
    bestFeatVec = -1    # 最佳划分的特征向量
    # 划分前样本集合D的熵H(D),即基本熵
    baseEntropy = calEntropy(dataSet)
    
    # 遍历每一个特征维度(列),得到基于当前特征划分的信息增益
    for curtFeatIndex in range(dataSet.shape[1]-1):
        
        # 计算信息增益
        infoGain = calInfoGain(baseEntropy, dataSet, curtFeatIndex)
        
        # 选取最优信息增益的划分
        if (infoGain > bestInfoGain):
            #更新信息增益,找到最大的信息增益
            bestInfoGain = infoGain
            #记录信息增益最大的特征的索引值
            bestFeatVec = curtFeatIndex
    
    print("最佳的划分为第%d个特征,是”%s“,信息增益为%.3f" % (bestFeatVec,featList[bestFeatVec],bestInfoGain))
    return bestFeatVec     

optimalPartition(dataSet)

输出:

  • 第0个特征的增益为0.083
  • 第1个特征的增益为0.324
  • 第2个特征的增益为0.420
  • 第3个特征的增益为0.363
  • 最佳的划分为第2个特征,是”有自己的房子“,信息增益为0.420

2. 信息增益率最优划分实现

根据信息增益率的定义,对上面的代码进行改造,可以得到信息增益率的最优选择实现


"""
函数说明:计算惩罚参数,信息增益g(D,A)与训练数据集D关于特征A的值的熵HA(D)之比
Parameters:
    dataSet:样本数据集D
    curtFeatIndex:当前用来划分数据集的特征A的位置
    categories:特征A所有可能分类的集合
Returns:
    conditionalEnt:惩罚参数
"""
def calPenaltyPara(dataSet, curtFeatIndex, categories):
    penaltyItem = 1
    # 对于每一个分类,计算选择当前特征的条件下条件熵
    # 比如在选择“年龄”这一特征下,共有“老中青”三个小分类
    for categroy in categories:
        # 得到当前特征条件下的小类的所有样本集合,即不包含当前特征的特征样本集
        # 如得到在选择“青年”这个小类下一共有5个样本,且不包含“年龄”这一特征
        cdtSetCategroy = currentConditionSet(dataSet, curtFeatIndex, categroy)
        # 计算当前特征条件下的小分类,占总分类的比例
        prob = len(cdtSetCategroy) / float(dataSet.shape[0])
        # 累加得到惩罚项
        penaltyItem += -prob * log(prob,2)
    return penaltyItem

"""
函数说明:计算信息增益率(惩罚参数 * 信息增益)
Parameters:
    baseEntropy:划分样本集合D的熵是为H(D),即基本熵
    dataSet:样本数据集D
    curtFeatIndex:当前用来划分数据集的特征A的位置
Returns:
    infoGain:信息增益值
"""
def calInfoGainRate(baseEntropy,dataSet,curtFeatIndex):
    infoGainRate = 0.0
    # 计算信息增益
    infoGain = calInfoGain(baseEntropy,dataSet,curtFeatIndex)
    # 得到该特征的所有分类
    categories = set(dataSet[:,curtFeatIndex])
    # 计算惩罚项
    penaltyItem = calPenaltyPara(dataSet, curtFeatIndex, categories)
    # 计算信息增益率
    infoGainRatio = infoGain / penaltyItem
    
    #打印每个特征的信息增益率
    print("第%d个特征的增益率为%.3f" % (curtFeatIndex, infoGainRatio))
    return infoGainRatio

"""
函数说明:寻找最优划分
Parameters:
    dataSet:数据集
Returns:
    打印最优划分结果
"""
def optimalPartition(dataSet):
    bestInfoGainRatio = 0.0   # 最佳信息增益率初始值
    bestFeatVec = -1    # 最佳划分的特征向量
    # 划分前样本集合D的熵H(D),即基本熵
    baseEntropy = calEntropy(dataSet)
    
    # 遍历每一个特征维度(列),得到基于当前特征划分的信息增益
    for curtFeatIndex in range(dataSet.shape[1]-1):
        
        # categories是所有特征向量中当前特征的对应值的set集合(去重复)
        # 相当于该特征一共有几种分类,如“年龄”这一特征,分为“老中青”三类
        #categories = set(dataSet[:,curtFeatIndex])
        
        # 计算信息增益率
        infoGainRatio = calInfoGainRate(baseEntropy, dataSet, curtFeatIndex)
        
        # 选取最优信息增益率的划分
        if (infoGainRatio > bestInfoGainRatio):
            #更新信息增益率,找到最大的信息增益率
            bestInfoGainRatio = infoGainRatio
            #记录信息增益率最大的特征的索引值
            bestFeatVec = curtFeatIndex
    
    print("最佳的划分为第%d个特征,是”%s“,信息增益率为%.3f" % (bestFeatVec,strs[bestFeatVec],bestInfoGainRatio))
    return     

optimalPartition(dataSet)

第0个特征的增益为0.083
第0个特征的增益率为0.032
第1个特征的增益为0.324
第1个特征的增益率为0.169
第2个特征的增益为0.420
第2个特征的增益率为0.213
第3个特征的增益为0.363
第3个特征的增益率为0.141
最佳的划分为第2个特征,是”有自己的房子“,信息增益率为0.213

(三) 基尼系数最优划分

1. 基尼系数最优划分实现

"""
函数说明:计算基尼系数
Parameters:
    y:使用标签y计算信息熵,此时传递y是多维数组
Returns:
    entropy:经验熵
"""
def calGini(y):
    # 计数器,统计y中所有类别出现的次数
    # 扁平化,将嵌套的多维数组变成一维数组
    counter = Counter(y.flatten())
    gini = 1
    for num in counter.values():
        p = num / len(y)
        gini -= p ** 2
    return gini


"""
函数说明:寻找最优划分
Parameters:
    X,y:特征向量、标签
Returns:
    返回最优熵,以及在哪个维度、哪个值进行划分
"""
def try_split(X, y):
    # 搞一个基尼系数的初始值:正无穷
    bestGini = float('inf')
    bestFeatVec = -1    # 特征向量
    bestValue = -1
    # 遍历每一个特征维度(列)
    for featVec in range(X.shape[1]):
        # 然后需要找到每个特征维度上的划分点。
        # 找出该维度上的每个两个样本点的中间值,作为候选划分点。
        # 为了方便寻找候选划分点,可以对该维度上的数值进行排序,
        # argsort函数返回的是数组值从小到大的索引值(不打乱原来的顺序)
        sort_index = np.argsort(X[:,featVec])        
        for i in range(1, len(X)):
            if X[sort_index[i-1], featVec] != X[sort_index[i], featVec]:
                value = (X[sort_index[i-1], featVec] + X[sort_index[i], featVec]) / 2
                X_l, X_r, y_l, y_r = split(X, y, featVec, value)
                # 要求最优划分,需要看在此划分下得到的两个分类数据集的熵之和是否是最小的
                gini = calGini(y_l) + calGini(y_r)
                if gini < bestGini:
                    bestGini, bestFeatVec, bestValue = gini, featVec, value
    return bestGini, bestFeatVec, bestValue

bestGini, bestFeatVec, bestValue = try_split(X, y)
print("最优基尼系数:", bestGini)
print("在哪个维度上进行划分:", bestFeatVec)
print("在哪个值上进行划分:", bestValue)

最优基尼系数: 0.4444444444444445
在哪个维度上进行划分: 2
在哪个值上进行划分: 0.5

四 构建算法之ID3、C4.5

(一)ID3算法介绍

1.简介

ID3算法是一种分类预测算法,算法以信息论中的“信息增益”为基础。核心是通过计算每个特征的信息增益,每次划分选取信息增益最高的属性为划分标准,递归地构建决策树。

ID3相当于用极大似然法进行概率模型的选择。

具体方法是:

  1. 从根结点(root node)开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征。
  2. 由该特征的不同取值建立子节点,再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止;
  3. 最后得到一个决策树。

从ID3的构建树过程而言,它可以看成使用贪心算法得到近似最优的一颗决策树,它无法保证是最优的。

2.构建流程

以递归的方式构建一棵决策树,其算法流程如下:

算法:createTree(dataSet,featList,bestFeatLists)。由给定的训练数据产生一棵判定树。
输入:
    dataSet:训练数据集
    featList:分类属性标签
    bestFeatLists:存储选择的最优特征标签
输出:
    myTree:一棵判定树。
方法:
createTree(dataSet,featList,bestFeatLists)
1)从传入的数据集dataSet中切割出分类标签,yList
2)如果yList中只有同一种标签,说明已经递归到分类边界了,则返回该标签
3)如果已经处理了dataSet中所有属性(列),但是类标签依然不是唯一的,采用多数判决的方法决定该子节点的分类
4)找出dataSet最优划分(信息增益最大)的特征所在位置bestFeatVec
5)在分类属性标签featList找出该位置所对应的特征值bestFeatLabel,并将该特征值存储到bestFeatLists中
6)将最优划分特征值作为当前(子)树的根节点,生成初始决策树myTree(用字典表示一个树结构)
7)在featList中删除当前已经使用过的特征标签(因为每次选择特征作为条件,dataSet会删掉这一列,形成新的子类,因此对应的featList中的值也要删掉)
8)确定子树分支:获取已选择的最优划分特征所对应的值分类categories(如“年龄”是最优特征,则“老”“中”“青”三个子类)
9)遍历每一个当前特征下的子类,在每个子类中,递归地调用创建决策树的方法,将递归调用的结果作为当前树节点的一个分支(构建树的方法是:特征作为字典的key,所得到的分类结果作为value;子树进行嵌套)

2.代码示例

现在我们以银行贷款申请业务为例,模拟四个特征,分别是:年龄、有工作、有房子、信贷情况。


"""
函数说明:数据集已经处理了所有属性,但是类标签依然不是唯一的,采用多数判决的方法决定该子节点的分类
        即统计yList中出现次数最多的元素(类标签)
Parameters:
    yList:类标签列表
Returns:
    sortedClassCount[0][0]:出现次数最多的元素(类标签)
"""
def majorityCnt(yList):
    yCount={}
    #统计yList中每个元素出现的次数
    for vote in yList:
        if vote not in yCount.keys():
            yCount[vote]=0
        yCount[vote]+=1
        #根据字典的值降序排列
        sortedYCount=sorted(yCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedYCount[0][0]

"""
函数说明:创建决策树

Parameters:
    dataSet:训练数据集
    featList:分类属性标签
    bestFeatLists:存储选择的最优特征标签
Returns:
    myTree:决策树
"""
def createTree(dataSet,featList,bestFeatLists):
    # 取训练数据集最后一列,即分类标签
    yList=[example[-1] for example in dataSet]
    
    # 如果类别完全相同,则停止继续划分,
    # 即yList中所有类别都是同一数据值(该类别数值个数等于列表长度)
    if yList.count(yList[0])==len(yList):
        # 返回该类别数值
        return yList[0]
    
    
    # 数据集已经处理了所有属性,但是类标签依然不是唯一的,
    # 则采用多数判决的方法决定该子节点的分类
    # 为什么要如此判断?dataSet的列是不断减少的,dataSet某一行的长度,就是列
    if len(dataSet[0])==1:
        return majorityCnt(yList)
    
    # 选择最优划分的特征index
    bestFeatVec = optimalPartition(dataSet)
    # 最优特征index所对应的分类标签,作为树的根节点
    bestFeatLabel=featList[bestFeatVec]
    # 存储选择的最优特征标签
    bestFeatLists.append(bestFeatLabel)
    
    # 将最优划分特征值作为当前(子)树的根节点,生成初始决策树(用字典表示一个树结构)
    myTree={bestFeatLabel:{}}
    
    # 删除已经使用的特征标签(del删除变量,解除引用)
    # 因为每次选择特征作为条件,dataSet会删掉这一列,形成新的子类,因此对应的featList中的值也要删掉
    del(featList[bestFeatVec])
    print('featList: ',featList)
    
    
    # 得到训练集中所有最优特征那一列所对应的值
    featValues=[example[bestFeatVec] for example in dataSet]
    # 去掉重复的属性值,得到最优特征下的子类
    categories=set(featValues)
    
    # 遍历最优特征列所对应的值,创建决策树
    # 如“年龄”是最优特征,则遍历“老”“中”“青”三个子类
    for category in categories:
        # 根据当前数据集、最优划分的特征index以及每个分类(条件)得到(条件下的子集)
        subDataSet = np.array(currentConditionSet(dataSet,bestFeatVec,category))
        # 递归地调用创建决策树的方法,将递归调用的结果作为当前树节点的一个分支
        myTree[bestFeatLabel][category]=createTree(subDataSet,featList,bestFeatLists)
    return myTree

if __name__=='__main__':
    #print('featLabels',featLabels)
    bestFeatLists = []
    myTree=createTree(dataSet,featList,bestFeatLists)
    print(myTree)

输出:
第0个特征的增益为0.083
第1个特征的增益为0.324
第2个特征的增益为0.420
第3个特征的增益为0.363
最佳的划分为第2个特征,是”有自己的房子“,信息增益为0.420
第0个特征的增益为0.252
第1个特征的增益为0.918
第2个特征的增益为0.474 最佳的划分为第1个特征,是”有工作“,信息增益为0.918 {‘有自己的房子’: {0: {‘有工作’: {0: 0, 1: 1}}, 1: 1}}
解读结果:
在这里插入图片描述

(三)ID3算法总结

1. 优缺点:

相对于其他数据挖掘算法,决策树在以下几个方面拥有优势

  1. 决策树易于理解和实现. 人们在通过解释后都有能力去理解决策树所表达的意义。
  2. 对于决策树,数据的准备往往是简单或者是不必要的 . 其他的技术往往要求先把数据一般化,比如去掉多余的或者空白的属性。
  3. 能够同时处理数据型和常规型属性。其他的技术往往要求数据属性的单一。
  4. 是一个白盒模型如果给定一个观察的模型,那么根据所产生的决策树很容易推出相应的逻辑表达式。
  5. 易于通过静态测试来对模型进行评测。表示有可能测量该模型的可信度。
  6. 在相对短的时间内能够对大型数据源做出可行且效果良好的结果。

ID3算法可用于划分标准称型数据,但存在一些问题:

  1. 没有剪枝过程,为了去除过渡数据匹配的问题,可通过裁剪合并相邻的无法产生大量信息增益的叶子节点;
  2. 信息增益的方法偏向选择具有大量值的属性,也就是说某个属性特征索取的不同值越多,那么越有可能作为分裂属性,这样是不合理的;
  3. 只可以处理离散分布的数据特征
  4. ID3算法只考虑了树的生成,即尽可能的是模型拟合当前训练数据集,所以该算法生成的树容易过拟合。

2 ID3总结

总结基本思想:

  1. 初始化属性集合和数据集合
  2. 计算数据集合信息熵S和所有属性的信息熵,选择信息增益最大的属性作为当前决策节点
  3. 更新数据集合和属性集合(删除掉上一步中使用的属性,并按照属性值来划分不同分支的数据集合)
  4. 依次对每种取值情况下的子集重复第二步
  5. 若子集只包含单一属性,则为分支为叶子节点,根据其属性值标记。
  6. 完成所有属性集合的划分

注意:该算法使用了贪婪搜索,从不回溯重新考虑之前的选择情况。

(四) C4.5算法

C4.5算法是数据挖掘十大算法之一,它是对ID3算法的改进,相对于ID3算法主要有以下几个改进:

  1. 用信息增益比来选择属性
  2. 在决策树的构造过程中对树进行剪枝
  3. 对非离散数据也能处理
  4. 能够对不完整数据进行处理

C4.5算法与ID3算法过程相似,仅在特征选择时,使用信息增益比作为特征选择准则。

其伪代码如下:
在这里插入图片描述

(五)小结

  • ID3:

熵表示的是数据中包含的信息量大小。熵越小,数据的纯度越高,也就是说数据越趋于一致,这是我们希望的划分之后每个子节点的样子。

信息增益 = 划分前熵 - 划分后熵。信息增益越大,则意味着使用属性 a 来进行划分所获得的 “纯度提升” 越大 **。也就是说,用属性 a 来划分训练集,得到的结果中纯度比较高。

ID3 仅仅适用于二分类问题。ID3 仅仅能够处理离散属性。

  • C4.5:

C4.5 克服了 ID3 仅仅能够处理离散属性的问题,以及信息增益偏向选择取值较多特征的问题,使用信息增益比来选择特征。信息增益比 = 信息增益 / 划分前熵 选择信息增益比最大的作为最优特征。

C4.5 处理连续特征是先将特征取值排序,以连续两个值中间值作为划分标准。尝试每一种划分,并计算修正后的信息增益,选择信息增益最大的分裂点作为该属性的分裂点。

  • 信息增益 vs 信息增益比:

之所以引入了信息增益比,是由于信息增益的一个缺点。那就是:信息增益总是偏向于选择取值较多的属性。信息增益比在此基础上增加了一个罚项,解决了这个问题。

五 剪枝与sklearn中的决策树

(一)剪枝

当训练数据量大、特征数量较多时构建的决策树可能很庞大,这样的决策树用来分类是否好?答案是否定的。

决策树是依据训练集进行构建的,为了尽可能正确地分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多。这就可能会把训练样本学的“太好”了,以至于把训练集自身的一些特点当作所有数据都具有的一般性质而导致过拟合。因此可主动去掉一些分支来降低过拟合风险。

决策树非常容易产生过拟合,实际所有非参数学习算法,都非常容易产生过拟合。

因此,对于决策树的构建还需要最后一步,即决策树的修剪。两个目的:降低复杂度,解决过拟合。

决策树的修剪,也就是剪枝操作,主要分为两种:

  • 预剪枝(Pre-Pruning)
  • 后剪枝(Post-Pruning)

接下来我们将详细地介绍这两种剪枝方法。

(二)预剪枝

1. 概念

预剪枝是指在决策树生成过程中,对每个节点在划分前先进行估计,若当前节点的划分不能带来决策树泛化性能的提升,则停止划分并将当前节点标记为叶节点。

那么所谓的“决策树泛化性能”如何来判定呢?这就可以使用性能评估中的留出法,即预留一部分数据用作“验证集”以进行性能评估。

如在下面的数据集中,将其划分成两部分:一部分作为训练集用来构建决策树,一部分作为验证集用来进行决策树的剪枝。具体划分如下。
在这里插入图片描述

2.具体实例

我们使用ID3算法,即使用信息增益进行决策树构建。得到的决策树如下图所示:
在这里插入图片描述
手工计算的过程如下:
在这里插入图片描述
因为色泽和脐部的信息增益值最大,所以从这两个中随机挑选一个,这里选择脐部来对数据集进行划分,这会产生三个分支,如下图所示:
在这里插入图片描述

然而我们是否应该进行这次划分呢?

评判依据就是对划分前后的泛化性能进行估计:划分前后的泛华性能是否有提升,也就是如果划分后泛华性能有提升,则划分;否则,不划分。

下面来看看是否要用脐部进行划分,划分前:所有样本都在根节点,把该结点标记为叶结点,其类别标记为训练集中样本数量最多的类别,因此标记为好瓜,然后用验证集对其性能评估,可以看出样本{4,5,8}被正确分类,其他被错误分类,因此精度为43.9%。划分后:划分后的的决策树为:
在这里插入图片描述
则验证集在这颗决策树上的精度为:5/7 = 71.4% > 42.9%。泛化性能得到了提升,因此,用“脐部”进行划分。

接下来,决策树算法对结点 (2) 进行划分,再次使用信息增益挑选出值最大的那个特征,这里我就不算了,计算方法和上面类似,信息增益值最大的那个特征是“色泽”,则使用“色泽”划分后决策树为:
在这里插入图片描述
但到底该不该划分这个结点,还是要用验证集进行计算,可以看到划分后,精度为:4/7=0.571<0.714,因此,预剪枝策略将禁止划分结点 (2) 。对于结点 (3) 最优的属性为“根蒂”,划分后验证集精度仍为71.4%,因此这个划分不能提升验证集精度,所以预剪枝将禁止结点 (3) 划分。对于结点 (4) ,其所含训练样本已属于同一类,所以不再进行划分。

所以基于预剪枝策略生成的最终的决策树为:
在这里插入图片描述
对比未剪枝的决策树和经过预剪枝的决策树可以看出:预剪枝使得决策树的很多分支都没有“展开”,这不仅降低了过拟合的风险,还显著减少了决策树的训练时间开销和测试时间开销。但是,另一方面,因为预剪枝是基于“贪心”的,所以,虽然当前划分不能提升泛化性能,但是基于该划分的后续划分却有可能导致性能提升,因此预剪枝决策树有可能带来欠拟合的风险。

3. 伪代码

在这里插入图片描述

(三)后剪枝

1 概念

后剪枝是先从训练集生成一颗完整的决策树,然后自底向上地对非叶节点进行考察,若将该节点对应的子树完全替换为叶节点能带来决策树繁花性的提升,则将该子树替换为叶节点。

2 具体实例

首先生成一棵完整决策树:
在这里插入图片描述
后剪枝算法首先考察上图中的结点 (6),若将以其为根节点的子树删除,即相当于把结点 (6) 替换为叶结点,替换后的叶结点包括编号为{7,15}的训练样本,因此把该叶结点标记为“好瓜”(因为这里正负样本数量相等,所以随便标记一个类别),因此此时的决策树在验证集上的精度为57.1%(为剪枝的决策树为42.9%),所以后剪枝策略决定剪枝,剪枝后的决策树如下图所示:
在这里插入图片描述
接着考察结点 5,同样的操作,把以其为根节点的子树替换为叶结点,替换后的叶结点包含编号为{6,7,15}的训练样本,根据“多数原则”把该叶结点标记为“好瓜”,测试的决策树精度认仍为57.1%,所以不进行剪枝。

考察结点 2 ,和上述操作一样,不多说了,叶结点包含编号为{1,2,3,14}的训练样本,标记为“好瓜”,此时决策树在验证集上的精度为71.4%,因此,后剪枝策略决定剪枝。剪枝后的决策树为:
在这里插入图片描述
接着考察结点 3 ,同样的操作,剪枝后的决策树在验证集上的精度为71.4%,没有提升,因此不剪枝;对于结点 1 ,剪枝后的决策树的精度为42.9%,精度下降,因此也不剪枝。

因此,基于后剪枝策略生成的最终的决策树如上图所示,其在验证集上的精度为71.4%。

3.伪代码

在这里插入图片描述

4.总结

对比预剪枝和后剪枝,能够发现,后剪枝决策树通常比预剪枝决策树保留了更多的分支,一般情形下,后剪枝决策树的欠拟合风险小,泛华性能往往也要优于预剪枝决策树。但后剪枝过程是在构建完全决策树之后进行的,并且要自底向上的对树中的所有非叶结点进行逐一考察,因此其训练时间开销要比未剪枝决策树和预剪枝决策树都大得多。

(四)sklearn中的剪枝处理

1.展示

sklearn中现在能做的是预剪枝,就是设置Classifier或者Regression里的参数max_depth, min_samples_split, min_samples_leaf。

后剪枝的确是在sklearn中做不到的。

我们看一下具体的例子。首先构造数据:


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

X,y = datasets.make_moons(noise=0.25,random_state=666)

plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
然后加载描绘分类边界的函数:


def plot_decision_boundary(model, axis): # model是模型,axis是范围
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
        np.linspace(axis[2], axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1),
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]

    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
    
    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)

下面开始进行决策树的构建和比较。首先创建决策树dt_clf1,在这里不限定决策树的最大深度,则决策树会一直向下划分,直到每一个节点的基尼系数为0为止。


from sklearn.tree import DecisionTreeClassifier

# 如果在构建时不传参数,则默认是使用基尼系数进行特征划分
# 不限定max_depth,则决策树会一直向下划分,直到每一个节点的基尼系数为0为止
dt_clf1 = DecisionTreeClassifier()
dt_clf1.fit(X,y)
    
plot_decision_boundary(dt_clf1, axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
我们可以看到,决策边界的形状非常不规则,这就是典型的过拟合现象。在图中用红色箭头标出的部分,就是为了迁就现有的数据集样本,才会学习成这个样子的。

下面我们重新生成一个决策树dt_clf2,这里限制了决策树的深度为2,也就是划分到第二层就停止了。
在这里插入图片描述
我们还可以设置最小样本划分,即对于一个节点来说,至少有多少个样本数据,才会对这个节点拆分下去。数值越高 越不容易过拟合,太高的话容易欠拟合。


dt_clf3 = DecisionTreeClassifier(min_samples_split=10)
dt_clf3.fit(X,y)

plot_decision_boundary(dt_clf3, axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
还可以设置最小样本叶节点,对于一个叶子节点来说,至少有几个样本。越少越容易过拟合。


dt_clf4 = DecisionTreeClassifier(min_samples_leaf=6)
dt_clf4.fit(X,y)

plot_decision_boundary(dt_clf4, axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
还可以设置最大叶子结点,即对于一个叶子节点来说,最多有几个叶子结点,叶子越多,树越复杂,越容易过拟合。


dt_clf5 = DecisionTreeClassifier(max_leaf_nodes=4)
dt_clf5.fit(X,y)

plot_decision_boundary(dt_clf5, axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(X[y==0,0],X[y==0,1])
plt.scatter(X[y==1,0],X[y==1,1])
plt.show()

在这里插入图片描述
在实际使用这些参数时需要注意要避免欠拟合,其次这些参数之间可以互相组合,可以使用网格搜索的方式看哪些参数可以得到更好的结果。

2.总结

sklearn.tree:提供了决策树模型,用于解决分类和回归问题。

class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, splitter=’best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, class_weight=None, presort=False)[source]

参数说明如下:

  • criterion:特征选择标准,可选参数,默认是gini,可以设置为entropy。gini是基尼不纯度,是将来自集合的某种结果随机应用于某一数据项的预期误差率,是一种基于统计的思想。entropy是香农熵,也就是上篇文章讲过的内容,是一种基于信息论的思想。Sklearn把gini设为默认参数,应该也是做了相应的斟酌的,精度也许更高些?ID3算法使用的是entropy,CART算法使用的则是gini。

  • splitter:特征划分点选择标准,可选参数,默认是best,可以设置为random。每个结点的选择策略。best参数是根据算法选择最佳的切分特征,例如gini、entropy。random随机的在部分划分点中找局部最优的划分点。默认的”best”适合样本量不大的时候,而如果样本数据量非常大,此时决策树构建推荐”random”。

  • max_features:划分时考虑的最大特征数,可选参数,默认是None。寻找最佳切分时考虑的最大特征数(n_features为总共的特征数),有如下6种情况:

    如果max_features是整型的数,则考虑max_features个特征;

    如果max_features是浮点型的数,则考虑int(max_features * n_features)个特征;
    如果max_features设为auto,那么max_features = sqrt(n_features);
    如果max_features设为sqrt,那么max_featrues = sqrt(n_features),跟auto一样;
    如果max_features设为log2,那么max_features = log2(n_features);
    如果max_features设为None,那么max_features = n_features,也就是所有特征都用。一般来说,如果样本特征数不多,比如小于50,我们用默认的”None”就可以了,如果特征数非常多,我们可以灵活使用刚才描述的其他取值来控制划分时考虑的最大特征数,以控制决策树的生成时间。

  • max_depth:决策树最大深,可选参数,默认是None。这个参数是这是树的层数的。层数的概念就是,比如在贷款的例子中,决策树的层数是2层。如果这个参数设置为None,那么决策树在建立子树的时候不会限制子树的深度。一般来说,数据少或者特征少的时候可以不管这个值。或者如果设置了min_samples_slipt参数,那么直到少于min_smaples_split个样本为止。如果模型样本量多,特征也多的情况下,推荐限制这个最大深度,具体的取值取决于数据的分布。常用的可以取值10-100之间。

  • min_samples_split:内部节点再划分所需最小样本数,可选参数,默认是2。这个值限制了子树继续划分的条件。如果min_samples_split为整数,那么在切分内部结点的时候,min_samples_split作为最小的样本数,也就是说,如果样本已经少于min_samples_split个样本,则停止继续切分。如果min_samples_split为浮点数,那么min_samples_split就是一个百分比,ceil(min_samples_split * n_samples),数是向上取整的。如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。

  • min_weight_fraction_leaf:叶子节点最小的样本权重和,可选参数,默认是0。这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。

  • max_leaf_nodes:最大叶子节点数,可选参数,默认是None。通过限制最大叶子节点数,可以防止过拟合。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制,具体的值可以通过交叉验证得到。

  • class_weight:类别权重,可选参数,默认是None,也可以字典、字典列表、balanced。指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多,导致训练的决策树过于偏向这些类别。类别的权重可以通过{class_label:weight}这样的格式给出,这里可以自己指定各个样本的权重,或者用balanced,如果使用balanced,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。当然,如果你的样本类别分布没有明显的偏倚,则可以不管这个参数,选择默认的None。

  • random_state:可选参数,默认是None。随机数种子。如果是证书,那么random_state会作为随机数生成器的随机数种子。随机数种子,如果没有设置随机数,随机出来的数与当前系统时间有关,每个时刻都是不同的。如果设置了随机数种子,那么相同随机数种子,不同时刻产生的随机数也是相同的。如果是RandomState instance,那么random_state是随机数生成器。如果为None,则随机数生成器使用np.random。

  • min_impurity_split:节点划分最小不纯度,可选参数,默认是1e-7。这是个阈值,这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值,则该节点不再生成子节点。即为叶子节点

  • presort:数据是否预排序,可选参数,默认为False,这个值是布尔值,默认是False不排序。一般来说,如果样本量少或者限制了一个深度很小的决策树,设置为true可以让划分点选择更加快,决策树建立的更加快。如果样本量太大的话,反而没有什么好处。问题是样本量少的时候,我速度本来就不慢。所以这个值一般懒得理它就可以了。

除了这些参数要注意以外,其他在调参时的注意点有:

  1. 当样本数量少但是样本特征非常多的时候,决策树很容易过拟合,一般来说,样本数比特征数多一些会比较容易建立健壮的模型如果样本数量少但是样本特征非常多,在拟合决策树模型前,推荐先做维度规约,比如主成分分析(PCA),特征选择(Losso)或者独立成分分析(ICA)。这样特征的维度会大大减小。再来拟合决策树模型效果会好。
  2. 推荐多用决策树的可视化,同时先限制决策树的深度,这样可以先观察下生成的决策树里数据的初步拟合情况,然后再决定是否要增加深度。
  3. 在训练模型时,注意观察样本的类别情况(主要指分类树),如果类别分布非常不均匀,就要考虑用class_weight来限制模型过于偏向样本多的类别。
  4. 决策树的数组使用的是numpy的float32类型,如果训练数据不是这样的格式,算法会先做copy再运行。
  5. 如果输入的样本矩阵是稀疏的,推荐在拟合前调用csc_matrix稀疏化,在预测前调用csr_matrix稀疏化。

sklearn.tree.DecisionTreeClassifier()提供了一些方法供我们使用,如下图所示:
在这里插入图片描述

六 分类与回归树CART

(一)概念介绍

1. CART算法

CART算法:Classification And Regression Tree。顾名思义,CART算法既可以用于创建分类树(Classification Tree),也可以用于创建回归树(Regression Tree)、模型树(Model Tree),两者在建树的过程稍有差异。既可以解决分类问题,也可以解决回归问题。根据某一个维度d和某一个阈值v进行二分,得到的决策树是二叉树。

ID3中使用了信息增益选择特征,增益大优先选择。C4.5中,采用信息增益比选择特征,减少因特征值多导致信息增益大的问题。CART分类树算法使用基尼系数来代替信息增益比,基尼系数代表了模型的不纯度,基尼系数越小,不纯度越低,特征越好。这和信息增益(比)相反。

2. 回顾基尼系数

回顾一下之前学习的关于基尼系数的知识:

基尼指数(基尼不纯度)= 样本被选中的概率 * 样本被分错的概率 有如下公式:
G i n i ( p ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(p)=\sum_{k=1}^Kp_k(1-p_k)=1-\sum_{k=1}^Kp_k^2 Gini(p)=k=1Kpk(1pk)=1k=1Kpk2
对上述公式进行说明:

  • p k p_k pk表示选中的样本属于k类别的概率,则这个样本被分错的概率是 ( 1 − p k ) (1-p_k) (1pk)
  • 因为样本集合中有k个类别,一个随机选中的样本可以属于这k个类别中的任意一个,因而累加所有的k个类别。
  • 当二分类时, G = 2 p ( 1 − p ) G=2p(1-p) G=2p(1p)

样本集合D的基尼系数:假设集合中有K个类别,每个类别的概率是 ∣ C k ∣ ∣ D ∣ \frac {|C_k|}{|D|} DCk,其中 ∣ C k ∣ |C_k| Ck表示类别k的样本个数, ∣ D ∣ {|D|} D表示样本总数,则:
G i n i ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D)=1-\sum_{k=1}^K(\frac {|C_k|}{|D|})^2 Gini(D)=1k=1K(DCk)2

3 .基尼系数和熵模型的比较

比较基尼系数和熵模型的表达式,二次运算比对数简单很多。尤其是二分类问题,更加简单。

和熵模型的度量方式比,基尼系数对应的误差有多大呢?对于二类分类,基尼系数和熵之半的曲线如下:
在这里插入图片描述
基尼系数和熵之半的曲线非常接近,仅在45度角附近误差稍大。因此,基尼系数可以作为熵模型的一个近似替代。

CART分类树算法每次仅对某个特征的值进行二分,而不是多分,这样CART分类树算法建立起来的是二叉树,而不是多叉树。

(二)CART作为分类树

CART作为分类树时,特征属性可以是连续类型也可以是离散类型,但观察属性(即标签属性或者分类属性)必须是离散类型。

1. 对离散特征和连续特征的处理

1.1 离散特征
CART分类树算法对离散值的处理,采用的思路:不停的二分离散特征。

在ID3、C4.5,特征A被选取建立决策树节点,如果它有3个类别A1,A2,A3,我们会在决策树上建立一个三叉点,这样决策树是多叉树。

CART采用的是不停的二分。会考虑把特征A分成{A1}和{A2,A3}、{A2}和{A1,A3}、{A3}和{A1,A2}三种情况,找到基尼系数最小的组合。

比如{A2}和{A1,A3},然后建立二叉树节点,一个节点是A2对应的样本,另一个节点是{A1,A3}对应的样本。**由于这次没有把特征A的取值完全分开,各分支下的子数据集必须依旧包含该特征,该连续特征在接下来的树分支过程中可能依旧起着决定性作用。后面还有机会对子节点继续选择特征A划分A1和A3。这和ID3、C4.5不同,在ID3或C4.5的一颗子树中,离散特征只会参与一次节点的建立。

1.2 连续特征
CART分类树算法对连续值的处理,思想和C4.5相同,都是将连续的特征离散化。唯一区别在选择划分点时,C4.5是信息增益比,CART是基尼系数。

具体思路:m个样本的连续特征A有m个,从小到大排列 a 1 , a 2 , … … , a m a_1,a_2,……,a_m a1,a2,,am,则CART取相邻两样本值的平均数做划分点,一共取m-1个,其中第i个划分点Ti表示为: T i = ( a i + a i + 1 ) / 2 T_i=(a_i+a_i+1)/2 Ti=(ai+ai+1)/2。分别计算以这m-1个点作为二元分类点时的基尼系数。选择基尼系数最小的点为该连续特征的二元离散分类点。比如取到的基尼系数最小的点为 a t a_t at,则小于 a t a_t at的值为类别1,大于 a t a_t at的值为类别2,这样就做到了连续特征的离散化。另外,对于连续属性先进行排序(升序),只有在决策属性(即分类发生了变化)发生改变的地方才需要切开,这可以显著减少运算量。

注意的是,与ID3、C4.5处理离散属性不同的是,如果当前节点为连续属性,则该属性在后面还可以参与子节点的产生选择过程。

2. CART分类树算法流程

CART分类树建立算法流程,之所以加上建立,是因为CART分类树算法有剪枝。


算法从根节点开始,用训练集递归建立CART分类树。
输入:训练集D,基尼系数的阈值,样本个数阈值。
输出:决策树T。
1)对于当前节点的数据集为D,如果样本个数小于阈值或没有特征,则返回决策子树,当前节点停止递归。
2)计算样本集D的基尼系数,如果基尼系数小于阈值,则返回决策树子树,当前节点停止递归。
3)计算当前节点现有的各个特征的各个特征值对数据集D的基尼系数。
4)在计算出来的各个特征的各个特征值对数据集D的基尼系数中,选择基尼系数最小的特征A和对应的特征值a。根据这个最优特征和最优特征值,把数据集划分成两部分D1和D2,同时建立当前节点的左右节点,做节点的数据集D为D1,右节点的数据集D为D2。
5)对左右的子节点递归的调用1-4步,生成决策树。

对生成的决策树做预测的时候,假如测试集里的样本A落到了某个叶子节点,而节点里有多个训练样本。则对于A的类别预测采用的是这个叶子节点里概率最大的类别。

(三) CART作为回归树

1. 回归问题思路

当数据拥有众多特征并且特征之间关系十分复杂时,构建全局模型的想法就显得太难了,也略显笨拙。而且,实际生活中很多问题都是非线性的,不可能使用全局线性模型来拟合任何数据。一种可行的方法是将数据集切分成很多份易建模的数据,然后利用线性回归技术来建模。如果首次切分后仍然难以拟合线性模型就继续切分。在这种切分方式下,树结构和回归法就相当有用。

回归树的目标是连续数据,树被用来预测目标变量的值是多少。

CART回归树和CART分类树的建立类似,区别在于样本的输出,如果样本输出是离散值,这是分类树;样本输出是连续值,这是回归树。分类树的输出是样本的类别,回归树的输出是一个实数。

并且分类树采用基尼系数的大小度量特征各个划分点的优劣。而回归树采用最小化均方差和进行最优划分特征的选择,对于划分特征A,划分点s两边的数据集D1和D2,求出使D1和D2各自集合的均方差最小,同时D1和D2的均方差之和最小,对应的特征和特征值划分点。。

和方差表达式为:
在这里插入图片描述
其中:C1为D1数据集的样本输出均值,C2为D2数据集的样本输出均值。

这很好理解,以预测年龄为例子:被预测出错的人数越多,错的越离谱,均方差就越大,通过最小化均方差能够找到最靠谱的分枝依据。分枝直到每个叶子节点上人的年龄都唯一(这太难了)或者达到预设的终止条件(如叶子个数上限),若最终叶子节点上人的年龄不唯一,则以该节点上所有人的平均年龄做为该叶子节点的预测年龄。

对于决策树建立后做预测的方式,CART分类树采用该叶子节点里概率最大的类别作为当前节点的预测类别。回归树输出不是类别,采用叶子节点的均值或者中位数来预测输出结果。

2. CART剪枝

由于决策树算法很容易对训练集过拟合,而导致泛化能力差,为了解决这个问题,我们需要对CART树进行剪枝,来增加决策树的泛化能力。CART采用的办法是后剪枝法。

CART树的剪枝算法可以概括为两步:

  1. 从原始决策树生成各种剪枝效果的决策树 。
  2. 用交叉验证来检验剪枝后的预测能力,选择泛化预测能力最好的剪枝后的树作为最终的CART树。

那么按照步骤来进行,我们可以分析如下:

对于位于节点t的任意一颗子树Tt,如果没有剪枝,它的损失函数是 C a ( T t ) = C ( T t ) + a ∣ T t ∣ Ca(T_t)=C(T_t)+a|T_t| Ca(Tt)=C(Tt)+aTt 。其中是a正则化因子, ∣ T t ∣ |T_t| Tt是子树 T t T_t Tt的结点的个数, C ( T t ) C(T_t) C(Tt)为训练数据的预测误差。

如果将其剪掉,仅仅保留根节点,则损失是 C a ( T t ) = C ( T t ) + a Ca(T_t)=C(T_t)+a Ca(Tt)=C(Tt)+a

那么如何才能确定剪枝呢?

可以假设当剪枝前和剪枝后的损失函数相同,即T这个树的结点数更少,可以对Tt这个子树进行剪枝,直接将其变为树T。我们可以得到等式如下:
C ( T t ) + a ∣ T t ∣ = C ( T t ) + a C(T_t)+a|T_t|=C(T_t)+a C(Tt)+aTt=C(Tt)+a
解得:
a = C ( T ) − C ( T t ) ∣ T t ∣ − 1 a=\frac {C(T)-C(T_t)}{|T_t|-1} a=Tt1C(T)C(Tt)
那么如何选择出最优的CART分类树呢?我们可以采用交叉验证策略,上面我们计算出了每个子树是否剪枝的阈值α,如果我们把所有的节点是否剪枝的值α都计算出来,然后分别针对不同的α所对应的剪枝后的最优子树做交叉验证。这样就可以选择一个最好的α,有了这个α,我们就可以用对应的最优子树作为最终结果。

(四)代码


import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split

boston = datasets.load_boston()
X = boston.data
y = boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

from sklearn.tree import DecisionTreeRegressor

dt_reg = DecisionTreeRegressor()
dt_reg.fit(X_train, y_train)

dt_reg.score(X_test, y_test)

不进行调参,决策树回归器得到的结果0.5974242901718485,其R方值是很低的。但是对于训练数据来说,R方值是100%,显然过拟合了。

(五)总结

CART算法既可以做分类,又可以做回归。在分类和回归时,其算法流程大致相同,但是其特征划分、输出预测结果等步骤是不同的,大家要多加对比和注意。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值