经典决策树算法(ID3、C4.5、CART)原理以及Python实现

1 决策树简介

 决策树(Decision Tree),是每个分支都通过条件判断进行划分的树,是解决分类和回归问题的一种机器学习算法,其核心是一个贪心算法,它采用自顶向下的递归方法构建决策树。

1.1 决策树模型

 决策树模型是一种对实例进行分类的树,由节点(node,由圆框表示)和有向边(directed edge,由方框表示)组成,其中节点分为内部节点(internal node)和叶子节点(leaf node),内部节点表示一个属性或特征,叶子节点表示一个类。

图1 决策树模型

 决策树可以被看作是一个if-then规则的集合:由决策树的根节点到叶子节点的每一条路径构建一条规则,路径上内部节点的特征对应规则的条件,而叶子结点的分类则对应规则的结论。这一个集合是互斥且完备的,也就是说每一个实例都被且只被一条规则所覆盖。

 决策树还表示给定特征条件下类的条件概率分布:这个条件概率分布被定义在特征空间的一个划分(partition)上。将特征空间划分为互不相交的单元(cell),并在每一个单元定义一个类的概率分布就构成一个条件概率分布。决策树的每一条路径对应一个划分单元,也就是一个条件概率分布。叶子节点(单元)上的条件概率往往偏向于某一个类,分类时将该节点的实例强行分到条件概率大的一类上去。

图2 决策树对应于条件概率分布

1.2 决策树学习

 假设给定训练数据集

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 ), ...\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)},...,x_{i}^{(n)} \right ) ^{T} xi=(xi(1),xi(2),...,xi(n))T为输入实例(特征向量), n n n为特征个数, y i = { 1 , 2 , . . . , K } y_i=\left \{ 1,2,...,K \right \} yi={1,2,...,K}为类标记, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N,N为样本容量。

 决策树学习是根据训练数据集构建一个决策树模型,能够对实例进行正确的分类。本质上是从训练数据集中归纳出一组分类规则,也可以看做是由训练数据集估计条件概率模型。决策树学习的策略是以损失函数为目标函数的最小化,现实中采用启发式方法来近似求解,得到的是次最优(sub-optimal)的决策树。决策树学习的算法通常是递归地选择最优特征,并根据最优特征对训练数据进行划分,使得对各个子数据集有一个最好的分类的过程。为了缓解过拟合现象,对于生成的决策树还必须进行自下而上的剪枝,使之泛化能力更加强。

 总得来说,决策树算法包含特征选址、决策树的生成、决策树的剪枝过程。深浅不同的决策树对应不同复杂度的概率模型。决策树的生成只考虑局部最优,决策树的剪枝考虑全局最优。

 常用的决策树算法有ID3算法、改进的C4.5算法和CART算法。

2 特征选择

 不同特征决定不同的决策树。特征选择就是选取对训练数据集具有分类能力的特征,也就是决定用哪一个特征来划分特征空间,这样可以提高决策树的学习效率。通常特征选择的准则有信息增益、信息增益比、基尼指数、平方误差等等。

图3 不同决策树

2.1 信息熵(entropy)和条件熵(conditional entropy)

 信息论中的 熵(entropy) 是表示随机变量不确定性的度量。熵越大,随机变量的不确定性就越大。假设 X X X是一个取有限个值的离散随机变量,其概率分布为

P ( X = x i ) = p i , i = 1 , 2 , . . . , n P\left ( X = x_i \right ) = p_i, \quad i=1,2,...,n P(X=xi)=pi,i=1,2,...,n

 则 X X X定义为

E ( X ) = − ∑ i = 1 n p i log ⁡ 2 p i , 0 ≤ E ( X ) ≤ log ⁡ 2 n E \left ( X \right ) = - \sum_{i=1}^{n} p_i \log_2 p_i, \quad 0 \le E \left ( X \right ) \le \log_2 n E(X)=i=1npilog2pi,0E(X)log2n

 以上公式,熵的单位为比特(bit)。由熵的定义可知,熵值大小只跟 X X X的分布有关,跟 X X X的取值无关,熵也可以表示为

E ( p ) = − ∑ i = 1 n p i log ⁡ 2 p i E \left ( p \right ) = - \sum_{i=1}^{n} p_i \log_2 p_i E(p)=i=1npilog2pi

 特殊地,当 X X X随机变量只取两个值,如0或1, X X X的分布为 伯努利分布,即

E ( X = 1 ) = p E \left ( X=1 \right ) = p E(X=1)=p

E ( X = 0 ) = 1 − p E \left ( X=0 \right ) = 1-p E(X=0)=1p

0 ≤ p ≤ 1 0 \le p \le 1 0p1

 此时 X X X

E ( p ) = − p log ⁡ 2 p − ( 1 − p ) log ⁡ 2 ( 1 − p ) E \left ( p \right ) = -p \log_2 p - \left ( 1-p \right ) \log_2 \left ( 1-p \right ) E(p)=plog2p(1p)log2(1p)

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

E ( Y ∣ X ) = ∑ i = 1 n p i E ( Y ∣ X = x i ) E \left ( Y|X \right ) = \sum_{i=1}^{n} p_i E \left ( Y|X=x_i \right ) E(YX)=i=1npiE(YX=xi)

 其中 p i = P ( X = x i ) , i = 1 , 2 , . . . , n p_i=P \left ( X=x_i \right ) , \quad i=1,2,...,n pi=P(X=xi),i=1,2,...,n

 特别需要注意,当熵和条件熵中的概率由数据估计(特别是极大似然估计)得到时,所对应的熵称为经验熵,条件熵称为经验条件熵

  • 示例:对表1所给的数据集 D D D,计算经验熵和经验条件熵:

 计算经验熵

E ( D ) = − 9 15 l o g 2 9 15 − 6 15 l o g 2 6 15 = 0.971 E(D) = - \frac{9}{15} log_2 \frac{9}{15} - \frac{6}{15} log_2 \frac{6}{15} = 0.971 E(D)=159log2159156log2156=0.971

 计算经验条件熵, A 1 , A 2 , A 3 , A 4 A_1, A_2, A_3, A_4 A1,A2,A3,A4分别代表年龄、有工作、有自己的房子、信贷4个特征, 1 , 2 , 3 1,2,3 1,2,3代表 A 1 A_1 A1取值为青年、中年、老年,则其对应取值的经验熵为

E ( D , A 1 = 1 ) = − 2 5 log ⁡ 2 2 5 − 3 5 log ⁡ 2 3 5 = 0.971 E (D, A_1=1) = - \frac{2}{5} \log_2 \frac{2}{5} - \frac{3}{5} \log_2 \frac{3}{5} = 0.971 E(D,A1=1)=52log25253log253=0.971

E ( D , A 1 = 2 ) = − 3 5 log ⁡ 2 3 5 − 2 5 log ⁡ 2 2 5 = 0.971 E (D, A_1=2) = - \frac{3}{5} \log_2 \frac{3}{5} - \frac{2}{5} \log_2 \frac{2}{5} = 0.971 E(D,A1=2)=53log25352log252=0.971

E ( D , A 1 = 3 ) = − 4 5 log ⁡ 2 4 5 − 1 5 log ⁡ 2 1 5 = 0.723 E (D, A_1=3) = - \frac{4}{5} \log_2 \frac{4}{5} - \frac{1}{5} \log_2 \frac{1}{5} = 0.723 E(D,A1=3)=54log25451log251=0.723

 则特征 A 1 A_1 A1对训练数据集 D D D的经验条件熵为

E ( D ∣ A 1 ) = 5 15 E ( D , A 1 = 1 ) + 5 15 E ( D , A 1 = 2 ) + 5 15 E ( D , A 1 = 3 ) = 0.888 E (D|A_1) = \frac{5}{15} E(D, A_1=1) + \frac{5}{15} E(D, A_1=2) + \frac{5}{15} E(D, A_1=3) = 0.888 E(DA1)=155E(D,A1=1)+155E(D,A1=2)+155E(D,A1=3)=0.888

 同理

E ( D ∣ A 2 ) = 0.647 E (D|A_2) =0.647 E(DA2)=0.647

E ( D ∣ A 3 ) = 0.550 E (D|A_3) =0.550 E(DA3)=0.550

E ( D ∣ A 4 ) = 0.608 E (D|A_4) =0.608 E(DA4)=0.608

2.2 信息增益(infomation gain)

信息增益(infomation gain) 定义:某特征 A A A对训练数据集 D D D的信息增益为集合 D D D的经验熵与 A A A给定条件下 D D D的经验条件熵之差

G a i n ( D , A ) = E ( D ) − E ( D ∣ A ) Gain(D,A) = E(D) - E(D|A) Gain(D,A)=E(D)E(DA)

 一般地,熵与条件熵之差称为 互信息(mutual information)

 设训练数据集 D D D,它包含 K K K个类 C k C_k Ck,这里 k = 1 , 2 , . . . , K k=1,2,...,K k=1,2,...,K;特征 A A A n n n个不同取值,根据它可以将 D D D划分为 n n n个子集 D i D_i Di,这里 i = 1 , 2 , . . . , n i=1,2,...,n i=1,2,...,n;记子集 D i D_i Di属于类 C k C_k Ck的子集为 D i k D_{ik} Dik信息增益算法的流程为:

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

 输出:特征 A A A对训练数据集 D D D的信息增益 G a i n ( D , A ) Gain(D, A) Gain(D,A)

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

E ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ E(D) = - \sum_{k=1}^{K} \frac{\left | C_k \right | }{\left | D \right | } \log_2 \frac{\left | C_k \right | }{\left | D \right | } E(D)=k=1KDCklog2DCk

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

E ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ log ⁡ 2 ∣ D i k ∣ ∣ D i ∣ E(D|A)=\sum_{i=1}^{n} \frac{\left | D_i \right | }{\left | D \right | } H\left ( D_i \right ) = - \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 | } E(DA)=i=1nDDiH(Di)=i=1nDDik=1KDiDiklog2DiDik

 (3)特征 A A A对训练数据集 D D D的信息增益

G a i n ( D , A ) = E ( D ) − E ( D ∣ A ) Gain(D, A) = E(D) - E(D|A) Gain(D,A)=E(D)E(DA)

 示例:对表1所给的数据集 D D D,根据信息增益来选择最优特征 A ∗ A^* A

  • 特征 A 1 A_1 A1对训练数据集 D D D的信息增益

G a i n ( D , A 1 ) = E ( D ) − E ( D ∣ A 1 ) = 0.971 − 0.888 = 0.083 Gain(D, A_1) = E(D) - E(D|A_1) = 0.971 - 0.888 = 0.083 Gain(D,A1)=E(D)E(DA1)=0.9710.888=0.083

 同理

G a i n ( D , A 2 ) = E ( D ) − E ( D ∣ A 2 ) = 0.324 Gain(D, A_2) = E(D) - E(D|A_2) = 0.324 Gain(D,A2)=E(D)E(DA2)=0.324

G a i n ( D , A 3 ) = E ( D ) − E ( D ∣ A 3 ) = 0.420 Gain(D, A_3) = E(D) - E(D|A_3) = 0.420 Gain(D,A3)=E(D)E(DA3)=0.420

G a i n ( D , A 4 ) = E ( D ) − E ( D ∣ A 4 ) = 0.363 Gain(D, A_4) = E(D) - E(D|A_4) = 0.363 Gain(D,A4)=E(D)E(DA4)=0.363

 由于特征 A 3 A_3 A3(有自己的房子)的信息增益最大,所以选择该特征作为最优的划分特征,即 A ∗ = A 3 A^*=A_3 A=A3

2.3 信息增益比(information gain ratio)

 训练数据集 D D D关于特征 A A A分裂信息(split information) 或叫做特征 A A A固有值(intrinsic value) 用于衡量特征分裂数据的广度和均匀性

I V ( D , A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ log ⁡ 2 ∣ D i ∣ ∣ D ∣ IV(D, A) = - \sum_{i=1}^{n} \frac{\left | D_i \right | }{\left | D \right | } \log_2 \frac{\left | D_i \right | }{\left | D \right | } IV(D,A)=i=1nDDilog2DDi

 其中 n n n为特征 A A A的取值个数。

 以信息增益作为准则选择训练数据集的特征时,存在偏向于选择取值较多的特征的问题,矫正的方法是改用 信息增益比(information gain ratio) ,其定义为:某特征 A A A对训练数据集 D D D的信息增益比 G a i n R a t i o ( D , A ) GainRatio(D,A) GainRatio(D,A)为其信息增益与训练数据集 D D D关于特征 A A A的分裂信息之比

G a i n R a t i o ( D , A ) = G a i n ( D , A ) I V ( D , A ) GainRatio(D, A) = \frac{Gain(D, A)}{IV(D, A)} GainRatio(D,A)=IV(D,A)Gain(D,A)

  • 示例:对表1所给的数据集 D D D,根据信息增益比来选择最优特征 A ∗ A^* A

 计算数据集 D D D分别关于 A 1 , A 2 , A 3 , A 4 A_1, A_2, A_3, A_4 A1,A2,A3,A4的分裂信息

I V ( D , A 1 ) = − 5 15 log ⁡ 2 5 15 − 5 15 log ⁡ 2 5 15 − 5 15 log ⁡ 2 5 15 = 1.585 IV(D, A_1) = - \frac{5}{15} \log_2 \frac{5}{15} - \frac{5}{15} \log_2 \frac{5}{15} - \frac{5}{15} \log_2 \frac{5}{15} = 1.585 IV(D,A1)=155log2155155log2155155log2155=1.585

I V ( D , A 2 ) = − 5 15 log ⁡ 2 5 15 − 10 15 log ⁡ 2 10 15 = 0.918 IV(D, A_2) = - \frac{5}{15} \log_2 \frac{5}{15} - \frac{10}{15} \log_2 \frac{10}{15} = 0.918 IV(D,A2)=155log21551510log21510=0.918

I V ( D , A 3 ) = − 6 15 log ⁡ 2 6 15 − 9 15 log ⁡ 2 9 15 = 0.971 IV(D, A_3) = - \frac{6}{15} \log_2 \frac{6}{15} - \frac{9}{15} \log_2 \frac{9}{15} = 0.971 IV(D,A3)=156log2156159log2159=0.971

I V ( D , A 4 ) = − 5 15 log ⁡ 2 5 15 − 6 15 log ⁡ 2 6 15 − 4 15 log ⁡ 2 4 15 = 1.566 IV(D, A_4) = - \frac{5}{15} \log_2 \frac{5}{15} - \frac{6}{15} \log_2 \frac{6}{15} - \frac{4}{15} \log_2 \frac{4}{15} = 1.566 IV(D,A4)=155log2155156log2156154log2154=1.566

 计算信息增益比

G a i n R a t i o ( D , A 1 ) = G a i n ( D , A 1 ) I V ( D , A 1 ) = 0.083 1.585 = 0.052 GainRatio(D, A_1) = \frac{Gain(D, A_1)}{IV(D, A_1) } = \frac{0.083}{1.585} = 0.052 GainRatio(D,A1)=IV(D,A1)Gain(D,A1)=1.5850.083=0.052

G a i n R a t i o ( D , A 2 ) = G a i n ( D , A 2 ) I V ( D , A 2 ) = 0.324 0.918 = 0.353 GainRatio(D, A_2) = \frac{Gain(D, A_2)}{IV(D, A_2) } = \frac{0.324}{0.918} = 0.353 GainRatio(D,A2)=IV(D,A2)Gain(D,A2)=0.9180.324=0.353

G a i n R a t i o ( D , A 3 ) = G a i n ( D , A 3 ) I V ( D , A 3 ) = 0.420 0.971 = 0.433 GainRatio(D, A_3) = \frac{Gain(D, A_3)}{IV(D, A_3) } = \frac{0.420}{0.971} = 0.433 GainRatio(D,A3)=IV(D,A3)Gain(D,A3)=0.9710.420=0.433

G a i n R a t i o ( D , A 4 ) = G a i n ( D , A 4 ) I V ( D , A 4 ) = 0.363 1.566 = 0.232 GainRatio(D, A_4) = \frac{Gain(D, A_4)}{IV(D, A_4) } = \frac{0.363}{1.566} = 0.232 GainRatio(D,A4)=IV(D,A4)Gain(D,A4)=1.5660.363=0.232

 由于特征 A 3 A_3 A3(有自己的房子)的信息增益率最大,所以依然选择该特征作为最优的划分特征,即 A ∗ = A 3 A^*=A_3 A=A3

注意:由于信息增益比偏向于可取值数量较少的特征,所以C4.5算法并不是直接使用信息增益率,而是采用一个启发式:先从候选特征中找出信息增益高于平均水平的特征,然后再从中选择信息增益比最高的特征。

2.4 基尼指数(Gini index)

 一个随机变量的不确定性,除了使用上述的 来度量,也可以使用 基尼指数(Gini index) 来度量,它一般用作CART分类决策树的特征选择准则。

 在分类问题中,假设有 K K K 个类,样本点属于第 k k k 类的概率为 p k p_k pk,则概率分布的基尼指数定义为

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}^{K} p_k(1 - p_k) = 1 - \sum_{k=1}^{K} p_k^{2} Gini(p)=k=1Kpk(1pk)=1k=1Kpk2

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

G i n i ( p ) = 2 p ( 1 − p ) Gini(p) = 2p(1 - p) Gini(p)=2p(1p)

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

G i n i ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 Gini(D) = 1 - \sum_{k=1}^{K} \left ( \frac{\left | C_k \right | }{\left | D \right | } \right ) ^{2} Gini(D)=1k=1K(DCk)2

 其中, C k C_k Ck D D D 中属于第 k k k 类的样本子集, K K K 是类的个数。

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

