上一篇博客简单粗暴的介绍了基于ID3算法的决策树的代码实现,这篇博客具体介绍一下决策树中常用算法ID3、C4.5以及CART算法的原理,最终会给出基于C4.5的代码实现,CART算法代码实现在回归树那边。
一 、基本概念
1.信息量
决策树生成算法的背后的思想是利用一个度量信息量的方法,来衡量一种“数据划分“的优劣,从而生成一个判定序列。具体而言,它会不断寻找数据划分的方法,使得在该划分下能够获得信息量最大。
2.不确定性
在决策树的生成过程中,获得信息度量的方法是从反向来定义的:若一种划分能使数据“不确定性”减少的越多,就意味着该划分获得越多信息、这很符合直观的。关键问题就是在于应该如何度量数据的不确定性(或者说不纯度,Impurity).常见的度量标准有两个:信息熵(Entropy)和基尼系数(Gini Index)。
(1)信息熵
信息熵的定义为:
H
(
y
)
=
−
∑
k
=
1
K
p
k
l
o
g
p
k
H(y)=-\sum_{k=1}^{K}p_{k}log^{p_{k}}
H(y)=−∑k=1Kpklogpk
对于具体的、随机变量
y
y
y生成的数据集
D
=
{
y
1
,
.
.
.
y
N
}
D=\left \{y_{1},...y_{N}\right \}
D={y1,...yN}而言在实际操作中通常会利用经验熵来估计真正的信息熵:
H
(
y
)
=
H
(
D
)
=
−
∑
k
=
1
K
∣
C
k
∣
∣
D
∣
l
o
g
∣
C
k
∣
∣
D
∣
H(y)=H(D)=-\sum_{k=1}^{K}\frac{\left | C_{k} \right |}{\left | D \right |}log^{\frac{\left | C_{k} \right |}{\left | D \right |}}
H(y)=H(D)=−∑k=1K∣D∣∣Ck∣log∣D∣∣Ck∣
这里假设随机变量
y
y
y的取值空间为
{
c
1
,
.
.
.
c
N
}
\left \{c_{1},...c_{N}\right \}
{c1,...cN},
p
k
p_{k}
pk表示
y
y
y取
c
k
c_{k}
ck的概率:
p
k
=
p
p_{k}=p
pk=p;
∣
C
k
∣
\left | C_{k} \right |
∣Ck∣代表由随机变量
y
y
y中类别为
c
k
c_{k}
ck的样本的个数,
∣
D
∣
\left | D \right |
∣D∣代表
D
D
D的总样本个数。可以看到经验背后的思想就是“频率估计概率”。
可以证明当:
p
1
=
p
2
=
.
.
.
=
p
k
=
1
k
p_{1}=p_{2}=...=p_{k}=\frac{1}{k}
p1=p2=...=pk=k1
时,
H
(
y
)
H(y)
H(y)达到最大值
l
o
g
K
log^{_{K}}
logK,意味着随机变量
y
y
y取每一个类的概率都是一样的,亦
y
y
y完全没有规律可循,想要预测它的值只能靠运气。我们的目的是想让
y
y
y的你确信减小,即是让
y
y
y变得有规律方便我们的预测。
(2)基尼系数
基尼系数定义为:
G
i
n
i
(
y
)
=
∑
k
=
1
K
p
k
(
1
−
p
k
)
=
1
−
∑
k
=
1
K
p
k
2
Gini(y)=\sum_{k=1}^{K}p_{k}(1-p_{k})=1-\sum_{k=1}^{K}p_{k}^{2}
Gini(y)=∑k=1Kpk(1−pk)=1−∑k=1Kpk2
同样可以利用经验基尼系数来进行估计:
G
i
n
i
(
y
)
=
G
i
n
i
(
D
)
=
1
−
∑
k
=
1
K
(
[
D
k
]
∣
D
∣
)
2
Gini(y)=Gini(D)=1-\sum_{k=1}^{K}\left ( \frac{\left [ D_{k} \right ]}{\left | D \right |} \right )^{2}
Gini(y)=Gini(D)=1−∑k=1K(∣D∣[Dk])2
以及同样可以证明,当:
p
1
=
p
2
=
.
.
.
=
p
k
=
1
k
p_{1}=p_{2}=...=p_{k}=\frac{1}{k}
p1=p2=...=pk=k1
时
G
i
n
i
(
y
)
Gini(y)
Gini(y)取得最大值
1
−
1
K
1-\frac{1}{K}
1−K1。
3.信息增益
我们引入条件熵的概念来定义信息增益,所谓条件熵就是根据特征
A
A
A的不同取值对
y
y
y进行限制后,先对这些被限制的
y
y
y分别进行计算信息熵,再把这些信息熵根据特征值本身的概率加权求和,从而得到总的条件熵。
所以条件熵
H
(
y
∣
A
)
H(y|A)
H(y∣A)越小、意味着
y
y
y被
A
A
A限制后的总不确定性最小,从而意味着
A
A
A特征更能够帮助我们作出决策。其数学定义如下:
H
(
y
∣
A
)
=
∑
j
=
1
m
p
(
A
=
a
j
)
H
(
y
∣
A
=
a
j
)
H\left ( y|A \right )=\sum_{j=1}^{m}p\left ( A=a_{j} \right )H(y|A=a_{j})
H(y∣A)=∑j=1mp(A=aj)H(y∣A=aj)
其中
H
(
y
∣
A
=
a
j
)
=
−
∑
j
=
1
m
p
(
y
=
c
k
∣
A
=
a
j
)
l
o
g
p
(
y
=
c
k
∣
A
=
a
j
)
H\left ( y|A=a_{j} \right )=-\sum_{j=1}^{m}p\left ( y=c_{k}|A=a_{j} \right )log^{p(y=c_{k}|A=a_{j})}
H(y∣A=aj)=−∑j=1mp(y=ck∣A=aj)logp(y=ck∣A=aj)
同样可以使用经验条件熵来估计真正的条件熵:
H
(
y
∣
A
=
a
j
)
=
H
(
y
∣
D
)
=
−
∑
j
=
1
m
∣
D
j
∣
∣
D
∣
∑
k
=
1
K
∣
D
j
k
∣
∣
D
j
∣
l
o
g
∣
D
j
k
∣
∣
D
j
∣
H\left ( y|A=a_{j} \right)=H(y|D)=-\sum_{j=1}^{m}\frac{\left | D_{j} \right |}{\left | D \right |}\sum_{k=1}^{K}\frac{\left | D_{jk} \right |}{\left | D_{j} \right |}log^{\frac{\left | D_{jk} \right |}{\left | D_{j} \right |}}
H(y∣A=aj)=H(y∣D)=−∑j=1m∣D∣∣Dj∣∑k=1K∣Dj∣∣Djk∣log∣Dj∣∣Djk∣
因此信息增益被定义为:
g
(
y
,
A
)
=
H
(
y
)
−
H
(
y
∣
A
)
g(y,A)=H(y)-H(y|A)
g(y,A)=H(y)−H(y∣A)
这里的
g
(
y
,
A
)
g(y,A)
g(y,A)常被称为互信息(Mutual Information),决策树中的ID3算法就是利用它来作为特征选取标准的。但是如果简单的以
g
(
y
,
A
)
g(y,A)
g(y,A)作为标准的话,会存在偏向于选择取值较多的特征。这样我们最终得到的决策树将会是一颗矮胖的决策树,而我们希望得到的决策树应该比较深的(又不是太深),为了解决该问题,可以给
m
m
m(信息熵的个数)一个惩罚,由此可以得到信息增益比的概念。该概念就对应着C4.5算法:
g
R
(
y
,
A
)
=
g
(
y
,
A
)
H
a
(
y
)
)
g_{R}\left ( y,A \right )=\frac{g(y,A)}{H_{a}(y))}
gR(y,A)=Ha(y))g(y,A)
其中
H
A
(
y
)
H_{A}(y)
HA(y)是
y
y
y关于
A
A
A的熵,定义为:
H
A
(
y
)
=
−
∑
j
=
1
m
p
(
y
A
=
a
j
)
l
o
g
p
(
y
A
=
a
j
)
H_{A}\left ( y \right )=-\sum_{j=1}^{m}p\left ( y^{A}= a_{j}\right )log^{p(y^{A}=a_{j})}
HA(y)=−∑j=1mp(yA=aj)logp(yA=aj)
同样使用经验上来估计:
H
A
(
y
)
=
H
A
(
D
)
=
−
∑
j
=
1
m
∣
D
j
∣
∣
D
∣
l
o
g
∣
D
j
∣
∣
D
∣
H_{A}(y)=H_{A}(D)=-\sum_{j=1}^{m}\frac{\left | D_{j} \right |}{\left | D \right |}log^{\frac{\left | D_{j}\right |}{\left | D \right |}}
HA(y)=HA(D)=−∑j=1m∣D∣∣Dj∣log∣D∣∣Dj∣
类比上述过程,同样可以使用基尼系数来定义信息增益。条件基尼系数:
4.信息增益的例子
下面举个例子说明ID3算法。C4.5算法以及CART算法的信息增益具体如何计算
客户ID | 故障原因 | 故障类型 | 维修时长 | 满意度 |
---|---|---|---|---|
1 | 1 | 5 | 10.2 | 1 |
2 | 1 | 5 | 12 | 0 |
3 | 1 | 5 | 14 | 1 |
4 | 2 | 5 | 16 | 0 |
5 | 2 | 5 | 18 | 1 |
6 | 2 | 6 | 20 | 0 |
7 | 3 | 6 | 22 | 1 |
8 | 3 | 6 | 23 | 0 |
9 | 3 | 6 | 24 | 1 |
10 | 3 | 6 | 25 | 0 |
(1) ID3算法的信息增益
10个客户中5个满意,5个不满意,仅仅基于满意和满意的类别比例进行划分所需要的信息需求,计算方式为
H
(
y
)
H(y)
H(y)(满意度)
=
−
5
10
l
o
g
2
5
10
−
5
10
l
o
g
2
5
10
=
1
=-\frac{5}{10}log2^{\frac{5}{10}}-\frac{5}{10}log2^{\frac{5}{10}}=1
=−105log2105−105log2105=1
故障原因分别为1、2、3进行划分,将目标变量分别划分为3个子集,{D1、D2、D3},因此V=3。即故障原因为1的划分中,有2个不满意和1个满意。D1即指2个不满意和1个满意。故障原因为2的划分中,有1个不满意和2个满意。D2即指1个不满意和2个满意。故障原因为3的划分中,有2个不满意和2个满意。D3即指2个不满意和2个满意。具体公式如下:
H
(
y
∣
A
)
H(y|A)
H(y∣A)(满意度)
=
3
10
(
−
2
3
l
o
g
2
2
3
−
1
3
l
o
g
2
1
3
)
+
3
10
(
−
1
3
l
o
g
2
1
3
−
2
3
l
o
g
2
2
3
)
+
4
10
(
−
2
4
l
o
g
2
2
4
−
2
4
l
o
g
2
2
4
)
=
0.95097
=\frac{3}{10}(-\frac{2}{3}log2^{\frac{2}{3}}-\frac{1}{3}log2^{\frac{1}{3}})+\frac{3}{10}(-\frac{1}{3}log2^{\frac{1}{3}}-\frac{2}{3}log2^{\frac{2}{3}})+\frac{4}{10}(-\frac{2}{4}log2^{\frac{2}{4}}-\frac{2}{4}log2^{\frac{2}{4}})=0.95097
=103(−32log232−31log231)+103(−31log231−32log232)+104(−42log242−42log242)=0.95097
因此信息增益:
g
(
y
,
A
)
=
1
−
0.95097
=
0.04903
g(y,A)=1-0.95097=0.04903
g(y,A)=1−0.95097=0.04903
对于不是二分类的子集,比如故障原因的每一个子集求得信息增益,然后相加得到总的信息增益
障原因和故障类型两个变量都是离散型变量,按上述方式即可求得信息增益,但修障时长为连续型变量,对于连续型变量该怎样计算信息增益呢?(此处的方法来自于C4.5)只需将连续型变量由小到大递增排序,取相邻两个值的中点作为分裂点,然后按照离散型变量计算信息增益的方法计算信息增益,取其中最大的信息增益作为最终的分裂点。如求修障时长的信息增益,首先将修障时长递增排序,即10.2、12、14、16、18、20、22、23、24、25,取相邻两个值的中点,如10.2和12,中点即为(10.2+12)/2=11.1,同理可得其他中点,分别为11.1、13、15、17、19、21、22.5、23.5、24.5。对每个中点都离散化成两个子集,如中点11.1,可以离散化为两个<=11.1和>11.1两个子集,然后按照离散型变量的信息增益计算方式计算其信息增益,如中点11.1的信息增益计算过程如下:
首先计算:
H
(
y
)
H(y)
H(y)(满意度)
=
−
5
10
l
o
g
2
5
10
−
5
10
l
o
g
2
5
10
=
1
=-\frac{5}{10}log2^{\frac{5}{10}}-\frac{5}{10}log2^{\frac{5}{10}}=1
=−105log2105−105log2105=1
经验条件熵:
H
(
y
∣
A
)
H(y|A)
H(y∣A)(满意度)
=
1
10
(
−
1
1
l
o
g
2
1
1
−
0
1
l
o
g
2
0
1
)
+
9
10
(
−
4
9
l
o
g
2
4
9
−
5
9
l
o
g
2
5
9
)
=
0.891968
=\frac{1}{10}(-\frac{1}{1}log2^{\frac{1}{1}}-\frac{0}{1}log2^{\frac{0}{1}})+\frac{9}{10}(-\frac{4}{9}log2^{\frac{4}{9}}-\frac{5}{9}log2^{\frac{5}{9}})=0.891968
=101(−11log211−10log210)+109(−94log294−95log295)=0.891968
信息增益
g
(
y
,
A
)
=
1
−
0.891968
=
0.10803
g(y,A)=1-0.891968=0.10803
g(y,A)=1−0.891968=0.10803
计算中点13的信息增益计算过程如下:
首先计算:
H
(
y
)
H(y)
H(y)(满意度)
=
−
5
10
l
o
g
2
5
10
−
5
10
l
o
g
2
5
10
=
1
=-\frac{5}{10}log2^{\frac{5}{10}}-\frac{5}{10}log2^{\frac{5}{10}}=1
=−105log2105−105log2105=1
经验条件熵:
H
(
y
∣
A
)
H(y|A)
H(y∣A)(满意度)
=
2
10
(
−
1
2
l
o
g
2
1
2
−
1
2
l
o
g
2
1
2
)
+
8
10
(
−
4
8
l
o
g
2
4
8
−
4
8
l
o
g
2
4
8
)
=
1
=\frac{2}{10}(-\frac{1}{2}log2^{\frac{1}{2}}-\frac{1}{2}log2^{\frac{1}{2}})+\frac{8}{10}(-\frac{4}{8}log2^{\frac{4}{8}}-\frac{4}{8}log2^{\frac{4}{8}})=1
=102(−21log221−21log221)+108(−84log284−84log284)=1
信息增益
g
(
y
,
A
)
=
1
−
1
=
0
g(y,A)=1-1=0
g(y,A)=1−1=0
同理分别求得各个中点的信息增益,选取其中最大的信息增益作为分裂点,如取中点11.1。然后与故障原因和故障类型的信息增益相比较,取最大的信息增益作为第一个树叉的分支,此例中选取了故障原因作为第一个分叉。按照同样的方式继续构造树的分支。
总之,信息增益的直观解释为选取按某个自变量划分所需要的期望信息,该期望信息越小,划分的纯度越高。因为对于某个分类问题而言,Info(D)都是固定的,而信息增益Gain(A)=Info(D)-InfoA(D) 影响信息增益的关键因素为:-InfoA(D),即按自变量A进行划分,所需要的期望信息越小,整体的信息增益越大,越能将分类变量区分出来。
由于信息增益选择分裂属性的方式会倾向于选择具有大量值的属性(即自变量),如对于客户ID,每个客户ID对应一个满意度,即按此变量划分每个划分都是纯的(即完全的划分,只有属于一个类别),客户ID的信息增益为最大值1。但这种按该自变量的每个值进行分类的方式是没有任何意义的。为了克服这一弊端,有人提出了采用增益率(GainRate)来选择分裂属性
求得
H
A
(
故
障
原
因
)
=
−
3
10
l
o
g
2
3
10
−
3
10
l
o
g
2
3
10
−
4
10
l
o
g
2
4
10
=
1.57
H_{A(故障原因)}=-\frac{3}{10}log2^{\frac{3}{10}}-\frac{3}{10}log2^{\frac{3}{10}}-\frac{4}{10}log2^{\frac{4}{10}}=1.57
HA(故障原因)=−103log2103−103log2103−104log2104=1.57
求得信息增益率为:
g
R
(
y
,
A
)
=
g
(
y
,
A
)
H
a
(
y
)
)
=
0.9507
1.5709
=
0.6051
g_{R}\left ( y,A \right )=\frac{g(y,A)}{H_{a}(y))}=\frac{0.9507}{1.5709}=0.6051
gR(y,A)=Ha(y))g(y,A)=1.57090.9507=0.6051
同理可以求得其他自变量的增益率。并且选取最大的信息增益率作为分裂属性
CART
CART算法选择分裂属性的方式是比较有意思的,首先计算不纯度,然后利用不纯度计算Gini指标。以满意度预警模型为例,计算自变量故障原因的Gini指标时,先按照故障原因可能的子集进行划分,即可以将故障原因具体划分为如下的子集:{1,2,3}、{1,2}、{1,3}、{2,3}、{1}、{2}、{3}、{},共计8(2V)个子集。由于{1,2,3}和{}对于分类来说没有任何意义,因此实际分为2V-2共计6个有效子集。然后计算这6个有效子集的不纯度和Gini指标,选取最小的Gini指标作为分裂属性。
对应到满意度模型中,A为自变量,即故障原因、故障类型、修障时长。D代表满意度,D1和D2分别为按变量A的子集所划分出的两个不同元组,如按子集{1,2}划分,D1即为故障原因属于{1,2}的满意度评价,共有6条数据,D2即故障原因不属于{1,2}的满意度评价,共有3条数据。计算子集{1,2}的不纯度时,即Gini(D1),在故障原因属于{1,2}的样本数据中,分别有3条不满意和3条满意的数据,因此
经验基尼系数为:
G
i
n
i
(
y
)
=
1
−
(
3
6
)
2
−
(
3
6
)
2
=
0.5
Gini(y)=1-(\frac{3}{6})^{2}-(\frac{3}{6})^{2}=0.5
Gini(y)=1−(63)2−(63)2=0.5
以故障原因为例,计算过程如下:
G
i
n
i
{
1
,
2
}
=
6
10
(
1
−
(
3
6
)
2
-
(
3
6
)
2
)
+
4
10
(
1
−
(
2
4
)
2
−
(
2
4
)
2
)
=
0.5
Gini_{\left \{ 1,2 \right \}}=\frac{6}{10}(1-(\frac{3}{6})^{2}-(\frac{3}{6})^{2})+\frac{4}{10}(1-(\frac{2}{4})^{2}-(\frac{2}{4})^{2})=0.5
Gini{1,2}=106(1−(63)2-(63)2)+104(1−(42)2−(42)2)=0.5
因此基尼增益为:
G
i
n
i
(
y
,
A
)
=
0.5
−
0.5
=
0
Gini(y,A)=0.5-0.5=0
Gini(y,A)=0.5−0.5=0
计算子集故障原因={1,3}的子集的Gini指标时,D1和D2分别为故障原因={1,3}的元组共计7条数据,故障原因不属于{1,3}的元组即故障原因为2的数据,共计3条数据。详细计算过程如下:
经验基尼系数为:
G
i
n
i
(
y
)
=
1
−
(
4
7
)
2
−
(
3
7
)
2
=
0.4897
Gini(y)=1-(\frac{4}{7})^{2}-(\frac{3}{7})^{2}=0.4897
Gini(y)=1−(74)2−(73)2=0.4897
G
i
n
i
{
1
,
3
}
=
7
10
(
1
−
(
4
7
)
2
-
(
3
7
)
2
)
+
3
10
(
1
−
(
1
3
)
2
−
(
2
3
)
2
)
=
0.47619
Gini_{\left \{ 1,3\right \}}=\frac{7}{10}(1-(\frac{4}{7})^{2}-(\frac{3}{7})^{2})+\frac{3}{10}(1-(\frac{1}{3})^{2}-(\frac{2}{3})^{2})=0.47619
Gini{1,3}=107(1−(74)2-(73)2)+103(1−(31)2−(32)2)=0.47619
因此基尼增益为:
G
i
n
i
(
y
,
A
)
=
0.4897
−
0.47619
=
0.0136
Gini(y,A)=0.4897-0.47619=0.0136
Gini(y,A)=0.4897−0.47619=0.0136
同理可以计算出故障原因的每个子集的Gini指标,按同样的方式还可以计算故障类型和修障时长每个子集的Gini指标,选取其中最小的Gini指标作为树的分支( Gini(D)越小,则数据集D的纯度越高)。连续型变量的离散方式与信息增益中的离散方式相同。
二 、代码实现
上一篇博文对ID3的算法进行了实现,在ID3算法的基础上要实现C4.5的算法只需要修改其中的一个函数即可,将函数中的信息增益选择最佳特征改为使用信息增益比选择最佳特征,需要修改的函数以及代码如下:
"""
#遍历整个数据集,循环计算香农熵和splitDataSet(),找到最好的特征划分方式来划分数据集
#该函数实现了选取特征,划分数据集,计算得出最好的划分数据集的特征
#数据集dataSet必须满足两个要求:
1.数据必须是一种由列表元素组成的列表,而且所有的列表元素都要具有相同的数据长度
2.数据的最后一列或者每个实例的最后一个元素是当前实例的标签
"""
def chooseBestFeatureToSplit(dataSet):
numFeatures=len(dataSet[0])-1
baseEntropy=calcShannonEnt(dataSet) #计算数据集的原始香农熵,保存最初的无序度量值,用于与划分完之后的数据集计算的熵值进行比较
bestInfoGain=0.0;bestFeature=-1
# 遍历数据集中的所有特征,使用列表推导创建新的列表,将数据集中所有第i个特征或者所有可能存在的值写入新的list中
for i in range(numFeatures):
featList=[example[i] for example in dataSet] #提取特征的每列数据
uniqueVals=set(featList)
newEntropy = 0.0
newEntropy1 = 0.0
#遍历当前特征值中所有唯一属性值,对每个唯一属性值划分一次数据集
for value in uniqueVals:
subDataSet=splitDataSet(dataSet,i,value)
prob=len(subDataSet)/float(len(dataSet))
newEntropy+=prob*calcShannonEnt(subDataSet)
infoGain=baseEntropy-newEntropy #使用最初的原始数据集的熵值减去经过特征划分数据集的熵值,得到按照第一种特征划分的熵值差值
# 计算y关于A的熵,用于计算C4.5算法中的信息增益比
featureCounts = {}
for featVec in dataSet:
currentFeature = featVec[i] # 键值是字典的最后一列数值
if currentFeature not in featureCounts.keys(): # 如果当前的键值不存在,则扩展字典,并将当前的键值加入字典
featureCounts[currentFeature] = 0 # 将该键加入到字典中,并给值附为0
featureCounts[currentFeature] += 1 # 将该键的值+1,最终得到每种分类的次数
for key in featureCounts.keys():
prob1 = float(featureCounts[key]) / float(len(dataSet))
newEntropy1 -= prob1 * log(prob1,2)
if(infoGain/newEntropy1>bestInfoGain): #将每次按照原始数据集的熵值与特征划分的熵值之差来判断哪种特征划分的熵值最高,
bestInfoGain=infoGain
bestFeature=i #比较所有特征的信息增益,返回最好特征划分的索引值
return bestFeature
基于CART的代码实现在后面的回归中会贴上代码
三 、训练结果
这是基于C4.5算法得到的二叉树模型