决策树

本文介绍了决策树模型,包括其定义、与if-then规则的关系、条件概率分布以及学习过程。决策树学习通过损失函数最小化策略选择最优决策树,特征选择通常使用信息增益或信息增益比准则。ID3、C4.5和CART算法分别用于决策树的生成和剪枝,以避免过拟合。CART算法尤其适用于分类和回归任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

决策树模型与学习

1. 决策树模型

决策树定义:分类决策树是一种描述对实例进行分类的树型结构。决策树由结点和有向边组成。结点有两种类型:内部结点和叶结点。内部结点表示一个特征或属性,叶子结点表示一个类。

用决策树分类,从根结点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其他子结点;这时,每一个子结点对应着该特征的一个取值,如此递归地对实例进行分配,直至达到叶结点。最后将实例分到叶结点的类中。

如图所示,是一个决策树的示意图:

 

1.2决策树与if-then规则

可以将决策树看成是if-then规则的集合.将决策树转换成if-then规则的过程是这样的:由决策树的根结点到叶结点的每一条路径构建一条规则;路径上的内部结点的特征对应着规则的条件,而叶结点的类对应着规则的结论。决策树的路径或者其对应的if-then规则集合具有一个重要的性质:互斥并且完备,即每个实例都被一条路径或者一条规则所覆盖,而且只被一条路径或者一条规则所覆盖,这里所谓覆盖是指实例的特征与路径上的特征一致或实例满足规则的条件

决策树与条件概率分布

决策树还表示给定特征条件下类的条件概率分布,这一条件概率分布定义在特征空间的一个划分上。将特征空间划分为互不相交的单元或者区域,并在每个单元定义一个类的概率分布就构成了一个条件概率分布。决策树的一条路径对应于划分中的一个单元。决策树所表示的条件概率分布由各个单元给定条件下类的条件概率分布组成。假设X为表示特征的随机变量,Y为表示类的随机变量,那么这个条件概率分布可以表示为p(Y|X)。X取值于给定划分下单元的集合,Y取值于类的集合。各叶结点上的条件概率往往偏向某一个类,即属于某一类的概率较大。决策树分类时将该结点的实例强行分到条件概率比较大的那一类去。

 

1.3决策树学习

假设给定数据训练数据集

D=\left \{ (x_{1},y_{1}),(x_{2},y_{2}),...,(x_{N},y_{N}) \right \}

其中,x_{i}=(x_{i}^{(1)},x_{i}^{(2)},...,x_{i}^{(n)})^{\mathrm{T}}为输入实例(特征向量),n为特征个数,y_{i}\in \left \{ 1,2,...,K \right \}为类标记,i=1,2,...,N,N为样本容量。学习的目标是根据给定的训练数据集建一个决策树模型,使其能够对实例进行正确分类。

决策树本质上是从训练数据集中归纳出分类规则,能对训练数据进行正确分类的决策树,它们可能有多个,可能一个也没有。我们需要的是一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。从另一角度来看,决策树学习是由训练数据集估计条件概率模型。我们选择的条件概率模型应该不仅对训练数据有很好的拟合效果,而且对未知数据有很好的预测能力。

决策树学习用损失函数表示目标,决策树的损失函数通常是正则化的极大似然函数,决策树学习的策略是以损失函数为目标函数的最小化。当损失函数确定后,学习问题就变为了损失函数的条件下选择最优决策树的问题。在所有可能的决策树中选取最优决策树是NP完全问题(NP中的某些问题的复杂性与整个类的复杂性相关联.这些问题中任何一个如果存在多项式时间的算法,那么所有NP问题都是多项式时间可解的.这些问题被称为NP-完全问题),所以现实中决策树学习算法通常采用启发式算法,近似求解这一最优化问题,这样获得的决策树是次最优的。

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

1.4 决策树特点介绍

优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以理解不相关特征数据。

缺点:可能会产生过度匹配问题

适用范围:数值型和标称型

问题1:评估每个特征,将原始数据集划分为几个数据子集。

创建分支的伪代码函数createBranch()如下所示:

检测数据集中的每个子项是否属于同一分类:

       if so return  类标签

       else

             寻找划分数据集的最好特征

             划分数据集

             创建分支节点

                    for 每个划分的子集

                          调用函数createBranch并增加返回结果到分支节点中

             return 分支节点

决策树学习算法包含特征选择,决策树的生成和决策树的剪枝过程。由于决策树表示为一个条件概率分布,所以深浅不同的决策树对应着不同复杂程度的概率模型。决策树的生成对应于模型的局部选择,决策树的剪枝对应于模型的全局选择。决策树的生成考虑局部最优;相对地,决策树的剪枝则考虑全局最优。

2. 特征选择

特征选择在于选取那些对训练数据具有分类能力的特征,这样可以提高决策树的学习的效率。如果利用一个特征的进行分类的结果与随机分类的结果没有很大差别,则这个特征是没有分类能力的。经验上扔掉这样的特征对决策树学习的精度的影响不大。通常特征选择的准则是信息增益或者信息增益比。