D 1 = { ( x , y ) ∈ D ∣ A ( x ) = a } , D 2 = D − D 1 D_1 = \left \{ (x, y)\in D | A(x) = a \right \}, \quad D_2 = D - D_1 D1={(x,y)DA(x)=a},D2=DD1

 则在特征 A A A 的条件下,集合 D 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{\left | D_1 \right | }{\left | D \right | } Gini(D_1) + \frac{\left | D_2 \right | }{\left | D \right | } Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)

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

 二类分类问题中基尼指数 G i n i ( p ) Gini(p) Gini(p)、熵(单位比特)之半 H ( p ) / 2 H(p)/2 H(p)/2 和分类误差
率的关系可以用下图表示。横坐标表示概率 p,纵坐标表示损失。可以看出基尼指数和熵之半的曲线很接近,都可以近似地代表分类误差率

图 基尼指数

  • 示例:对表1所给的数据集 D D D,根据基尼系数来选择最优特征 A ∗ A^* A

 求特征 A 1 A_1 A1 的基尼指数:

G i n i ( D , A 1 = 1 ) = 5 15 [ 2 × 2 5 × ( 1 − 2 5 ) ] + 10 15 [ 2 × 7 10 × ( 1 − 7 10 ) ] = 0.44 Gini(D, A_1=1) = \frac{5}{15}[2 \times \frac{2}{5} \times (1- \frac{2}{5})] + \frac{10}{15}[2 \times \frac{7}{10} \times (1- \frac{7}{10})] = 0.44 Gini(D,A1=1)=155[2×52×(152)]+1510[2×107×(1107)]=0.44

G i n i ( D , A 2 = 2 ) = 5 15 [ 2 × 3 5 × ( 1 − 3 5 ) ] + 10 15 [ 2 × 6 10 × ( 1 − 6 10 ) ] = 0.48 Gini(D, A_2=2) = \frac{5}{15}[2 \times \frac{3}{5} \times (1- \frac{3}{5})] + \frac{10}{15}[2 \times \frac{6}{10} \times (1- \frac{6}{10})] = 0.48 Gini(D,A2=2)=155[2×53×(153)]+1510[2×106×(1106)]=0.48

G i n i ( D , A 3 = 3 ) = 5 15 [ 2 × 4 5 × ( 1 − 4 5 ) ] + 10 15 [ 2 × 5 10 × ( 1 − 5 10 ) ] = 0.44 Gini(D, A_3=3) = \frac{5}{15}[2 \times \frac{4}{5} \times (1- \frac{4}{5})] + \frac{10}{15}[2 \times \frac{5}{10} \times (1- \frac{5}{10})] = 0.44 Gini(D,A3=3)=155[2×54×(154)]+1510[2×105×(1105)]=0.44

 由于 G i n i ( D , A 1 = 1 ) Gini(D, A_1=1) Gini(D,A1=1) G i n i ( D , A 1 = 3 ) Gini(D, A_1=3) Gini(D,A1=3) 相等,且最小,所以 A 1 = 1 A_1=1 A1=1 A 1 = 3 A_1=3 A1=3 都可以选作 A 1 A_1 A1 的最优切分点。

 求特征 A 2 A_2 A2 A 3 A_3 A3 的基尼指数:

G i n i ( D , A 2 = 1 ) = 10 15 [ 2 × 4 10 × ( 1 − 4 10 ) ] = 0.32 Gini(D, A_2=1) = \frac{10}{15}[2 \times \frac{4}{10} \times (1- \frac{4}{10})] = 0.32 Gini(D,A2=1)=1510[2×104×(1104)]=0.32

G i n i ( D , A 3 = 1 ) = 9 15 [ 2 × 3 9 × ( 1 − 3 9 ) ] = 0.27 Gini(D, A_3=1) = \frac{9}{15}[2 \times \frac{3}{9} \times (1- \frac{3}{9})] = 0.27 Gini(D,A3=1)=159[2×93×(193)]=0.27

 由于 A 2 A_2 A2 A 3 A_3 A3 只有一个切分点,所以它们就是最优切分点。

 求特征 A 4 A_4 A4 的基尼指数:

G i n i ( D , A 4 = 1 ) = 11 15 [ 2 × 5 11 × ( 1 − 5 11 ) ] = 0.36 Gini(D, A_4=1) = \frac{11}{15}[2 \times \frac{5}{11} \times (1- \frac{5}{11})] = 0.36 Gini(D,A4=1)=1511[2×115×(1115)]=0.36

G i n i ( D , A 4 = 2 ) = 6 15 [ 2 × 4 6 × ( 1 − 4 6 ) ] + 9 15 [ 2 × 5 9 × ( 1 − 5 9 ) ] = 0.47 Gini(D, A_4=2) = \frac{6}{15}[2 \times \frac{4}{6} \times (1- \frac{4}{6})] + \frac{9}{15}[2 \times \frac{5}{9} \times (1- \frac{5}{9})] = 0.47 Gini(D,A4=2)=156[2×64×(164)]+159[2×95×(195)]=0.47

G i n i ( D , A 4 = 3 ) = 5 15 [ 2 × 1 5 × ( 1 − 1 5 ) ] + 10 15 [ 2 × 8 10 × ( 1 − 8 10 ) ] = 0.32 Gini(D, A_4=3) = \frac{5}{15}[2 \times \frac{1}{5} \times (1- \frac{1}{5})] + \frac{10}{15}[2 \times \frac{8}{10} \times (1- \frac{8}{10})] = 0.32 Gini(D,A4=3)=155[2×51×(151)]+1510[2×108×(1108)]=0.32

G i n i ( D , A 4 = 3 ) Gini(D, A_4=3) Gini(D,A4=3) 最小,所以 A 4 = 3 A_4 = 3 A4=3 为 A4 的最优切分点。

 在 A 1 , A 2 , A 3 , A 4 A_1,A_2,A_3,A_4 A1A2A3A4 几个特征中, G i n i ( D , A 3 = 1 ) = 0.27 Gini(D, A_3=1)=0.27 Gini(D,A3=1)=0.27 最小,所以选择特征 A 3 A_3 A3为最优特征, A 3 = 1 A_3=1 A3=1 为其最优切分点。于是根结点生成两个子结点,一个是叶结点。对另一个结点继续使用以上方法在 A 1 , A 2 , A 4 A_1,A_2,A_4 A1A2A4 中选择最优特征及其最优切分点,结果是 A 2 = 1 A_2=1 A2=1。依此计算得知,所得结点都是叶结点。

3 决策树的生成

3.1 ID3算法

ID3算法的核心是在决策树的各个节点上,以信息增益为准测来选择最优特征,递归地构建决策树,其算法流程如下:

图4 ID3算法流程
 对表1所给的数据集 D D D,使用ID3算法生成的决策树如下:

图5 ID3生成决策树

 ID3算法的优缺点:

优点:

(1)ID3算法算法结构简单,清晰易懂;
(2)非常灵活方便;
(3)不存在无解的危险;
(4)可以利用全部训练例的统计性质进行决策,从而抵抗噪音。

缺点:

(1)ID3算法采用信息增益来选择最优划分特征,信息增益倾向与取值较多的特征,往往容易导致结果误差;
(2)没有考虑连续值,对与连续值的特征无法进行划分;
(3)无法处理有缺失值的数据;
(4)没有考虑过拟合的问题,而在决策树中,过拟合是很容易发生的;
(5)采用贪心算法,每次划分都是考虑局部最优化,而局部最优化并不是全局最优化,当然这一缺点也是决策树的缺点,获得最优决策树本身就是一个NP难题,所以只能采用局部最优。

 使用Python实现ID3算法代码:


class TreeNode:

    def __init__(self, parent, children, feature, category, X_data, Y_data):
        self.parent = parent
        self.children = children
        self.feature = feature
        self.category = category
        self.X_data = X_data
        self.Y_data = Y_data

    def get_parent(self):
        return self.parent

    def get_children(self):
        return self.children
        

import numpy as np
import pandas as pd

from treeNode import TreeNode




