决策树原理以及python实现
1. 决策树的概念
决策树是一个分类与回归的算法,由Quinlan在1986年提出了决策树的ID3算法,后来在1993年提出了C4.5算法,在1984年Breiman等人提出了CART算法来实现建立决策树。决策树通常包括三个部分:特征选择、决策树的构建、决策树剪枝。本文主要针对ID3和C4.5算法原理来了解决策树的构建剪枝和python实现。
1.1 什么是决策树
决策树是一种对实例进行分类的树形结构。用决策树分类,从根节点开始,对实例的某一特征进行测试。根据测试结果,将实力分配到子节点。直到分配到叶节点。最后将实例分配到叶节点的类中。我们可以举一个生活中的例子来看一下什么是决策树。
这就是一颗典型的二叉决策树,决策树也可以是多个分叉的。根据不同的输入,输出不同的结果。我们再看一个例子。
这是一个申请贷款人的一些信息。我们需要根据这些信息来建造一个新的决策树。所以第一步,我们要根据某个特征来进行判断,进行分支操作。那么问题来了,我们要选择的是哪个特征呢?具体的分配规则是什么?在分配之前,我们先来了解一下几个概念。
1.2 决策树的相关概念
给定数据集 D :
D
=
{
(
x
1
,
y
1
)
,
(
x
2
,
y
2
)
,
.
.
.
.
,
(
x
N
,
y
N
)
}
D = \lbrace(x_1,y_1),(x_2,y_2),....,(x_N,y_N)\rbrace
D={(x1,y1),(x2,y2),....,(xN,yN)}
其中,
x
i
=
(
x
i
(
1
)
,
x
i
(
2
)
,
x
i
(
3
)
,
x
i
(
n
)
)
x_i = (x_i^{(1)}, x_i^{(2)},x_i^{(3)},x_i^{(n)})
xi=(xi(1),xi(2),xi(3),xi(n)) 是输入的实例,n是实例的特征种类,N是实例的数量。所谓的特征就指的是数据集中的某一列属性。比如某个人所属的省份。特征种类就是这个特征有多少个不同的取值。
熵:表示随机变量不确定性的度量。设X是一个有限个值的离散随机变量,其概率分布为
P
(
X
=
x
i
)
=
p
i
,
i
=
1
,
2
,
3
,
4
。
。
。
n
P(X=x_i) = p_i,i = 1,2,3,4。。。n
P(X=xi)=pi,i=1,2,3,4。。。n
则随机变量X的熵定义为:
H
(
X
)
=
−
∑
i
=
1
n
p
i
log
p
i
H(X) = -\sum_{i=1}^np_i\log p_i
H(X)=−i=1∑npilogpi
熵的理解:如果X有多个可选的值,则每个值的概率是在0-1之间,对每个值的概率求 p i log p i p_i\log p_i pilogpi 之后进行叠加计算。log在0-1之间的取值为负,所以需要在整体的等式前面添加一个符号,方便观察和计算。熵的值越大,则表明X的不确定性越大。
信息增益:表示得知特征X的信息而使得类 Y的不确定性减少的程度。特征A对数据集D的信息增益
g
(
D
,
A
)
g(D,A)
g(D,A),定义为集合D的经验熵
H
(
D
)
H(D)
H(D) 与特征A 在给定条件下D的经验条件熵
H
(
D
∣
A
)
H(D|A)
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(D∣A)
信息增益率:定义为信息增益
g
(
D
,
A
)
g(D,A)
g(D,A),与训练数据D关于特征A的值的熵
H
A
(
D
)
H_A(D)
HA(D) 之比,即
g
R
(
D
,
A
)
=
g
R
(
D
,
A
)
H
A
(
D
)
g_R(D,A) = \frac{g_R(D,A)}{H_A(D)}
gR(D,A)=HA(D)gR(D,A)
2. 决策树的构建
构建决策树有三种算法,分别是ID3、C4.5和CART,本文主要讲解ID3和C4.5两种算法,其中ID3算法是基于信息增益来构建决策树的。C4.5算法是基于信息增益率来构建决策树。
2.1 特征选择方法
决策树学习应用信息增益准则选择特征,给定训练数据集D和特征A,经验熵H(D)表示对数据集D进行分类的不确定性,而经验条件熵H(D|A)表示在特征A给定的条件下对数据集D进行分类的不确定性,那么他们的差,即信息增益,就表示由于特征A使得对数据集D的分类的不确定性减少的程度。显然,对于数据集De而言,信息增益依赖于特征,不同的特征有着不同的信息增益,所以信息增益大的对数据来说有更强的分类能力。
2.2 ID3算法
ID3算法是基于信息增益来构建决策树的。下面我们来看一下算法流程:
为了方面描述,我们给出以下的定义。设训练数据为D,|D| 表示样本的容量。设有K个类
C
K
C_K
CK,
∣
C
K
∣
|C_K|
∣CK∣为属于类
C
K
C_K
CK的样本个数。设特征A有n个不同的取值,根据特征A的取值,将数据集D划分为n个子集
D
1
,
D
2
,
D
3
.
.
.
D
n
D_1,D_2,D_3...D_n
D1,D2,D3...Dn。
∣
D
i
∣
|D_i|
∣Di∣为
D
i
D_i
Di样本的个数。记自己
D
i
D_i
Di中属于类
C
K
C_K
CK的样本的集合为
D
i
k
D_ik
Dik,
∣
D
i
k
∣
|D_ik|
∣Dik∣为
D
i
k
D_ik
Dik的样本个数。
输入数据:数据集D和特征A
- 计算数据集的经验熵
H ( D ) = − ∑ k = 1 K ∣ C K ∣ ∣ D ∣ l o g 2 ∣ C K ∣ ∣ D ∣ H(D) = -\sum_{k=1}^K\frac{|C_K|}{|D|}log_2 \frac{|C_K|}{|D|} H(D)=−k=1∑K∣D∣∣CK∣log2∣D∣∣CK∣ - 计算特征A对数据的经验条件熵H(D|A)
H ( D ∣ A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ H(D|A) = -\sum_{i=1}^n\frac{|D_i|}{|D|}\sum_{k=1}^K\frac{|D_{ik}|}{|D_i|}log_2 \frac{|D_{ik}|}{|D_i|} H(D∣A)=−i=1∑n∣D∣∣Di∣k=1∑K∣Di∣∣Dik∣log2∣Di∣∣Dik∣ - 计算信息增益
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D,A) = H(D) - H(D|A) g(D,A)=H(D)−H(D∣A)
2.2.1 ID3算法例题
对上面的表进行信息增益算法
解:
- 首先计算经验熵H(D)
H ( D ) = − 9 15 l o g 2 9 15 − 6 15 l o g 2 6 15 = 0.971 H(D) = -\frac{9}{15}log_2\frac{9}{15} - \frac{6}{15}log_2\frac{6}{15} = 0.971 H(D)=−159log2159−156log2156=0.971 - 计算各个特征的经验条件熵
年龄:
H ( D ∣ A ) = 5 15 ∗ ( − 2 5 l o g 2 2 5 − 3 5 l o g 2 3 5 ) + 5 15 ∗ ( − 3 5 l o g 2 3 5 − 2 5 l o g 2 2 5 ) + 5 15 ∗ ( − 4 5 l o g 2 4 5 − 1 5 l o g 2 1 5 ) = 0.888 H(D|A) = \frac{5}{15}*\bigg(-\frac{2}{5}log_2\frac{2}{5} - \frac{3}{5}log_2\frac{3}{5}\bigg)+\frac{5}{15}*\bigg(-\frac{3}{5}log_2\frac{3}{5} - \frac{2}{5}log_2\frac{2}{5}\bigg)+\frac{5}{15}*\bigg(-\frac{4}{5}log_2\frac{4}{5} - \frac{1}{5}log_2\frac{1}{5}\bigg) = 0.888 H(D∣A)=155∗(−52log252−53log253)+155∗(−53log253−52log252)+155∗(−54log254−51log251)=0.888
有工作:
H ( D ∣ A ) = 5 15 ∗ 0 + 10 15 ∗ ( − 4 10 l o g 2 4 10 − 6 10 l o g 2 6 10 ) = 0.647 H(D|A) = \frac{5}{15}*0+\frac{10}{15}*\bigg(-\frac{4}{10}log_2\frac{4}{10} - \frac{6}{10}log_2\frac{6}{10}\bigg) = 0.647 H(D∣A)=155∗0+1510∗(−104log2104−106log2106)=0.647
有自己的房子:
H ( D ∣ A ) = 6 15 ∗ 0 + 9 15 ∗ ( − 3 9 l o g 2 3 9 − 6 9 l o g 2 6 9 ) = 0.551 H(D|A) = \frac{6}{15}*0+\frac{9}{15}*\bigg(-\frac{3}{9}log_2\frac{3}{9} - \frac{6}{9}log_2\frac{6}{9}\bigg) = 0.551 H(D∣A)=156∗0+159∗(−93log293−96log296)=0.551
信贷情况:
H ( D ∣ A ) = 5 15 ∗ ( − 1 5 l o g 2 1 5 − 4 5 l o g 2 4 5 ) + 6 15 ∗ ( − 4 6 l o g 2 4 6 − 2 6 l o g 2 2 6 ) + 4 15 ∗ 0 = 0.608 H(D|A) = \frac{5}{15}*\bigg(-\frac{1}{5}log_2\frac{1}{5} - \frac{4}{5}log_2\frac{4}{5}\bigg)+\frac{6}{15}*\bigg(-\frac{4}{6}log_2\frac{4}{6} - \frac{2}{6}log_2\frac{2}{6}\bigg)+\frac{4}{15}*0= 0.608 H(D∣A)=155∗(−51log251−54log254)+156∗(−64log264−62log262)+154∗0=0.608 - 计算信息增益
年龄:
g ( D , A 1 ) = H ( D ) − H ( D ∣ A ) = 0.971 − 0.888 = 0.083 g(D,A_1) = H(D) - H(D|A) = 0.971-0.888 = 0.083 g(D,A1)=H(D)−H(D∣A)=0.971−0.888=0.083
有工作:
g ( D , A 2 ) = H ( D ) − H ( D ∣ A ) = 0.971 − 0.647 = 0.324 g(D,A_2) = H(D) - H(D|A) = 0.971-0.647= 0.324 g(D,A2)=H(D)−H(D∣A)=0.971−0.647=0.324
有自己的房子:
g ( D , A 3 ) = H ( D ) − H ( D ∣ A ) = 0.971 − 0.551 = 0.420 g(D,A_3) = H(D) - H(D|A) = 0.971-0.551= 0.420 g(D,A3)=H(D)−H(D∣A)=0.971−0.551=0.420
信贷情况:
g ( D , A 3 ) = H ( D ) − H ( D ∣ A ) = 0.971 − 0.608 = 0.363 g(D,A_3) = H(D) - H(D|A) = 0.971-0.608= 0.363 g(D,A3)=H(D)−H(D∣A)=0.971−0.608=0.363
最后,根据各个信息增益的比较,我们选取特征 A 3 A_3 A3 即有自己的房子,作为最优特征,进行第一次划分。
筛选出特征 A 3 A_3 A3后,重新进行上面的计算(因为数据集已经变化了),选出第二阶特征。直到全部的特征划分完毕。这是一个递归的流程。
2.2.2 ID3算法缺点
- 使用ID3算法构建决策树时, 若出现各属性值取值数分布偏差大的情况, 分类精度会大打折扣
- ID3算法本身并未给出处理连续数据的方法。
- ID3算法不能处理带有缺失值的数据集, 故在算法挖掘之前需要对数据集中的缺失值进行预处理。
- ID3算法只有树的生成,所以该算法生成的树容易产生过拟合
2.3 C4.5算法
采用信息增益来进行特征的划分,会导致算法偏向于取值较多的特征。可以采用信息增益率来对这一问题进行处理。同样是上面的实例。信息增益率的公式如下
g
R
(
D
,
A
)
=
g
(
D
,
A
)
−
∑
i
=
1
n
∣
D
i
∣
∣
D
∣
log
2
∣
D
i
∣
∣
D
∣
g_R(D,A) = \frac{g(D,A)}{-\sum_{i=1}^n\frac{|D_i|}{|D|}\log_2 \frac{|D_i|}{|D|}}
gR(D,A)=−∑i=1n∣D∣∣Di∣log2∣D∣∣Di∣g(D,A)
其中:
H
A
(
D
)
=
−
∑
i
=
1
n
∣
D
i
∣
∣
D
∣
log
2
∣
D
i
∣
∣
D
∣
H_A(D) = -\sum_{i=1}^n\frac{|D_i|}{|D|}\log_2 \frac{|D_i|}{|D|}
HA(D)=−i=1∑n∣D∣∣Di∣log2∣D∣∣Di∣叫做特征A的分裂信息。
流程:
输入:数据集D 特征集A 和阈值
ε
\varepsilon
ε
输出:决策树T
- 如果D中所有实例属于同一类 C k C_k Ck,则设置T为单结点树,并将 C k C_k Ck作为该结点的类返回。
- 如果A = ∅ \emptyset ∅,则设置T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类,返回T。
- 否则,按照上面信息增益率的公式计算各个特征的信息增益率,选择信息增益率最大的特征 A g A_g Ag。
- 如果 A g A_g Ag的信息增益率比阈值 ε \varepsilon ε小,则设置T为单结点树,并将D中实例数最大的类 C k C_k Ck作为该结点的类,返回T。
- 否则,对 A g A_g Ag的每一个可能值 a i a_i ai,依 A g = a i A_g = a_i Ag=ai将D分为子集若干非空 D i D_i Di,将 D i D_i Di中实例数最大的类作为标记,构建子节点,由结点及其子节点构成树T,返回T。
- 对结点i,以 D i D_i Di为训练集,以 A − A g A-{A_g} A−Ag为特征集,递归的调用1-5步骤。得到子树 T i T_i Ti,返回 T i T_i Ti。
2.3.1 C4.5算法例题
在不考虑
ε
\varepsilon
ε值的情况下对上面的例题进行信息增益率的求解。
解:
计算每个特征的分裂信息
年龄:
H
A
1
(
D
)
=
−
5
15
l
o
g
2
5
15
−
5
15
l
o
g
2
5
15
−
5
15
l
o
g
2
5
15
=
1.585
H_{A_1}(D) = -\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
HA1(D)=−155log2155−155log2155−155log2155=1.585
有工作:
H
A
2
(
D
)
=
−
5
15
l
o
g
2
5
15
−
10
15
l
o
g
2
10
15
=
0.918
H_{A_2}(D) = -\frac{5}{15}log_2\frac{5}{15} -\frac{10}{15}log_2\frac{10}{15} = 0.918
HA2(D)=−155log2155−1510log21510=0.918
有自己的房子:
H
A
3
(
D
)
=
−
6
15
l
o
g
2
6
15
−
9
15
l
o
g
2
9
15
=
0.971
H_{A_3}(D) = -\frac{6}{15}log_2\frac{6}{15} -\frac{9}{15}log_2\frac{9}{15} = 0.971
HA3(D)=−156log2156−159log2159=0.971
信贷情况:
H
A
4
(
D
)
=
−
5
15
l
o
g
2
5
15
−
6
15
l
o
g
2
6
15
−
4
15
l
o
g
2
4
15
=
1.566
H_{A_4}(D) = -\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
HA4(D)=−155log2155−156log2156−154log2154=1.566
计算信息增益率
年龄:
g
R
(
D
,
A
1
)
=
g
(
D
,
A
1
)
H
A
1
(
D
)
=
0.083
1.585
=
0.052
g_R(D,A_1) = \frac{g(D,A_1)}{H_{A_1}(D)} = \frac{0.083}{1.585} = 0.052
gR(D,A1)=HA1(D)g(D,A1)=1.5850.083=0.052
有工作:
g
R
(
D
,
A
2
)
=
g
(
D
,
A
2
)
H
A
2
(
D
)
=
0.324
0.918
=
0.353
g_R(D,A_2) = \frac{g(D,A_2)}{H_{A_2}(D)} = \frac{0.324}{0.918} = 0.353
gR(D,A2)=HA2(D)g(D,A2)=0.9180.324=0.353
有自己的房子:
g
R
(
D
,
A
1
)
=
g
(
D
,
A
3
)
H
A
3
(
D
)
=
0.420
0.971
=
0.433
g_R(D,A_1) = \frac{g(D,A_3)}{H_{A_3}(D)} = \frac{0.420}{0.971} = 0.433
gR(D,A1)=HA3(D)g(D,A3)=0.9710.420=0.433
信贷情况:
g
R
(
D
,
A
1
)
=
g
(
D
,
A
4
)
H
A
4
(
D
)
=
0.363
1.566
=
0.232
g_R(D,A_1) = \frac{g(D,A_4)}{H_{A_4}(D)} = \frac{0.363}{1.566} = 0.232
gR(D,A1)=HA4(D)g(D,A4)=1.5660.363=0.232
最后,我们可以看出来,这里的信息增益率最大的是特征有自己的房子。所以我们按照C4.5算法,将有自己的房子作为划分的第一个准则。
3. 决策树剪枝
决策树根据ID3或者C4.5算法递归地进行构造,直到所有的特征全部构造出一个一个的分支。这样对于用于训练的数据分类会十分准确,但是一旦出现新的未知的测试数据的特征数值,那么就很难进行正确的分类。这就是过拟合现象。那么如果处理这个现象呢?
3.1 决策树的剪枝
将已经生成的决策树进行简化的过程称为决策树的剪枝。具体的方法是,裁剪决策树的某些叶子节点或者子树节点,将其上级节点作为新的叶子节点。使开枝散叶的决策树修剪枝叶。
3.2决策树剪枝算法
介绍一种简单的决策树剪枝算法。
决策树的剪枝往往通过极小化决策树整体的损失函数或代价函数来实现。设树T的叶子结点个数为|T|,t是树T的叶结点,该叶结点有
N
t
N_t
Nt个样本,其中k类的样本点有
N
t
k
N_{tk}
Ntk个,
H
t
(
T
)
H_t(T)
Ht(T)为叶结点t上的经验熵,
a
a
a>=0为参数,则损失函数可以定义为
C
a
(
T
)
=
∑
t
=
1
∣
T
∣
N
t
H
t
(
T
)
+
a
∣
T
∣
C_a(T) = \sum_{t=1}^{|T|}N_tH_t(T) + a|T|
Ca(T)=t=1∑∣T∣NtHt(T)+a∣T∣
经验熵为
H
t
(
T
)
=
−
∑
k
N
t
k
N
t
l
o
g
N
t
k
N
t
H_t(T) = -\sum_{k}\frac{N_{tk}}{N_t}log\frac{N_{tk}}{N_t}
Ht(T)=−k∑NtNtklogNtNtk
这时有
C
a
(
T
)
=
C
(
T
)
+
a
∣
T
∣
C_a(T) = C(T) + a|T|
Ca(T)=C(T)+a∣T∣
3.3决策树剪枝流程
输入:生成的决策树T,参数
a
a
a
输出:修建后的决策树
T
a
T_a
Ta
- 计算每个结点的经验熵
- 递归地从树的叶结点向上回缩
- 返回(2),知道不能继续为止,得到损失函数最小的子树 T a T_a Ta。
4决策树的python实现
import numpy as np
import pandas as pd
from collections import Counter
class Node(object):
def __init__(self, x=None, label=None, y=None, data=None):
self.label = label
self.x = x
self.y = y
self.data = data
self.child = []
def append(self, node):
self.child.append(node)
def predict(self, features):
if self.y is not None:
return self.y
for c in self.child:
if c.x == features[self.label]:
return c.predict(features)
class DTreeID3(object):
def __init__(self, epsilon=0, alpha=0):
# 信息增益阈值
self.epsilon = epsilon
self.alpha = alpha
self.tree = Node()
# 计算某列特征中每个种类的概率
# 输入为某一列
# 输出为字典形式:特征的属性的名字和所占的百分比{'是': 0.625, '否': 0.375}
def prob(self, datasets):
datasets = pd.Series(datasets)
data_len = len(datasets)
p = {}
vc = datasets.value_counts().values.tolist()
vc_index = datasets.value_counts().index.tolist()
for i in range(len(vc_index)):
p[vc_index[i]] = vc[i] / data_len
return p
# 计算某一列的信息熵
def calc_ent(self, datasets):
p = self.prob(datasets)
value = list(p.values())
sums = 0
for i in value:
sums += -i * np.log2(i)
#print("sums:",sums)
#print("ss",-np.sum(np.multiply(value, np.log2(value))))
#return -np.sum(np.multiply(value, np.log2(value)))
return sums
# 计算某列的条件熵
def cond_ent(self, datasets, col):
redata = datasets.T
labelx = redata.columns.tolist()[col]
labelx = redata[labelx].value_counts().index.tolist()
p = {}
for i in labelx:
p[i] = redata.loc[redata[redata.columns[col]]==i][redata.columns[-1]].tolist()
sums = 0
for k in p.keys():
sums += self.prob(datasets.iloc[col])[k] * self.calc_ent(p[k])
return sums
# 计算信息增益
def info_gain_train(self, datasets, datalabels):
datasets = datasets.T
ent = self.calc_ent(datasets.iloc[-1])
gainmax = {}
for i in range(len(datasets) - 1):
cond = self.cond_ent(datasets, i)
gainmax[ent - cond] = i
m = max(gainmax.keys())
return gainmax[m], m
# 训练
def train(self, datasets, node):
labely = datasets.columns[-1]
# 判断样本是否为同一类输出Di,如果是则返回单节点树T。标记类别为Di
if len(datasets[labely].value_counts()) == 1:
node.data = datasets[labely]
node.y = datasets[labely][0]
return
# 判断特征是否为空,如果是则返回单节点树T,标记类别为样本中输出类别D实例数最多的类别
if len(datasets.columns[:-1]) == 0:
node.data = datasets[labely]
node.y = datasets[labely].value_counts().index[0]
return
gainmaxi, gainmax = self.info_gain_train(datasets, datasets.columns)
if gainmax <= self.epsilon:
node.data = datasets[labely]
node.y = datasets[labely].value_counts().index[0]
return
vc = datasets[datasets.columns[gainmaxi]].value_counts()
for Di in vc.index:
node.label = gainmaxi
child = Node(Di)
node.append(child)
new_datasets = pd.DataFrame([list(i) for i in datasets.values if i[gainmaxi]==Di], columns=datasets.columns)
self.train(new_datasets, child)
def fit(self, datasets):
self.train(datasets, self.tree)
def findleaf(self, node, leaf):
for t in node.child:
if t.y is not None:
leaf.append(t.data)
else:
for c in node.child:
self.findleaf(c, leaf)
def findfather(self, node, errormin):
if node.label is not None:
cy = [c.y for c in node.child]
if None not in cy: # 全是叶节点
childdata = []
for c in node.child:
for d in list(c.data):
childdata.append(d)
childcounter = Counter(childdata)
old_child = node.child # 剪枝前先拷贝一下
old_label = node.label
old_y = node.y
old_data = node.data
node.label = None # 剪枝
node.y = childcounter.most_common(1)[0][0]
node.data = childdata
error = self.c_error()
if error <= errormin: # 剪枝前后损失比较
errormin = error
return 1
else:
node.child = old_child # 剪枝效果不好,则复原
node.label = old_label
node.y = old_y
node.data = old_data
else:
re = 0
i = 0
while i < len(node.child):
if_re = self.findfather(node.child[i], errormin) # 若剪过枝,则其父节点要重新检测
if if_re == 1:
re = 1
elif if_re == 2:
i -= 1
i += 1
if re:
return 2
return 0
def c_error(self): # 求C(T)
leaf = []
self.findleaf(self.tree, leaf)
leafnum = [len(l) for l in leaf]
ent = [self.calc_ent(l) for l in leaf]
error = self.alpha*len(leafnum)
for l, e in zip(leafnum, ent):
error += l*e
return error
def cut(self, alpha=0): # 剪枝
if alpha:
self.alpha = alpha
errormin = self.c_error()
self.findfather(self.tree, errormin)
if __name__ == "__main__":
def printnode(node, depth=0): # 打印树所有节点
if node.label is None:
print(depth, (node.label, node.x, node.y, len(node.data)))
else:
print(depth, (node.label, node.x))
for c in node.child:
printnode(c, depth+1)
datasets = np.array([
['青年', '否', '否', '一般', '否'],
['青年', '否', '否', '好', '否'],
['青年', '是', '否', '好', '是'],
['青年', '是', '是', '一般', '是'],
['青年', '否', '否', '一般', '否'],
['中年', '否', '否', '一般', '否'],
['中年', '否', '否', '好', '否'],
['中年', '是', '是', '好', '是'],
['中年', '否', '是', '非常好', '是'],
['中年', '否', '是', '非常好', '是'],
['老年', '否', '是', '非常好', '是'],
['老年', '否', '是', '好', '是'],
['老年', '是', '否', '好', '是'],
['老年', '是', '否', '非常好', '是'],
['老年', '否', '否', '一般', '否'],
['青年', '否', '否', '一般', '是']]) # 在李航原始数据上多加了最后这行数据,以便体现剪枝效果
datalabels = np.array(['年龄', '有工作', '有自己的房子', '信贷情况', '类别'])
train_data = pd.DataFrame(datasets, columns=datalabels)
test_data = ['老年', '否', '否', '一般']
dt = DTreeID3(epsilon=0) # 可修改epsilon查看预剪枝效果
dt.fit(train_data)
y = dt.tree.predict(test_data)
print('result:', y)
dt.cut(alpha=0.5) # 可修改正则化参数alpha查看后剪枝效果
y = dt.tree.predict(test_data)
print('result:', y)