例1.表1是一个由15个样本组成的贷款申请训练数据。数据包括贷款申请人的4个特征(属性):第一个特征年龄,有三个可能值:青年、中年、老年;第二个特征是有工作,有2个可能值:是、否;第三个特征是有自己的房子,有2个可能值:是、否;第四个特征是信贷情况,有3个可能值:非常好、好,一般。表的最后一列是类别,是否同意贷款,取2个值:是,否。

                                                                            表1 贷款申请样本数据表

ID年龄有工作有自己的房子信贷情况类别
1青年一般
2青年
3青年
4青年一般
5青年一般
6中年一般
7中年
8中年
9中年非常好
10中年非常好
11老年非常好
12老年
13老年
14老年非常好
15老年一般

图(a)和图(b)表示从数据学习到的两个可能的决策树,图(a)表示年龄特征,有三个子结点,图(b)表示有工作的特征,有2个子结点。问题是:我们选择哪个特征更加好些。也就是说我们按照什么样的准则来分割子集,使得各个子集在当前条件下有最好的分类,那么就更应该选择这个特征。信息增益就是一个非常好的准则。

2.1 信息增益

在信息论与概率统计中,熵是表示随机变量不确定性的度量。设X是一个取有限个值的离散随机变量,其概率分布为

                                                         p(X=x_{i})=p_{i},\quad i=1,2,...n     

则随机变量X的熵的定义为

                                                          H(X)=-\sum_{i=1}^{n}log_{2}p_{i} \quad \quad \quad (2.1) 

在式(2.1)中,若P_{i}=0,则定义0log_{2}0=0,通常,式(2.1)中的对数以2为底或以e为底(自然对数),这时熵的单位分别称作比特(bit)或者纳特(nat)。有定义可知,熵只依赖于X的分布,而与X的取值无关,所以也可将X的熵记作H(p),即

                                                          H(p)=-\sum_{i=1}^{n}p_{i}log_{2}p_{i} \quad \quad \quad (2.2)

\rightarrow熵越大,随机变量的不确定性就越大。从定义可验证

                                                           0\leq H(p)\leq log_{2}n \quad \quad \quad(2.3)

当随机变量只取两个值,例如1,0时,即X的分布为

                                                           p(X=1)=p,\quad p(X=0)=1-p,\quad 0\leq p\leq 1

\Rightarrow熵为                                               H(p)=-plog_{2}p-(1-p)log_{2}(1-p) \quad \quad \quad (2.4) 