class DecisionTree:

    def __init__(self, X, Y):
        self.X_train = X
        self.Y_train = Y
        self.root_node = TreeNode(None, None, None, None, self.X_train, self.Y_train)
        self.features = self.get_features(self.X_train)
        self.tree_generate(self.root_node)

    def get_features(self, X_train_data):
    	"""计算各个特征的每个取值的频次"""
        features = dict()
        for i in range(len(X_train_data.columns)):
            feature = X_train_data.columns[i]
            features[feature] = list(X_train_data[feature].value_counts().keys())

        return features

    def tree_generate(self, tree_node):
    	"""生成决策树"""
        X_data = tree_node.X_data
        Y_data = tree_node.Y_data
        # get all features of the data set
        features = list(X_data.columns)
        # 如果Y_data中的实例属于同一类,则置为单结点,并将该类作为该结点的类
        if len(list(Y_data.value_counts())) == 1:
            tree_node.category = Y_data.iloc[0]
            tree_node.children = None
            return
        # 如果特征集为空,则置为单结点,并将Y_data中最大的类作为该结点的类
        elif len(features) == 0:
            tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
            tree_node.children = None
            return
        # 否则,计算各特征的信息增益,选择信息增益最大的特征
        else:
            ent_d = self.compute_entropy(Y_data)
            XY_data = pd.concat([X_data, Y_data], axis=1)
            d_nums = XY_data.shape[0]
            max_gain_ratio = 0
            feature = None

            for i in range(len(features)):
                v = self.features.get(features[i])
                Ga = ent_d
                for j in v:
                    dv = XY_data[XY_data[features[i]] == j]
                    dv_nums = dv.shape[0]
                    ent_dv = self.compute_entropy(dv[dv.columns[-1]])
                    Ga -= dv_nums/d_nums*ent_dv

                if Ga > max_gain_ratio:
                    max_gain_ratio = Ga
                    feature = features[i]

            # 信息增益低于阈值0
            if feature is None:
                tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
                tree_node.children = None
                return
            tree_node.feature = feature

            # get all kinds of values of the current partition feature
            branches = self.features.get(feature)
            # branches = list(XY_data[feature].value_counts().keys())
            tree_node.children = dict()
            for i in range(len(branches)):
                X_data = XY_data[XY_data[feature] == branches[i]]
                if len(X_data) == 0:
                    category = XY_data[XY_data.columns[-1]].value_counts(ascending=False).keys()[0]
                    childNode = TreeNode(tree_node, None, None, category, None, None)
                    tree_node.children[branches[i]] = childNode
                    # return
                    # error, not should return, but continue
                    continue
                    
                Y_data = X_data[X_data.columns[-1]]
                X_data.drop(X_data.columns[-1], axis=1, inplace=True)
                X_data.drop(feature, axis=1, inplace=True)
                childNode = TreeNode(tree_node, None, None, None, X_data, Y_data)
                tree_node.children[branches[i]] = childNode
                # print("feature: " + str(tree_node.feature) + " branch: " + str(branches[i]) + "\n")
                self.tree_generate(childNode)

            return

    def compute_entropy(self, Y):
    	"""计算信息熵"""
        ent = 0
        for cate in Y.value_counts(1):
            ent -= cate*np.log2(cate)
        return ent

3.2 C4.5算法

 C4.5算法在ID3算法基础上进行了以下几点改进:

  • 用信息增益比来选择特征,克服了ID3算法选择特征时偏向选择取值多的特征的不足。
  • 在决策树的构造过程中进行剪枝,不考虑某些具有很好元素的节点。
  • 能够完成对连续型特征的离散化处理。
  • 能够对不完整数据进行处理。

 C4.5算法流程如下:

图6 C4.5算法流程

 使用Python实现C4.5算法代码


import numpy as np
import pandas as pd

from treeNode import TreeNode

class DecisionTreeC45:

    def __init__(self, X, Y):
        self.X_train = X
        self.Y_train = Y
        self.root_node = TreeNode(None, None, None, None, self.X_train, self.Y_train)
        self.features = self.get_features(self.X_train)
        self.tree_generate(self.root_node)

    def get_features(self, X_train_data):
    	"""计算各个特征的每个取值的频次"""
        features = dict()
        for i in range(len(X_train_data.columns)):
            feature = X_train_data.columns[i]
            features[feature] = list(X_train_data[feature].value_counts().keys())

        return features

    def tree_generate(self, tree_node):
    	"""生成决策树"""
        X_data = tree_node.X_data
        Y_data = tree_node.Y_data
        # get all features of the data set
        features = list(X_data.columns)

        # 如果Y_data中的实例属于同一类,则置为单结点,并将该类作为该结点的类
        if len(list(Y_data.value_counts())) == 1:
            tree_node.category = Y_data.iloc[0]
            tree_node.children = None
            return

        # 如果特征集为空,则置为单结点,并将Y_data中最大的类作为该结点的类
        elif len(features) == 0:
            tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
            tree_node.children = None
            return

        # 否则,计算各特征的信息增益比,选择信息增益比最大的特征
        else:
            ent_d = self.compute_entropy(Y_data)
            XY_data = pd.concat([X_data, Y_data], axis=1)
            d_nums = XY_data.shape[0]
            max_gain_ratio = 0
            feature = None

            for i in range(len(features)):
                v = self.features.get(features[i])
                Ga = ent_d
                IV = 0
                for j in v:
                    dv = XY_data[XY_data[features[i]] == j]
                    dv_nums = dv.shape[0]
                    ent_dv = self.compute_entropy(dv[dv.columns[-1]])
                    if dv_nums == 0 or d_nums == 0:
                        continue
                    Ga -= dv_nums / d_nums * ent_dv # 信息增益
                    IV -= dv_nums/d_nums*np.log2(dv_nums/d_nums) # 分离信息

                if IV != 0.0 and (Ga/IV) > max_gain_ratio:
                    max_gain_ratio = Ga/IV # 信息增益比
                    feature = features[i]

            # 如果当前特征的信息增益比小于阈值epsilon,则置为单结点,并将Y_data中最大的类作为该结点的类
            if max_gain_ratio < 0:
                tree_node.feature = None
                tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
                tree_node.children = None
                return

            if feature is None:
                tree_node.feature = None
                tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
                tree_node.children = None
                return
            tree_node.feature = feature

            # 否则,对当前特征的每一个可能取值,将Y_data分成子集,并将对应子集最大的类作为标记,构建子结点
            # get all kinds of values of the current partition feature
            branches = self.features.get(feature)
            # branches = list(XY_data[feature].value_counts().keys())
            tree_node.children = dict()
            for i in range(len(branches)):
                X_data = XY_data[XY_data[feature] == branches[i]]
                if len(X_data) == 0:
                    category = XY_data[XY_data.columns[-1]].value_counts(ascending=False).keys()[0]
                    childNode = TreeNode(tree_node, None, None, category, None, None)
                    tree_node.children[branches[i]] = childNode
                    # return
                    # error, not should return, but continue
                    continue

                Y_data = X_data[X_data.columns[-1]]
                X_data.drop(X_data.columns[-1], axis=1, inplace=True)
                X_data.drop(feature, axis=1, inplace=True)
                childNode = TreeNode(tree_node, None, None, None, X_data, Y_data)
                tree_node.children[branches[i]] = childNode
                # print("feature: " + str(tree_node.feature) + " branch: " + str(branches[i]) + "\n")
                self.tree_generate(childNode)

            return

    def compute_entropy(self, Y):
    	"""计算信息熵"""
        ent = 0
        for cate in Y.value_counts(1):
            ent -= cate*np.log2(cate)
        return ent

3.3 CART算法

 分类树与回归树(classification and regression tree,CART)模型是应用广泛的决策树模型,它同样是由特征选择、树的生成及剪枝组成,既可以用于分类也可以用于回归。

 CART 算法是在给定输入随机变量 X X X 条件下输出随机变量 Y Y Y 的条件概率分布的学习方法。它假设决策树是 二叉树,内部结点特征的取值为“是”和“否”,左分支是取值为“是”的分支,右分支是取值为“否”的分支。这样的决策树等价于递归地二分每个特征,将输入空间即特征空间划分为有限个单元,并在这些单元上确定预测的概率分布,也就是在输入给定的条件下输出的条件概率分布。

 综合来说,CART算法由以下两步组成:

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

3.3.1 分类树的生成

 CART分类树用 基尼指数 最小化准则来选择最优特征,同时决定该特征的最优二值切分点。以下是分类树的生成流程:

CART分类树生成算法

 使用Python实现CART分类树算法代码

import pandas as pd
import numpy as np

from treeNode import TreeNode


class DecisionTreeCART:

    def __init__(self, X, Y):
        self.X_train = X
        self.Y_train = Y
        self.root_node = TreeNode(None, None, None, None, self.X_train, self.Y_train)
        self.features = self.get_features(self.X_train)
        self.tree_generate(self.root_node)

    def get_features(self, X_train_data):
        features = dict()
        for i in range(len(X_train_data.columns)):
            feature = X_train_data.columns[i]
            features[feature] = list(X_train_data[feature].value_counts().keys())

        return features

    def tree_generate(self, tree_node):
        X_data = tree_node.X_data
        Y_data = tree_node.Y_data
        # get all features of the data set
        features = list(X_data.columns)

        # 如果Y_data中的实例属于同一类,则置为单结点,并将该类作为该结点的类
        if len(list(Y_data.value_counts())) == 1:
            tree_node.category = Y_data.iloc[0]
            tree_node.children = None
            return

        # 如果特征集为空,则置为单结点,并将Y_data中最大的类作为该结点的类
        elif len(features) == 0:
            tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
            tree_node.children = None
            return

        # 否则,计算各特征的基尼指数,选择基尼指数最小的特征
        else:
            # gini_d = self.compute_gini(Y_data)
            XY_data = pd.concat([X_data, Y_data], axis=1)
            d_nums = XY_data.shape[0]
            min_gini_index = 1
            feature = None
            feature_value = None

            for i in range(len(features)):
                # 当前特征有哪些取值
                # v = self.features.get(features[i])
                v = XY_data[features[i]].value_counts().keys()
                # 当前特征的取值只有一种
                if len(v) <= 1:
                    continue
                # 当前特征的每一个取值分为是和不是两类
                for j in v:
                    Gini_index = 0
                    dv = XY_data[XY_data[features[i]] == j]
                    dv_nums = dv.shape[0]
                    dv_not = XY_data[XY_data[features[i]] != j]
                    dv_not_nums = dv_not.shape[0]
                    gini_dv = self.compute_gini(dv[dv.columns[-1]])
                    gini_dv_not = self.compute_gini(dv_not[dv_not.columns[-1]])
                    if d_nums == 0:
                        continue
                    Gini_index += dv_nums / d_nums * gini_dv + dv_not_nums / d_nums * gini_dv_not

                    if Gini_index < min_gini_index:
                        min_gini_index = Gini_index
                        feature = features[i]
                        feature_value = j

            if feature is None:
                tree_node.category = Y_data.value_counts(ascending=False).keys()[0]
                tree_node.children = None
                return
            tree_node.feature = feature

            # 否则,对当前特征的最小基尼指数取值,将Y_data分成两类子集,构建子结点
            # get all kinds of values of the current partition feature
            # branches = list({feature_value, "!"+str(feature_value)})
            # branches = list(XY_data[feature].value_counts().keys())
            tree_node.children = dict()
            for i in range(2):
                # 左分支,左分支为是的分支
                if i == 0:
                    X_data = XY_data[XY_data[feature] == feature_value]
                    X_data.drop(feature, axis=1, inplace=True)
                    child_name = feature_value
                # 右分支,右分支为否的分支
                else:
                    X_data = XY_data[XY_data[feature] != feature_value]
                    child_name = "!" + str(feature_value)
                # 可能出现节点没有数据的情况吗?
                # if len(X_data) == 0:
                #     print("I'm a bug")
                #     category = XY_data[XY_data.columns[-1]].value_counts(ascending=False).keys()[0]
                #     childNode = TreeNode(tree_node, None, None, category, None, None)
                #     tree_node.children[child_name] = childNode
                #     # return
                #     # error, not should return, but continue
                #     continue

                Y_data = X_data[X_data.columns[-1]]
                X_data.drop(X_data.columns[-1], axis=1, inplace=True)
                # X_data.drop(feature, axis=1, inplace=True)
                childNode = TreeNode(tree_node, None, None, None, X_data, Y_data)
                tree_node.children[child_name] = childNode
                # print("feature: " + str(tree_node.feature) + " branch: " + str(branches[i]) + "\n")
                self.tree_generate(childNode)

            return

    def compute_gini(self, Y):
        gini = 1
        for cate in Y.value_counts(1):
            gini -= cate*cate
        return gini
        

3.3.2 回归树的生成

 CART回归树用 平方误差 最小化准则来进行特征选择,生成的回归树称为 最小二乘回归树

 假设 X X X Y Y Y 是输入变量和输出变量,并且 Y Y Y 是连续的,给定训练数据集

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)}

 如果我们生成一棵回归树,将以上输入空间划分为 M M M 个单元 R 1 , R 2 , . . . , R M R_1, R_2, ... , R_M R1,R2,...,RM,并且每一个单元 R m R_m Rm 上都有一个输出值 c m c_m cm,则该回归树模型就可以表示为

f ( x ) = ∑ m = 1 M c m I ( x ∈ R m ) f(x) = \sum_{m=1}^{M} c_m I(x∈R_m) f(x)=m=1McmI(xRm)

 其中 I I I 为指示函数。当划分空间确定后,回归树对训练数据集的预测误差可以使用平方误差,即

M S E = ∑ x i ∈ R m ( y i − f ( x i ) ) 2 MSE = \sum_{x_i ∈ R_m} (y_i - f(x_i))^2 MSE=xiRm(yif(xi))2

 不难推导, c m c_m cm 的最优值 c ^ m \hat{c}_m c^m 就是 R m R_m Rm 上所有输入实例 x i x_i xi 的输出值 y i y_i yi 的均值,即

c ^ m = a v e ( y i ∣ x i ∈ R m ) \hat{c}_m = ave(y_i | x_i ∈ R_m) c^m=ave(yixiRm)

 那么如何对输入空间进行划分好呢?采用启发式的方法,首先,选择第 j j j 个变量 x j x^j xj 以及它的取值 s s s 作为切分变量(splitting variable)和 切分点(splitting point),并定义两个区域

R ( j , s ) = { x ∣ x j ≤ s } , R ( j , s ) = { x ∣ x j > s } R(j, s) = \{ x|x^j ≤ s \}, \quad R(j, s) = \{ x|x^j > s \} R(j,s)={xxjs},R(j,s)={xxjs}

 然后,寻找最优的切分变量 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 ∈ R_1(j, s)} (y_i - c_1)^2 + \min_{c_2} \sum_{x_i ∈ R_2(j, s)} (y_i - c_2)^2 ] (j,s)min[c1minxiR1(j,s)(yic1)2+c2minxiR2(j,s)(yic2)2]

 对于固定的变量 j j j 就可以求出最优切分点 s s s

c ^ 1 = a v e ( y i ∣ x i ∈ R 1 ( j , s ) ) , c ^ 2 = a v e ( y i ∣ x i ∈ R 2 ( j , s ) ) \hat{c}_1 = ave(y_i | x_i ∈ R_1(j, s)), \quad \hat{c}_2 = ave(y_i | x_i ∈ R_2(j, s)) c^1=ave(yixiR1(j,s)),c^2=ave(yixiR2(j,s))

 遍历所有的输入变量,找到最优的切分变量 j j j,构成一对 ( j , s ) (j, s) (j,s)。依此就可以把输入空间分成两个区域。递归地对每个区域重复以上过程,直到满足设定的停止条件为止,就生成了一棵回归树。

 以下是最小二乘回归树的生成算法流程:

最小二乘回归树

 示例:如下图所示的训练数据,试用平方误差损失准则生成一个二叉回归树。

最小二乘回归树1

 (1)先取 s = 1 s=1 s=1,那么以上训练数据集就被划分为两个区域: R 1 = { 1 } R_1 = \{ 1 \} R1={1} R 2 = { 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } \quad R_2=\{ 2,3,4,5,6,7,8,9,10 \} R2={2,3,4,5,6,7,8,9,10},它们的输出值为:

c 1 = 4.50 c_1 = 4.50 c1=4.50

c 2 = 1 9 ( 4.75 + 4.91 + 5.34 + 5.80 + 7.05 + 7.90 + 8.23 + 8.70 + 9.00 ) = 6.85 c_2 = \frac{1}{9} (4.75+4.91+5.34+5.80+7.05+7.90+8.23+8.70+9.00) = 6.85 c2=91(4.75+4.91+5.34+5.80+7.05+7.90+8.23+8.70+9.00)=6.85

 依此取s为不同的值,按照上面的计算方法,可以得到下面的表:

最小二乘回归树2
 (2)把 c 1 , c 2 c_1, c_2 c1,c2 的值代入到平方误差公式中,如

m ( s = 1 ) = ( 4.50 − 4.50 ) 2 + ( 4.75 − 6.85 ) 2 + ( 4.91 − 6.85 ) 2 + ( 5.34 − 6.85 ) 2 + ( 5.80 − 6.85 ) 2 + ( 7.05 − 6.85 ) 2 + ( 7.90 − 6.85 ) 2 + ( 8.23 − 6.85 ) 2 + ( 8.70 − 6.85 ) 2 + ( 9.00 − 6.85 ) 2 = 22.6 m(s=1) = {(4.50-4.50)^2} + {(4.75-6.85)^2+(4.91-6.85)^2+(5.34-6.85)^2+(5.80-6.85)^2+(7.05-6.85)^2+(7.90-6.85)^2+(8.23-6.85)^2+(8.70-6.85)^2+(9.00-6.85)^2}=22.6 m(s=1)=(4.504.50)2+(4.756.85)2+(4.916.85)2+(5.346.85)2+(5.806.85)2+(7.056.85)2+(7.906.85)2+(8.236.85)2+(8.706.85)2+(9.006.85)2=22.6

 然后可以得到下表:

最小二乘回归树3

 显然,当 s = 5 s=5 s=5 时, m ( s ) m(s) m(s) 最小。因此,最佳切分变量和切分点为 ( j = x i , s = 5 ) (j=x_i, s=5) (j=xi,s=5)

 (3)用选定的 ( j = x i , s = 5 ) (j=x_i, s=5) (j=xi,s=5) 划分数据空间,得到

R 1 = 1 , 2 , 3 , 4 , 5 R_1 = {1,2,3,4,5} R1=1,2,3,4,5

R 2 = 6 , 7 , 8 , 9 , 10 R_2 = {6,7,8,9,10} R2=6,7,8,9,10

 以上 R 1 , R 2 R_1, R_2 R1,R2 两个子区域的输出值分别为 c 1 = 5.06 , c 2 = 8.18 c_1 = 5.06, c_2 = 8.18 c1=5.06,c2=8.18

 (4)对以上 R 1 , R 2 R_1, R_2 R1,R2 两个子区域,重复调用算法流程中的步骤(1)、步骤(2)。比如对 R 1 R_1 R1 继续进行划分