此时,熵H(P)随概率p变化的曲线如图所示(单位为比特,横坐标表示p,纵坐标表示H(P)

    当p=0或者p=1时,H(P)=0,随机变量完全没有不确定性,当p=0.5时,H(P)=1,熵取最大,此时随机变量的不确定性最大。

    设有随机变量p(X,Y),其联合概率分布为

                                          p(X=x_{i},Y=y_{i})=p_{ij},\quad \quad i=1,2,...,n;\quad \quad j=1,2,...,m

条件熵H(Y|X)表示在已知随机变量X的条件下随机变量Y的不确定性的,随机变量X给定的条件下随机变量Y的条件熵H(Y|X),定义为X给定条件下Y的条件概率分布的熵对X的数学期望

                                          H(Y|X)=\sum_{i=1}^{n}p_{i}H(Y|X=x_{i}) \quad \quad \quad (2.5)

这里,p_{i}=p(X=x_{i}),\quad i=1,2,...n.

当熵和条件熵中的概率由数据统计(特别是极大似然估计)得到时,所对应的熵与条件熵分别称为经验熵和经验条件熵,此时,如果有0概率,令0log_{2}0=0

信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度(即分类前的信息熵减去分类后的信息熵)。

定义(信息增益)特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差,即

                                           g(D,A)=H(D)-H(D|A) \quad \quad \quad(2.6)

一般地,熵H(Y)与条件熵H(Y|X)之差称为互信息,即H(Y)-H(Y|X)=互信息。决策树学习中的信息增益等价于训练数据集中类与特征的互信息。

\rightarrow给定训练数据集D和特征A,信息增益表示由于特征A而使得对数据集D的分类的不确定性减少的程度。显然,对于数据集D而言,信息增益依赖于特征,不同的特征往往具有不同的信息增益,信息增益大的特征具有更加强的分类能力。

根据信息增益准则的特征选择方法是:对于训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征。

 

设训练数据集为D,\left | D \right |表示其样本容量,即样本个数,设有K个类的C_{k},\quad k=1,2,...,K\left | C_{K} \right |为属于类C_{k}的样本个数,\sum_{k=1}^{K}\left | C_{k} \right |=\left | D \right |。设特征A有n个不同的取值\left \{ a_{1},a_{2},...,a_{n} \right \},根据特征A的取值将D划分为n个子集D_{1},D_{2},...,D_{n},\left | D_{i} \right |D_{i}的样本个数,\sum_{i=1}^{n}\left | D_{i} \right |=\left | D \right |。记子集D_{i}中属于类C_{k}的样本的集合为D_{ik},即D_{ik}=D_{i}\cap C_{k},\left | D_{ik} \right |D_{ik}的样本个数。于是信息增益的算法如下:

信息增益算法

输入:训练数据集D和特征A;

输出:特征A对训练数据集D的信息增益g(D,A)

(1)计算数据集D的经验熵H(D)

                                                         H(D)=-\sum_{k=1}^{K}\frac{\left | C_{k} \right |}{\left | D \right |}log_{2}\frac{\left | C_{k} \right |}{\left | D \right |}

(2)计算特征A对数据集D的经验条件熵H(D|A)

                                 H(D|A)=\sum_{i=1}^{n}\frac{\left | D_{i} \right |}{\left | D \right |}H(D_{i})=-\sum_{i=1}^{n}\frac{\left | D_{i} \right |}{\left | D \right |}\sum_{k=1}^{K}\frac{\left | D_{ik} \right |}{\left | D_{i} \right |}log_{2} \frac{\left |D_{ik} \right |}{\left | D_{i}\right |}

(3)计算信息增益

                                                         g(D,A)=H(D)-H(D|A)

例1. 对于表1 所给的训练数据集,根据信息增益准则选择最优特征。

解答过程如下:

2.2 信息增益比

使用信息增益来作为划分训练数据集的特征,存在偏向于选择取值较多的特征的问题。使用信息增益比可以对这一问题进行校正,这是特征选择的另一准则。

信息增益比:信息增益g(D,A)与训练数据集D关于特征A的熵H(D)之比,即

                                                            g_{R}(D,A)=\frac{g(D,A)}{H_{A}(D)}

其中H_{A}(D)=-\sum_{i=1}^{n}\frac{\left | D_{i} \right |}{\left | D \right |} log_{2}\frac{\left | D_{i} \right |}{\left | D \right |},n是特征A取值的个数。

 

3. 决策树的生成

ID3算法:从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点,再对子结点递归地调用以上方法,构建决策树;直到所有的特征的信息增益均小或者没有特征可以选择为止。最后得到一个决策树。ID3相当于用极大似然法进行概率模型的选择。

算法过程:

输入:训练数据集D,特征集A,阈值\varepsilon

输出:决策树T

(1)若D中所有实例属于同一类C_{k},则T为单结点树,并将类C_{k}作为该结点的类标记,返回T;

(2)若A=\o,则T为单结点树,并将D中实例数最大的类C_{k}作为该结点的类标记,返回T;

(3)否则,按信息增益的算法计算A中各个特征的信息增益,选择信息增益最大的特征A_{g}

(4)如果A_{g}的信息增益小于阈值\varepsilon,则置T为单结点树,并将D中实例数最大的类C_{k}作为该结点的类标记,返回T

(5)否则,对A_{g}的每一可能值a_{i},依A_{g}=a_{i}将D分割为若干非空子集D_{i},将D_{i}中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;

(6)对第i个子结点,以D_{i}为训练集,以A-{A_{g}}为特征集,递归地调用步骤(1)~(5)得到子树T_{i},返回T_{i}

例2:对于例1中的训练数据集,用ID3算法构建决策树

解:结合例1的计算结果,由于特征(有自己的房子)的信息增益值最大,所以选择此特征作为根结点的特征,它将数据集划分为两个子集D_{1}(特征取值为“是”)和D_{2}(特征取值为“否”)。而D_{1}中只有同一类的样本点,所以它成为一个叶结点,结点的类标记为“是”。

D_{2}则需要从特征(年龄)、(有工作)和(信贷情况)中选择新的特征。计算各个特征的信息增益:

                                 年龄:  g(D_{2},A_{1})=H(D_2)-H(D_2|A_1)=0.918-0.667=0.251

                                 有工作:g(D_{2},A_{2})=H(D_2)-H(D_2|A_2)=0.918

                                 信贷情况: g(D_{2},A_{4})=H(D_2)-H(D_2|A_4)=0.474

选择信息增益最大的特征A_2(有工作)作为结点的特征,由于特征(有工作)有两个可能的取值,从这一结点引出两个子结点:一个对应“是”(有工作)的子结点,包含3个样本,它们属于同一类,所以这是一个叶结点,类标记为“是”;另一个是对应“否”(无工作)的子结点,包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为“否”。

这样就生成了一个决策树(包含两个特征):

  

ID3算法只有树的生成,所以该算法生成的树容易过拟合

 

2.3  C4.5的生成算法

C4.5是ID3算法的升级版,C4.5算法在生成过程中,采用了信息增益比来选择特征。

算法过程:

输入:训练数据集D,特征集A,阈值\varepsilon

输出:决策树T。

(1)如果D中所有实例属于同一类C_{k},则T为单结点树,并将类C_{k}作为该结点的类标记,返回T;

(2)若A=\o,则T为单结点树,并将D中实例数最大的类C_{k}作为该结点的类标记,返回T;

(3)否则,按信息增益比公式计算A中各个特征的信息增益比,选择信息增益比最大的特征A_{g}

(4)如果A_{g}的信息增益比小于阈值\varepsilon,则置T为单结点树,并将D中实例数最大的类C_{k}作为该结点的类标记,返回T

(5)否则,对A_{g}的每一可能值a_{i},依A_{g}=a_{i}将D分割为若干非空子集D_{i},将D_{i}中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T;

(6)对第i个子结点,以D_{i}为训练集,以A-{A_{g}}为特征集,递归地调用步骤(1)~(5)得到子树T_{i},返回T_{i}

 

2.4 决策树的剪枝

决策树对于训练数据集的分类很准确,但是对于未知数据的预测效果不是很好,容易出现过拟合现象。这是因为决策树过于考虑如何正确分类训练数据集,构造了一个相对复杂的决策树。所以需要对决策树进行简化处理(这个过程就是剪枝)。

决策树的剪枝常通过极小化决策树整体的损失函数或者是代价函数。设树T的叶结点个数为\left | T \right |,t是树T的叶结点,该叶结点有N_t个样本点,其中k类的样本点有N_{tk}个,k=1,2,...,K,\quad H_{t}(T)为叶结点t上面的经验熵,\alpha \geq 0为参数,则决策树学习的损失函数可以定义为

                                                                   C_{\alpha }(T)=\sum_{t=1}^{\left | T \right |}N_{t}H_{t}(T)+\alpha \left | T \right |\quad \quad \quad(2.4.1)         

其中经验熵为

                                                                  H_{t}(T)=-\sum_{k}^{ }\frac{N_tk}{N_t}log\frac{N_tk}{N_t} \quad \quad \quad \quad (2.4.2)

在损失函数中,将式(2.4.1)右端的第1项记作

                                                              C(T)=\sum_{t=1}^{\left | T \right |}N_{t}H_{t}(T)=-\sum_{t=1}^{\left | T \right |} \sum_{k=1}^{K}N_{tk}log_{2}\frac{N_{tk}}{N_t}\quad \quad(2.4.3)

这时有                            

                                                             C_{\alpha }(T)=C(T)+\alpha \left | T \right |\quad \quad \quad(2.4.4)

(关于式(2.4.1)和式(2.4.2)的理解,我们可以参考链接:https://blog.youkuaiyun.com/wjc1182511338/article/details/76793598

式(2.4.4)中,C(T)表示模型对训练数据的预测误差,即模型与训练数据的拟合程度,\left | T \right |表示模型复杂度,参数\alpha \geq 0控制两者之间的影响。较大的\alpha促使选择较简单的模型(树),较小的\alpha促使选择较复杂的模型(树)。\alpha =0意味着中考虑模型与训练数据的拟合程度,不考虑模型的复杂度。

剪枝,就是当\alpha值确定时,选择损失函数最小的模型,即损失函数最小的子树。当\alpha值确定时,子树越大,往往与训练数据的拟合越好,但是模型的复杂度就越高;相反,子树越小,模型的复杂度就越低,但是往往与训练数据的拟合不好,损失函数正好表示了对两者的平衡。

可以看出,决策树生成只考虑了通过提高信息增益(或者信息增益比)对训练数据进行更好的拟合。而决策树剪枝学习整体的模型。

式(2.4.1)和式(2.4.4)定义的损失函数的极小化等价于正则化的极大似然估计。所以,利用损失函数最小原则进行剪枝就是用正则化的极大似然估计进行模型选择。

(树的剪枝算法)

输入:生成算法产生的整个树T,参数\alpha

输出:修剪后的子树T_{\alpha }

(1)计算每个结点的经验熵;

(2)递归地从树的叶结点向上回缩;

设一组叶结点回缩到其父结点之前与之后的整体树分别为T_{B}T_{A},其对应的损失函数值分别是C_{\alpha }(T_{A})C_{\alpha }(T_{B}),如果

                                                                         C_{\alpha }(T_{A})\leq C_{\alpha }(T_{B})\quad \quad \quad (2.4.5)

则进行剪枝,即将父结点变为新的叶结点。

(3)返回(2),直至不能继续为止,得到损失函数最小的子树T_{\alpha }

只需要考虑两个树的损失函数的差,其计算可以在局部进行。

 

2.5  CART算法

分类和回归树(classification and regression tree,CART)模型是Breiman等人于1984年提出,是应用于广泛的决策树学习方法。CART同样有特征选择、树的生成及剪枝组成,既可以用于分类也可以用于回归。

CART算法由以下两步组成:

(1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;

(2)决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。

2.5.1 CART生成

决策树的生成就是递归的构建二叉决策树的过程,对于回归树用平均误差最小化准则,对分类树用基尼系数最小化准则,进行特征选择,生成二叉树。

1.回归树的生成

假设X与Y分别为输入和输出变量,并且Y为连续变量,给定训练数据集

                                                                D={(x_1,y_1),(x_2,y_2),....,(x_N,y_N)} \quad \quad \quad (2.5.1)

考虑如何生成回归树。

一个回归树对应着输入空间的一个划分以及划分的单元上面的输入值。假设已将输入空间划分为M个单元R_1,R_2,...,R_M,并且在每个单元R_m上的有一个固定的输出值c_m,于是回归树模型可表示为

                                                                 f(x)=\sum_{m=1}^{M}c_mI(x\in R_{m}) \quad \quad \quad(2.5.2)

当输入空间的划分确定时,可以用平方误差   \sum_{x_i\in R_{m}}(y_{i}-f(x_{i}))^2来表示回归树对训练数据的预测误差,用平方误差最小的准则求解每个单元上的最优输出值。可知,单元R_m上的c_m的最优值\widehat{c}_mR_m上的所有输入实例 x_{i} 对应的输出 y_{i} 的均值,即

                                                                  \widehat{c}_{m}=ave(y_{i}|x_{i}\in R_m) \quad \quad \quad (2.5.3)

问题是怎样对输入空间进行划分。这里采用启发式的方法,选择第j个变量x^{(j)}和它取的值s,作为切分变量和切分点并定义两个区域:

                                                      R_{1}(j,s)=\left \{ x|x^{(j)}\leq s \right \}   和  R_{2}(j,s)=\left \{ x|x^{(j)}> s \right \}

然后寻找最优切分变量j和最优切分点s。具体地,求解。

                                                     \min_{j,s}\left [ \min_{c_1} \sum_{x_{i}\in R_{1} (j,s) }(y_{i}-c_1)^2 +\min_{c_{2}} \sum_{x_{i}\in R_{2} (j,s) }(y_{i}-c_{2})^2 \right ] \quad \quad \quad (2.5.4)

对固定输入向量j 可以找到最优切分点s。

                                                     \widehat{c}_{1}=ave(y_{i}|x_{i}\in R_1(j,s))   和   \widehat{c}_{2}=ave(y_{i}|x_{i}\in R_2(j,s)) \quad \quad \quad (2.5.5)

变量所有的输入变量,找到最优的切分变量j ,构成一个对(j,s)。依此将输入空间划分为两个区域。接着,对每个区域重复上述划分过程,直到满足停止条件为止。对每个区域重复上述划分过程,直到满足停止条件为止。这样就生成一棵回归树。这样的回归树通常称为最小二乘回归树,现将算法叙述如下:
   (最小二乘回归树生成算法)

输入:训练数据集D;

输出:回归树f(x).

在训练数据集所在的输入空间中,递归地将每个区域划分为两个子区域并决定每个子区域上面的输出值,构建二叉决策树:

(1)选择最优切分变量j与切分点s,求解

                                           \min_{j,s}\left [ \min_{c_1} \sum_{x_{i}\in R_{1} (j,s) }(y_{i}-c_1)^2 +\min_{c_{2}} \sum_{x_{i}\in R_{2} (j,s) }(y_{i}-c_{2})^2 \right ] \quad \quad

遍历变量j,对固定的切分变量j 扫描切分点s,选择使式(2.5.4)达到最小值的对(j,s)

(2)用选定的对(j,s)划分区域并决定相应的输出值:
 

                                        R_{1}(j,s)=\left \{ x|x^{(j)}\leq s \right \}R_{2}(j,s)=\left \{ x|x^{(j)}> s \right \}

                                             \widehat{c}_{m}=\frac{1}{N_{m}}\sum_{x_{i}\in R_{m}(j,s)} y_{i} ,x\in R_{m},m=1,2

(3)继续对两个子区域调用步骤(1)和(2),直至满足停止条件。

(4)将输入空间划分为M个区域R_{1},R_{2},...,R_{M},生成决策树:

                                               f(x)=\sum_{m=1}^{M}\widehat{c}_mI(x\in R_{m})

2. 分类树的生成

分类树用基尼系数选择最优特征,同时决定该特征的最优二值切分点。

基尼系数:分类问题中,假设有K个类,样本点属于第k类的概率为p_{k},则概率分布的基尼指数定义为

                                                Gini(p)=\sum_{k=1}^{K}p_{k}(1-p_{k})=1- \sum_{k=1}^{K}p_{k}^{2} \quad \quad (2.5.6)

对于二分类问题,若样本点属于第1个类的概率是p,则概率分布的基尼指数为

                                                Gini(p)=2p(1-p) \quad \quad (2.5.7)  

对于给定的样本集合D,其基尼系数为

                                                Gini(D)=1-\sum_{k=1}^{K}\left (\frac{\left | C_{k} \right |}{\left | D \right |} \right )^{2} \quad \quad (2.5.8)

这里,C_{k}是D中属于第k类的样本子集,K是类的个数。

如果样本集合D根据特征A是否取某一可能值a被分割成D_{1}D_{2}两部分,即

                                                D_{1}=\left \{(x,y)\in D|A(x)=a \right \},\quad D_2=D-D_1 \quad \quad(2.5.9)

则在特征A的条件下,集合D的基尼指数定义为

                                                Gini(D,A)=\frac{\left | D_1 \right |}{\left | D \right |}Gini(D_1)+\frac{D_2}{D}Gini(D_2) \quad \quad (2.5.10)

基尼指数Gini(D)表示集合D的不确定性,基尼指数Gini(D,A)表示经A=a分割后集合D的不确定性,基尼指数越大,样本集合的不确定性也就越大,这一点与熵类似。

下面来比较二分类问题中基尼指数Gini(p)、熵(单位比特)之半 \frac{1}{2}H(p) 和分类误差率的关系。横坐标表示概率P,纵坐标表示损失。

从图中可以看出,基尼指数和熵之半的曲线很接近,都可以近似地代表分类误差率。

(CART生成算法)

输入:训练数据集D,停止计算的条件;

输出:CART决策树。

根据训练数据集,从根结点开始,递归地对每个结点进行以下操作,构建二叉决策树:

(1)设结点的训练数据集为D,计算现有特征对该数据集的基尼指T_o数。此时,对每一个特征A,对其可能取的每个值a,根据样本点对A=a的测试为“是”或“否”将D分割成D_{1}D_{2}两部分,利用式(2.5.10)计算A=a时的基尼指数;

(2)在所有可能的特征A以及它们所有可能的切分点a中,选择基尼指数最小的特征及其对应的切分点作为最优特征与最优切分点。依最优特征与最优切分点,从现结点生成两个子结点,将训练数据集依特征分配到两个子结点中去;

(3)对两个子结点递归地调用(1)和(2),直至满足停止条件;

(4)生成CART决策树

(注意:算法停止计算的条件是结点中的样本个数小于预定阈值,或者样本集的基尼指数小于预定阈值(样本基本属于同一类),或者没有更多特征)。

例3. 根据表1 所给训练数据集,应用CART算法生成决策树。                    

解答过程如下:

                                                                                   

                                                               

2.6  CART剪枝

CART剪枝算法从“完全生长”的决策树的底端剪去一些子树,使决策树变小(模型变简单),从而能够对未知数据有更准确的预测。CART剪枝算法由两步组成:

①首先从生成算法产生的决策树 T_o 底端开始不断剪枝,直到 T_o 的根结点,形成一个子树序列\left \{ T_o,T_1,...,T_n \right \}

②然后通过交叉验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树。

1. 剪枝,形成子树序列

在剪枝过程中,计算子树的损失函数:

                                                    C_{\alpha }(T)=C(T)+\alpha \left | T \right |\quad \quad \quad(2.6.1)

其中,T 为任意子树,C(T)为对训练数据的预测误差(如基尼指数),\left |T \right | 为子树的叶结点个数,\alpha \geq 0为参数,C_\alpha (T)为参数是\alpha时的整体损失。参数\alpha权衡的训练数据的拟合程度和模型的复杂度。

对固定的\alpha,一定存在使损失函数C_{\alpha }(T)最小的子树,将其表示为T_\alphaT_\alpha在损失函数C_{\alpha }(T)最小的意义下是最优的。容易验证这样的最优子树是唯一的。当\alpha大的时候,最优子树T_\alpha偏小;当\alpha小的时候,最优子树T_\alpha偏大。极端情况,当\alpha =0时,整体树是最优的。当\alpha \rightarrow \infty时,根结点组成的单结点树是最优的。

Breiman等人证明:可以用递归的方法对树进行剪枝。将\alpha从小增加,0=\alpha _0< \alpha _1< ...< \alpha _n< +\infty,产生一系列的区间[\alpha _i,\alpha _{i+1}),i=0,1,...,n;剪枝得到的子树序列对应着区间\alpha \in [\alpha _i,\alpha _{i+1}),i=0,1,...,n 的最优子树序列\left \{ T_0, T_1,..., T_n \right \},序列中的子树是嵌套的。

具体地,从整体树T_0 开始剪枝,对T_0 的任意的内部结点t,以t为单结点树的损失函数是

                                                 C_{\alpha }(T)=C(T)+\alpha \quad \quad \quad(2.6.2)

以t为根结点的子树 T_t 的损失函数是

                                                 C_{\alpha }(T_t)=C(T_t)+\alpha \left | T_t \right |\quad \quad \quad(2.6.3)

\alpha =0\alpha充分小时,有不等式

                                                 C_{\alpha }(T_t)< C_\alpha (t) \quad \quad (2.6.4)

\alpha增加时,在某一\alpha

                                                 C_{\alpha }(T_t)=C_\alpha (t) \quad \quad (2.6.5)

\alpha再增大时,不等式(2.6.4)反向。只要  \alpha =\frac{C(t)-C(T_{t})}{\left | T_{t} \right |-1} ,T_{t} 与t 有相同的损失函数值,而t的结点少,因此t比T_{t} 有相同的损失函数值,而t的结点少,因此t比T_{t}更加可取,对T_{t}进行剪枝。

为此,对T_{o}中每一内部结点t,计算

                                                  g(t)=\frac{C(t)-C(T_{t})}{|T_{t}|-1} \quad \quad \quad(2.6.6)

它表示剪枝后整体损失函数减少的程度,在T_{o}中剪去g(t)最小的T_{t},将得到的子树作为T_{1} ,同时将最小的g(t)设为\alpha_{1} 。T_{1}为区间

[\alpha _i,\alpha _{i+1}) 的最优子树。

如此剪枝下去,直至得到根结点。在这个过程中,不断地增加\alpha 的值,产生新的区间。

2. 在剪枝得到的子树序列T_{o},T_{1},...,T_{n} 中通过交叉验证选取最优子树T_{\alpha }

具体地,利用独立的验证数据集,测试子树序列T_{o},T_{1},...,T_{n} 中各棵子树的平均误差或者基尼指数。平方误差或基尼指数,最小的决策树被认为是最优的决策树。在子树序列中,每棵子树T_{o},T_{1},...,T_{n} 都对应一个参数\alpha_{1} ,\alpha_2 ,...,\alpha _{n}。所以,当最优子树T_{k}确定时,对应的\alpha_{k}也确定了,即得到最优决策树T_{\alpha }

CART剪枝算法

输入:CART算法生成的决策树T_{o}

输出:最优决策树T_{\alpha }

(1)设k=0,T=T_{o}

(2)设\alpha =+\infty

(3)自上而下地对各内部结点t计算C(T_{t}) ,\left | T_{t} \right | 以及

                                                              g(t)=\frac{C(t)-C(T_{t})}{|T_{t}|-1}

                                                              \alpha =\min(\alpha ,g(t))

这里,T_{t}表示以t为根的结点的子树,C(T_{t})是对训练数据的预测误差,\left | T_{t} \right | 是 T_{t}的叶结点个数。

(4)对g(t)=\alpha的内部结点t 进行剪枝,并对叶结点t 以多数表决法决定其类,得到树T。

(5)设k=k+1,\alpha _{k}=\alpha ,T_{k}=T

(6)如果T_{k}不是由根结点及两个叶结点构成的树,则回到步骤(3);否则令T_{k}=T_{n}

(7)采用交叉验证法在子树序列T_{o},T_{1},...,T_{n} 中选取最优子树T_{\alpha }

 

                                        

 

代码(包括构建决策树,用决策树区分鱼的种类和使用决策树预测隐形眼镜类型等):

#!/usr/bin/env python3
#coding=utf-8

from math import log
import operator
import matplotlib.pyplot as plt

#计算给定数据集的信息熵
def calcEntroy(dataSet):
    numEntroy=len(dataSet)
    labelCounts={}
    for featVec in dataSet:
        currentLabel=featVec[-1]
        if currentLabel not in labelCounts.keys():  #labelCounts.keys()表示返回labelCounts字典的键列表
            labelCounts[currentLabel]=0 #将字典不存在的键添加到字典中并且给键值赋初值
        labelCounts[currentLabel]+=1 #记录当前类别的出现的次数
    Entroy=0.0
    '''计算信息熵'''
    for key in labelCounts:
        prob=float(labelCounts[key])/numEntroy
        Entroy-=prob*log(prob,2)
    return Entroy

#按照给定特征划分数据集
def splitDataSet(dataSet,axis,value):
    retDataSet=[]
    for featVec in dataSet:
        if featVec[axis]== value:
            reducedFeatVec=featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

#选择最好的数据集划分方式
def chooseBestFeatureToSplit(dataSet):
    numFeatures=len(dataSet[0])-1 #数据集最好一项是标签,而前len(dataSet[0])-1表示数据特征数
    baseEntropy = calcEntroy(dataSet)
    bestInfoGain=0.0; bestFeature=-1
    for i in range(numFeatures):
        featList=[example[i] for example in dataSet]
        uniqueVals=set(featList) #创建一个无序重复元素集
        newEntropy=0.0
        '''计算每种划分方法的信息熵'''
        for value in uniqueVals:
            subDataSet=splitDataSet(dataSet,i,value)
            prob=len(subDataSet)/float(len(dataSet))
            newEntropy+=prob*calcEntroy(subDataSet)
        infoGain=baseEntropy-newEntropy
        if (infoGain>bestInfoGain):
            bestInfoGain=infoGain
            bestFeature=i
    #返回最好特征划分的索引值
    return bestFeature

#计算出现次数最多的分类名称
def majorityVCnt(classList):
    classCount={}
    for vote in classCount:
        if vote not in classCount.keys():
            classCount[vote]=0
        classCount[vote]+=1
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount[0][0]

#构建决策树函数
def createTree(dataSet,labels):
    classList=[example[-1] for example in dataSet] #用索引的-1位置表示列表最后的位置
    #如果第一个类别的数目=整个数据集的长度,即整个数据集只有一个类别,可以直接返回该类标签
    #第一个停止条件
    if classList.count(classList[0])==len(classList):
        return classList[0]
    #如果数据集只有一列,则无法简单的返回唯一的类标签,这里使用前面介绍的majorityCnt函数挑选出现次数最多的类别作为返回值
    if len(dataSet[0])==1:
        return majorityVCnt(classList)
    #获取数据集中最好特征划分的索引
    bestFeatSplitIndex=chooseBestFeatureToSplit(dataSet)
    #获取最好特征所属类别标签
    bestFeatLabel=labels[bestFeatSplitIndex]
    #初始化myTree
    myTree={bestFeatLabel:{}}
    del(labels[bestFeatSplitIndex])
    #获取数据集中最优的列
    featValues=[example[bestFeatSplitIndex] for example in dataSet]
    uniqueVals=set(featValues)
    for value in uniqueVals:
        #求出剩余的类标签
        subLables=labels[:]
        #遍历当前选择特征包含的所有属性值,递归调用createTree()函数

        myTree[bestFeatLabel][value]=createTree(splitDataSet(dataSet,bestFeatSplitIndex,value),subLables)
    return myTree

#使用文本注解绘制树节点
'''使用dict(a="a",b="b")返回字典形式的数据类型{"a":"a","b":"b"}'''
#定义文本框和箭头格式
decisionNode=dict(boxstyle="sawtooth",fc="0.8")
leafNode=dict(boxstyle="round4",fc="0.8")
arrow_args=dict(arrowstyle="<-")
'''画节点'''
def plotNode(nodeText,centerPt,parentPt,nodeType):
    #annotate()图形增加注释
    #nodeText为显示的文本,centerPt为箭头所在的点,parentPt为指向文本的初始点
    createPlot.ax1.annotate(nodeText,xy=parentPt,xycoords='axes fraction',\
    xytext=centerPt,textcoords='axes fraction',va='center',ha='center',\
    bbox=nodeType,arrowprops=arrow_args)
"""
def createPlot():
    fig=plt.figure(1,facecolor='white')
    fig.clf() #清楚所有轴,但是窗口打开,这样它可以被重复使用
    #creatplot.ax1为全局变量
    #frameon表示是否绘制坐标轴矩阵
    createPlot.ax1=plt.subplot(111,frameon=False)  #111表示1行一列,最后的1表示第一个图
    plotNode('决策节点',(0.5,0.1),(0.1,0.5),decisionNode)
    plotNode('叶节点',(0.8,0.1),(0.3,0.8),leafNode)
    plt.show()
"""

#获取叶子节点的数目和树的深度
def getNumLeafs(myTree):
    #初始化叶子节点的数目
    numLeafs=0
    #获取第一次划分数据集的类别标签
    firstStr=list(myTree.keys())[0]
    secondDict=myTree[firstStr]
    for key in secondDict.keys():
        #判断子节点的数据类型是否为字典类型,如果是使用getNumLeafs()进行递归调用
        if type(secondDict[key]).__name__=='dict':
            numLeafs+=getNumLeafs(secondDict[key])
        else:
            numLeafs+=1
    return numLeafs

def getTreeDepth(myTree):
    #类似于上面的getNumleafs函数
    maxDepth=0
    firstStr=list(myTree.keys())[0]
    secondDict=myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            thisDepth=1+getTreeDepth(secondDict[key])
        else:
            thisDepth=1
        if thisDepth>maxDepth:
            maxDepth=thisDepth
    return maxDepth

'''在父子节点间填充文本信息'''
def plotMidText(cntrPt,parentPt,txtString):
    xMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[0]
    yMid=(parentPt[0]-cntrPt[0])/2.0+cntrPt[1]
    createPlot.ax1.text(xMid,yMid,txtString)

#构建可视化树函数
def plotTree(myTree,parentPt,nodeTxt):
    #获取树的节点数目
    numLeafs=getNumLeafs(myTree)
    #获取树的深度
    depth=getTreeDepth(myTree)
    #获取第一次划分数据集的列表
    firstStr=list(myTree.keys())[0]
    #
    cntrPt=(plotTree.xOff+(1.0+float(numLeafs))/2.0/plotTree.totalW,plotTree.yOff)
    #在父子节点间添加文本信息
    plotMidText(cntrPt,parentPt,nodeTxt)
    #绘制树节点
    plotNode(firstStr,cntrPt,parentPt,decisionNode)
    #获取根节点的子节点
    secondDict=myTree[firstStr]
    plotTree.yOff=plotTree.yOff-1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            plotTree(secondDict[key],cntrPt,str(key))
        else:
            plotTree.xOff=plotTree.xOff+1.0/plotTree.totalW
            plotNode(secondDict[key],(plotTree.xOff,plotTree.yOff),cntrPt,leafNode)
            plotMidText((plotTree.xOff,plotTree.yOff),cntrPt,str(key))
    plotTree.yOff=plotTree.yOff+1.0/plotTree.totalD

'''创建图形'''
def createPlot(inTree):
    fig=plt.figure(1,facecolor='white')
    fig.clf()
    axprops=dict(xticks=[],yticks=[])
    createPlot.ax1=plt.subplot(111,frameon=False,**axprops)
    #使用plotTree.totalW来存储树的宽度,plotTree.totalD存储树的高度
    plotTree.totalW=float(getNumLeafs(inTree))
    plotTree.totalD=float(getTreeDepth(inTree))
    #初始化plotTree.xOff和plotTree.yOff
    plotTree.xOff=-0.5/plotTree.totalW
    plotTree.yOff=1.0
    #设置根节点的坐标为(0.5,1.0)
    plotTree(inTree,(0.5,1.0),'')
    plt.show()

#使用决策树进行分类
def classify(inputTree,featLabels,testVec):
    firstStr=list(inputTree.keys())[0]
    secondDict=inputTree[firstStr]
    #获取第一个匹配firstStr的索引
    featIndex=featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex]==key:
            if type(secondDict[key]).__name__=='dict':
                classLabel=classify(secondDict[key],featLabels,testVec)
            else:
                classLabel=secondDict[key]
    return classLabel

#决策树的存储
def storeTree(inputTree,filename):
    import pickle
    fw=open(filename,'w')
    #序列化对象,并将结果数据流写入到文件对象中,即将inputTree对象保存到fw中
    pickle.dump(inputTree,fw)
    fw.close()

def grabTree(filename):
    import pickle
    fr=open(filename)
    #反序列化对象,将文件中的数据解析为一个Python对象。
    return pickle.load(fr)

#使用决策树预测隐形眼镜类型
fr=open(r'F:\python\machinelearninginaction\Ch03\lenses.txt')
lenses=[inst.strip().split('\t') for inst in fr.readlines()]
lensesLabels=['age','prescript','astigmatic','tearRate']
lensesTree=createTree(lenses,lensesLabels)
print(lensesTree)
createPlot(lensesTree)


 

 

 

             

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值