x i x_i xi12345
y i y_i yi4.504.754.915.345.80

 同样将 s s s 取不同的值,得到不同的输出 c c c 如下表:

s s s12345
c 1 c_1 c14.504.634.724.885.06
c 1 c_1 c15.205.355.575.800.00

 计算出对应的平方误差:

s s s12345
m ( s ) m(s) m(s)0.670.430.190.371.06

 显然,s=3时,m(s)最小。

 之后按照以上同样的递归流程,最终得到以下的回归树:

最小二乘回归树4

 使用python来实现最小二乘回归树代码:


from numpy import *

# 载入数据
def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        # python3不适用:fltLine = map(float,curLine) 修改为:
        fltLine = list(map(float, curLine))  # 将每行映射成浮点数,python3返回值改变,所以需要
        dataMat.append(fltLine)
    return dataMat


# 切分数据集为两个子集
def binSplitDataSet(dataSet, feature, value):  # 数据集 待切分特征 特征值
    mat0 = dataSet[nonzero(dataSet[:, feature] > value)[0], :]
    mat1 = dataSet[nonzero(dataSet[:, feature] <= value)[0], :]
    # 下面原书代码报错 index 0 is out of bounds,使用上面两行代码
    # mat0 = dataSet[nonzero(dataSet[:, feature] > value)[0], :][0]
    # mat1 = dataSet[nonzero(dataSet[:, feature] <= value)[0], :][0]
    return mat0, mat1


# Tree结点类型:回归树
def regLeaf(dataSet):  # 生成叶结点,在回归树中是目标变量特征的均值
    return mean(dataSet[:, -1])


# 误差计算函数:回归误差
def regErr(dataSet):  # 计算目标的平方误差(均方误差*总样本数)
    return var(dataSet[:, -1]) * shape(dataSet)[0]


# 二元切分
def chooseBestSplit(dataSet, leafType=regLeaf, errType=regErr, ops=(0, 1)):
    # 切分特征的参数阈值,用户初始设置好
    tolS = ops[0]  # 允许的误差下降值
    tolN = ops[1]  # 切分的最小样本数
    # 若所有特征值都相同,停止切分
    if len(set(dataSet[:, -1].T.tolist()[0])) == 1:  # 倒数第一列转化成list 不重复
        return None, leafType(dataSet)  # 如果剩余特征数为1,停止切分1。
        # 找不到好的切分特征,调用regLeaf直接生成叶结点
    m, n = shape(dataSet)
    S = errType(dataSet)  # 最好的特征通过计算平均误差
    bestS = inf
    bestIndex = 0
    bestValue = 0
    for featIndex in range(n - 1):  # 遍历数据的每个属性特征
        # for splitVal in set(dataSet[:,featIndex]): python3报错修改为下面
        for splitVal in set((dataSet[:, featIndex].T.A.tolist())[0]):  # 遍历每个特征里不同的特征值
            mat0, mat1 = binSplitDataSet(dataSet, featIndex, splitVal)  # 对每个特征进行二元分类
            if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN): continue
            newS = errType(mat0) + errType(mat1)
            if newS < bestS:  # 更新为误差最小的特征
                bestIndex = featIndex
                bestValue = splitVal
                bestS = newS
    # 如果切分后误差效果下降不大,则取消切分,直接创建叶结点
    if (S - bestS) < tolS:
        return None, leafType(dataSet)  # 停止切分2
    mat0, mat1 = binSplitDataSet(dataSet, bestIndex, bestValue)
    # 判断切分后子集大小,小于最小允许样本数停止切分3
    if (shape(mat0)[0] < tolN) or (shape(mat1)[0] < tolN):
        return None, leafType(dataSet)
    return bestIndex, bestValue  # 返回特征编号和用于切分的特征值


# 构建tree
def createTree(dataSet, leafType=regLeaf, errType=regErr, ops=(0, 1)):
    # 数据集默认NumPy Mat 其他可选参数【结点类型:回归树,误差计算函数,ops包含树构建所需的其他元组】
    feat, val = chooseBestSplit(dataSet, leafType, errType, ops)
    if feat == None: return val  # 满足停止条件时返回叶结点值
    # 切分后赋值
    retTree = {}
    retTree['spInd'] = feat
    retTree['spVal'] = val
    # 切分后的左右子树
    lSet, rSet = binSplitDataSet(dataSet, feat, val)
    retTree['left'] = createTree(lSet, leafType, errType, ops)
    retTree['right'] = createTree(rSet, leafType, errType, ops)
    return retTree

if __name__ == "__main__":
    myDat = mat(loadDataSet('5.2test.txt'))
    print(createTree(myDat))
    

4 决策树的剪枝

 决策树生成算法递归地生成决策树,直到不能继续下去为止,却很容易发生 过拟合(overfitting) 现象,为了解决这个问题,提高决策树的 泛化能力,需要对决策树复杂度进行简化,简化决策树的过程叫做 剪枝(pruning) 。决策树剪枝的基本策略有 预剪枝(prepruning)后剪枝(post-pruning)

4.1 预剪枝(prepruning)

 预剪枝是指在决策树生成的过程中,对每一个节点在划分前先进行评估,若当前节点的划分不能带来决策树的泛化能力的提升,则停止划分,并将当前节点标记为叶子结点,否则继续进行划分。对是否停止划分需要设定条件,这些条件称为 停止标准(stopping criteria),常用的停止标准如下:

  • 树的深度
  • 叶子节点个数
  • 叶子节点的样本数
  • 信息增益、信息增益比等特征选择标准的阈值
  • 使用 验证集(validition set) 进行验证,看分割前后,精度是否有提高

 由于预剪枝是在构建决策树的同时进行剪枝处理,所以其训练时间开销较少,同时可以有效的降低过拟合的风险。但是,预剪枝有一个问题,会给决策树带来 欠拟合(underfitting) 的风险。

4.2 后剪枝(post-pruning)

 后剪枝则是从训练集生成一颗完整的决策树,然后自下而上地对内部节点进行考察,若将该节点对应的子树替换成叶子节点能够提升决策树的泛化能力,则将该子树替换为叶子节点。常用的方法是使用验证集进行验证,看替换前后,精度是否提高,这样的方法叫做 留出法(hold-out method)

 相对于预剪枝来说,后剪枝的欠拟合风险很小,同时,泛化能力往往要优于预剪枝,但是,因为后剪枝先要生成整个决策树后,然后才自底向上依次考察每个内部节点,所以训练时间长。以下介绍几种常用的后剪枝算法。

4.2.1 降低错误率剪枝(Reduced-Error Pruning,REP)

 REP算法是最简单的后剪枝方法之一 ,它用训练集合来训练模型,并需要一个验证集来对决策树进行剪枝。 通常取出可用数据集的1/3用作验证集,用剩余2/3作训练集合。 主要用于防止决策树的过度拟合。决定是否修剪某个结点的步骤如下:

  • (1)删除以该结点作为根结点的子树;
  • (2)使该结点成为叶子结点;
  • (3)赋予该结点关联的训练数据的最常见分类;
  • (4)当修剪后的树对于验证集的性能与原来的树相同或优于原来的树时,该结点才被删除;
  • (5)直到进一步修剪会减低验证集合的精度为止。

 在数据量较少的情况下很少应用REP方法。该方法趋于过拟合,这是因为训练数据集中存在的特性在剪枝过程中都被忽略,当剪枝数据集比训练数据集小得多时这个问题需要特别注意。

4.2.2 悲观误差剪枝(Pessimistic-Error Pruning,PEP,用于C4.5算法)

 PEP算法是在C4.5算法中提出的,该方法基于训练数据的误差评估,因此比起REP剪枝法,它不需要一个单独的验证数据集。但训练数据也带来误差偏向于训练集,因此需要加入修正1/2(惩罚因子,用常数0.5),是自上而下的修剪。 之所以叫“悲观”,是因为每个叶子结点都会主观加入一个惩罚因子,“悲观”地提高误判率。

PEP理论推导

 PEP剪枝法是根据剪枝前后的 误判率 来判定子树的修剪。该方法引入了统计学上连续修正的概念弥补REP中的缺陷,在评价子树的训练错误公式中添加了一个常数,假定每个叶子结点都自动对实例的某个部分进行错误的分类。

 把一棵子树的分类用一个叶子节点来替代的话,在训练集上的误判率肯定是上升的,但是在新数据上不一定。于是我们需要把子树的误判计算加上一个经验性的惩罚因子。对于一个叶子节点,它覆盖了 N N N 个样本,其中有 E E E 个错误,那么该叶子节点的误判率为 ( E + 0.5 ) / N (E+0.5)/N (E+0.5)/N,这个0.5就是惩罚因子。那么对于一棵拥有 L L L 个叶子结点的子树,其误判率估计为:

e = ∑ i ∈ L E i + 0.5 N i = ∑ i ∈ L ( E i + 0.5 ) ∑ i ∈ L N i = 0.5 L + ∑ E ∑ N e = \sum_{i\in L} \frac{E_i+0.5}{N_i} = \frac{\sum_{i\in L} (E_i + 0.5)}{\sum_{i\in L} N_i} = \frac{0.5L +\sum E}{\sum N} e=iLNiEi+0.5=iLNiiL(Ei+0.5)=N0.5L+E

 这样,我们可以看到一颗子树虽然具有多个子节点,但由于加上了惩罚因子,所以子树的误判率计算未必占到便宜。剪枝后内部节点变成了叶子节点,其误判个数 J J J 也需要加上一个惩罚因子,变成 J + 0.5 J+0.5 J+0.5。使用训练数据,子树总是比替换为一个叶节点后产生的误差小,但是使用修正后误差的计算方法却并非如此,而是 当子树的误判个数大过对应叶节点的误判个数一个标准差之后,就决定剪枝,即满足 被替换子树的误判数 - 标准差 > 新叶子的误判数,写成公式为:

E ( s u b _ e r r _ c o u n t ) − v a r ( s u b _ e r r _ c o u n t ) > E ( l e a f _ e r r _ c o u n t ) E(sub\_err\_count) - var(sub\_err\_count) > E(leaf\_err\_count) E(sub_err_count)var(sub_err_count)>E(leaf_err_count)

 这里出现的标准差如何计算呢?

 假定一棵子树错误分类一个样本的值为 1,正确分类一个样本的值为 0,该子树错误分类的概率(误判率)为 e e e,则每分类一个样本都可以近似看作是一次伯努利试验,覆盖 N N N 个样本的话就是做 N N N 次独立的伯努利试验,因此,我们可以把子树误判次数近似看成是服从 B ( N , e ) B(N, e) B(N,e) 的二项分布(二项分布就是重复 N N N 次独立的伯努利试验)。因此,我们很容易估计出该子树 误判次数均值和标准差

e x p ( s u b t r e e _ e r r _ c o u n t ) = N ∗ e exp(subtree\_err\_count) = N * e exp(subtree_err_count)=Ne

v a r ( s u b t r e e e r r c o u n t ) = N ∗ e ∗ ( 1 − e ) var(subtree_err_count) = \sqrt{N * e * (1-e)} var(subtreeerrcount)=Ne(1e)

 当然并不一定非要一个标准差,可以给定任意的置信区间,我们设定一定的显著性因子,就可以估算出误判次数的上下界。对于给定的置信区间,采用下界估计作为规则性能的度量。这样做的结果是对于大的数据集合,该剪枝策略能够非常接近观察精度,随着数据集合的减小,离观察精度越来越远。该剪枝方法尽管不是统计有效的,但是在实践中有效。

 PEP采用自顶向下的方式 将符合上述不等式的非叶子结点裁剪掉。该算法看作目前决策树后剪枝算法中精度比较高的算法之一,同时该算法仍存在一些缺陷。首先,PEP算法是唯一使用自顶向上剪枝策略的后剪枝算法,但这样的方法有时会导致某些不该被剪掉的某结点的子结点被剪掉。虽然PEP方法存在一些局限性,但是在实际应用中表现出了较高的精度。

PEP示例说明

 下面是一个简单的例子,T4 为根的整个这棵子树是不是可以被剪掉?

图7 PEP举例
  该树中每个节点有两个数字,左边的代表正确,右边代表错误。比如 T 4 T4 T4 这个节点,说明覆盖了训练集的 16 条数据,其中 9 条分类正确,7 条分类错误。 我们先来计算替换标准不等式中,关于子树的部分:子树有 3 个叶子节点,分别为 T 7 、 T 8 、 T 9 T7、T8、T9 T7T8T9,因此 L = 3 L=3 L=3;子树中一共有 16 条数据(根据刚才算法说明把三个叶子相加),所以 N = 16 N=16 N=16; 子树一共有 7 条错误判断,所以 E = 7 E=7 E=7; 那么根据 e e e 的公式 e = ( 7 + 0.5 × 3 ) / 16 = 8.5 / 16 = 0.53 e = (7 + 0.5 × 3) / 16 = 8.5 /16 = 0.53 e=(7+0.5×3)/16=8.5/16=0.53; 根据二项分布的标准差公式,标准差为 16 × 0.53 × ( 1 − 0.53 ) = 2.00 \sqrt{16×0.53×(1−0.53)} = 2.00 16×0.53×(10.53) =2.00; ​ 子树的错误数为 “所有叶子实际错误数 + 0.5 * 调整值” = 7 + 0.5 × 3 = 8.5 = 7 + 0.5 × 3 = 8.5 =7+0.5×3=8.5; 而把子树剪枝后,只剩下 T 4 T4 T4 T 4 T4 T4 的错误数为 7 + 0.5 = 7.5 7 + 0.5 = 7.5 7+0.5=7.5。 这样, 8.5 – 2 < 7.5 8.5 – 2 < 7.5 8.5–2<7.5, 因此不满足剪枝标准,不能用 T 4 T4 T4 替换整个子树。

4.2.3 最小误差剪枝(Minimum-Error Pruning,MEP)

MEP理论推导

 在结点 T T T处, 属于类别 k k k 的概率为

p k ( T ) = n k ( t ) + P r k × m N ( T ) + m p_k(T) = \frac{n_k(t) + Pr_k \times m}{N(T) + m} pk(T)=N(T)+mnk(t)+Prk×m

 其中, n k ( T ) n_k(T) nk(T) 表示该节点下的训练样本中被判断为 k k k 类的样本数量。 P r k Pr_k Prk 表示在该节点下类别 k k k 的先验概率。 N ( T ) N(T) N(T) 表示该节点下训练样本的数量。 m m m 是指先验概率对后验概率的影响

  • 如果 m ⟶ 0 m \longrightarrow 0 m0 P k ( T ) ⟶ n k ( T ) N ( T ) P_k(T) \longrightarrow \frac{n_k(T)}{N(T)} Pk(T)N(T)nk(T) ,则以后验概率为主;
  • 如果 m ⟶ ∞ m \longrightarrow \infty m P k ( T ) ⟶ P r k ( T ) P_k(T) \longrightarrow Pr_k(T) Pk(T)Prk(T),则以先验概率为主,此时 m m m 可以近似等价于类别总数 K K K

 在结点 T T T处,因为是最小误差剪枝,所以就是求最小预测错误率

E r r o r ( T ) = min ⁡ { 1 − P k ( T ) } Error(T) = \min \left \{ 1 - P_k(T) \right \} Error(T)=min{1Pk(T)}

E r r o r ( T ) = min ⁡ { 1 − n k ( T ) + P r k ( T ) × m N ( T ) + m } Error(T) = \min \left \{ 1 - \frac{n_k(T) + Pr_k(T) \times m}{N(T) + m} \right \} Error(T)=min{1N(T)+mnk(T)+Prk(T)×m}

E r r o r ( T ) = min ⁡ { N ( T ) − n k ( T ) + m ( 1 − P r k ( T ) ) N ( T ) + m } Error(T)= \min \left \{ \frac{N(T) - n_k(T) + m(1 - Pr_k(T))}{N(T) + m} \right \} Error(T)=min{N(T)+mN(T)nk(T)+m(1Prk(T))}

 如果先验概率是确定的,那么就是以 m m m 作为变量,得到对应的预测错误率的最小值。这里的 m m m 是求解出来的,而不是给定的,求解公式为

m ∗ = arg ⁡ min ⁡ m E r r o r ( T ) m^* = \arg \min_{m} Error(T) m=argmminError(T)

 假设 m = K m=K m=K K K K为类别的总个数。一般在无任何假设的情况下,各类别可以看作是等概率出现的,因此,先验概率为

P r k ( T ) = 1 K Pr_k(T) = \frac{1}{K} Prk(T)=K1

 这样,在结点 T T T处,预测错误率就可以表示为

E r r o r ( T ) = N ( T ) − n k ( T ) + K − 1 N ( T ) + K Error(T)= \frac{N(T) - n_k(T) + K - 1}{N(T) + K} Error(T)=N(T)+KN(T)nk(T)+K1

MEP剪枝步骤

 (1)计算剪枝前目标子树每个叶子结点的预测错误率

E r r o r ( L e a f i ) = N ( L e a f i ) − n k ( L e a f i ) + K − 1 N ( L e a f i ) + K Error(Leaf_i)= \frac{N(Leaf_i) - n_k(Leaf_i) + K - 1}{N(Leaf_i) + K} Error(Leafi)=N(Leafi)+KN(Leafi)nk(Leafi)+K1

 (2)计算剪枝前目标子树的预测错误率

   假如目标子树中一共有 L L L 个叶子结点

E r r o r ( L e a f ) = ∑ i = 1 L w i E r r o r ( L e a f i ) = ∑ i = 1 L N ( L e a f i ) N ( T ) E r r o r ( L e a f i ) Error(Leaf) = \sum_{i=1}^{L} w_i Error(Leaf_i) = \sum_{i=1}^{L} \frac{N(Leaf_i)}{N(T)} Error(Leaf_i) Error(Leaf)=i=1LwiError(Leafi)=i=1LN(T)N(Leafi)Error(Leafi)

   其中 w i = N ( L e a f i ) N ( T ) w_i = \frac{N(Leaf_i)}{N(T)} wi=N(T)N(Leafi) 为每个叶子节点对应的子树的权重。

 (3)计算剪枝后目标子树的预测错误率

E r r o r ( T ) = N ( T ) − n k ( T ) + K − 1 N ( T ) + K Error(T)= \frac{N(T) - n_k(T) + K - 1}{N(T) + K} Error(T)=N(T)+KN(T)nk(T)+K1

   这里就是将整个子树 T T T 判定为类 k k k,计算出对应的预测错误率。

 (4)比较剪枝前后的预测错误率,如果满足以下不等式,则剪枝;否则,不剪枝

E r r o r ( T ) < E r r o r ( L e a f ) Error(T) < Error(Leaf) Error(T)<Error(Leaf)

MEP示例说明

 使用4.2.2的例子。首先,对于 T 4 T4 T4 这棵子树,样本总数为16。它有 T 7 T7 T7 T 8 T8 T8 T 9 T9 T9三个叶子节点。 T 7 T7 T7的样本总数为 N = 9 N=9 N=9,分为正类(6个)和负类(3个),即 K = 2 K=2 K=2,故 m = 2 m=2 m=2,它的预测误差率为

E r r o r ( T 7 ) = 9 − 6 + 2 − 1 9 + 2 = 0.364 Error(T7) = \frac{9-6+2-1}{9+2} = 0.364 Error(T7)=9+296+21=0.364

 同理可以计算出

E r r o r ( T 8 ) = 5 − 3 + 2 − 1 5 + 2 = 0.429 Error(T8) = \frac{5-3+2-1}{5+2} = 0.429 Error(T8)=5+253+21=0.429

E r r o r ( T 9 ) = 2 − 0 + 2 − 1 2 + 2 = 0.75 Error(T9) = \frac{2-0+2-1}{2+2} = 0.75 Error(T9)=2+220+21=0.75

 然后,计算剪枝前目标子树的预测错误率

E r r o r ( L e a f ) = 9 16 × 0.364 + 5 16 × 0.429 + 2 16 × 0.75 = 0.433 Error(Leaf) = \frac{9}{16} \times 0.364 + \frac{5}{16} \times 0.429 + \frac{2}{16} \times 0.75 = 0.433 Error(Leaf)=169×0.364+165×0.429+162×0.75=0.433

 计算剪枝后目标子树的预测错误率

E r r o r ( T 4 ) = 16 − 9 + 2 − 1 16 + 2 = 0.444 Error(T4) = \frac{16-9+2-1}{16+2} = 0.444 Error(T4)=16+2169+21=0.444

 比较剪枝前后的预测错误率:由于 E r r o r ( T 4 ) > E r r o r ( L e a f ) Error(T4) > Error(Leaf) Error(T4)>Error(Leaf),说明对节点 T 4 T4 T4 剪枝后的预测错误率会提高,结论是不剪枝。

4.2.4 代价复杂度剪枝(Cost-Complexity Pruning,CCP,用于CART算法)

CCP理论推导

 CCP的剪枝算法分成两步:首先,从生成的决策树 T 0 T_0 T0 自下而上不断剪枝,知道根节点,得到一系列的子树 { T 0 , T 1 , . . . , T n } \{T_0, T_1,...,T_n\} {T0,T1,...,Tn};然后通过交叉验证法使用验证数据集对子树进行测试,得到最优的子树。

 第1步,剪枝,得到一个子树序列

 任意一棵子树 T T T 的好坏可以使用以下损失函数来衡量

C α ( T ) = C ( T ) + α ∣ T ∣ C_α (T) = C(T) + α \left | T \right | Cα(T)=C(T)+αT

 其中 C ( T ) C(T) C(T) 为子树 T T T 对训练数据集的预测误差(如基尼指数)。 ∣ T ∣ \left | T \right | T 为子树的叶子结点个数。 α ≥ 0 α ≥ 0 α0为参数,用于权衡训练数据的拟合程度与模型的复杂度。 C α ( T ) C_α(T) Cα(T) 为参数是 α α α 的子树的整体损失。

 对固定的 α α α,一定存在使损失函数 C α ( T ) C_α(T) Cα(T) 最小的子树,表示为 T α T_α Tα。当 α α α 大的时候,最优子树 T α T_α Tα 复杂度偏小;当 α α α 小的时候,最优子树 T α T_α Tα 复杂度偏大。极端情况:当 α = 0 α = 0 α=0 时,整体树是最优的。当 α → ∞ α → ∞ α 时,根结点组成的单结点树是最优的。

可以用递归的方法对树进行剪枝。将 α α α 从小增大,即 0 < α 0 < α 1 < . . . < α n < + ∞ 0<α_0<α_1<...<α_n<+∞ 0<α0<α1<...<αn<+,产生一系列区间 [ α i , α i + 1 ) , i = 0 , 1 , . . . , n [α_i, α_{i+1}), i=0,1,...,n [αi,αi+1),i=0,1,...,n;剪枝得到的子树序列对应着区间 α ∈ [ α i , α i + 1 ) , i = 0 , 1 , . . . , n α ∈ [α_i, α_{i+1}), i=0,1,...,n α[αi,αi+1),i=0,1,...,n 的最优子树序列 { T 0 , T 1 , . . . , T n } \{T_0, T_1,...,T_n\} {T0,T1,...,Tn},序列中的子树是嵌套的。

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

C α ( t ) = C ( t ) + α C_α (t) = C(t) + α Cα(t)=C(t)+α

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

C α ( T t ) = C ( T t ) + α ∣ T t ∣ C_α (T_t) = C(T_t) + α \left | T_t \right | Cα(Tt)=C(Tt)+αTt

 当 α = 0 α = 0 α=0 α α α 充分小时,有不等式

C α ( T t ) < C α ( t ) C_α (T_t) < C_α (t) Cα(Tt)<Cα(t)

 当 α α α 增大时,在某一 α α α

C α ( T t ) = C α ( t ) C_α (T_t) = C_α (t) Cα(Tt)=Cα(t)

 即

C ( T t ) + α ∣ T t ∣ = C ( t ) + α C(T_t) + α \left | T_t \right | = C(t) + α C(Tt)+αTt=C(t)+α

 得到

α = C ( t ) − C ( T t ) ∣ T t ∣ − 1 α = \frac{C(t) - C(T_t)}{\left | T_t \right | -1} α=Tt1C(t)C(Tt)

 当 α α α 继续增大时,以上不等式反向。只要 α = C ( t ) − C ( T t ) ∣ T t ∣ − 1 α = \frac{C(t) - C(T_t)}{\left | T_t \right | -1} α=Tt1C(t)C(Tt),树 T t T_t Tt t t t 有相同的损失函数值,而 t t t 的节点比 T t T_t Tt 少,因此 t t t T t T_t Tt更优,对 T t T_t Tt 进行剪枝。

 因此,对 T 0 T_0 T0 中的内一个内部节点 t t t,计算

g ( t ) = C ( t ) − C ( T t ) ∣ T t ∣ − 1 g(t) = \frac{C(t) - C(T_t)}{\left | T_t \right | -1} g(t)=Tt1C(t)C(Tt)

g ( t ) g(t) g(t) 表示剪枝前后整体损失函数的减少程度。在 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 α_1 α1 T 1 T_1 T1 为区间 [ α 1 , α 2 ) [α_1, α_2) [α1,α2) 的最优子树。如此剪枝下去,直至得到根结点。在这一过程中,不断地增加 α α α 的值,产生新的区间。

 第2步,利用交叉验证在子树序列中选择最优子树

 利用独立的验证数据集,测试子树序列 { T 0 , T 1 , . . . , T n } \{T_0, T_1,...,T_n\} {T0,T1,...,Tn} 中各棵子树的预测误差(平方误差或基尼指数)。预测误差最小的决策树是最优的决策树。在子树序列中,每棵子树都对应一个参数 { α 1 , α 2 , ⋅ ⋅ ⋅ , α n } \{α_1, α_2, · · · , α_n\} {α1,α2,⋅⋅⋅,αn}。所以,当最优子树 T k T_k Tk 确定时,对应的 α k α_k αk 也确定了,即得到最优决策树 T α T_α Tα

CCP的算法流程

图8 CCP剪枝算法流程

参考文章

1.《统计学习方法》作者 李航

2.《机器学习》作者 周志华

3.《机器学习》作者 Tom M.Mitchell

4.《机器学习实战》作者 Peter Harrington

5.决策树后剪枝算法(四)最小错误剪枝MEP

6.决策树的后剪枝 最小错误剪枝

7.深度理解hold-out Method(留出法)和K-fold Cross-Validation(k折交叉验证法)

8.机器学习算法之决策树分类

9.决策树

10.决策树(脑图)

11.Regression Tree 回归树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值