1. 决策树模型
决策树模型是一种树形结构,其依据一系列的if-then规则将数据集分散到各个叶子上。决策树分为两种:分类决策树和回归决策树。通常依据输出值y来判定决策树的种类,如果输出值是离散值,那么为分类决策树;如果输出值为连续值,则为回归决策树。决策树的主要优点是模型具有可读性、分类速度快。学习决策树时,利用训练数据,根据损失函数最小化的原则建立决策树模型。决策树的思想主要来源于由Quinlan在1986年提出的ID3算法和1993年提出的C4.5算法,以及由Breiman等人在1984年提出的CART算法。
2. 决策树的学习过程
- 特征选择:特征选择表示从众多的特征中选择一个特征作为当前节点分裂的标准,如何选择特征有不同的量化评估方法,从而衍生出不同的决策树,如ID3(信息增益)、C4.5(信息增益比)、CART(Gini指数)等。
- 决策树的生成:根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则停止决策树停止生长。这个过程实际上就是使用满足划分准则的特征不断的将数据集划分成纯度更高,不确定度更小的子集的过程。
- 决策树的剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。
决策树算法最常用的算法是ID3、C4.5和CART等,本文主要对这三种决策树算法做一些详细解析。
3. ID3算法
在学习ID3算法之前,首先回归一下信息论的一些熵的基础知识,熵度量事物的不确定性,不确定性越大,熵越大。假设X是一个离散随机变量,其概率分布为:
p
(
X
=
x
i
)
=
p
i
,
i
=
1
,
2
,
.
.
.
,
n
p(X=x_i)=p_i,i=1,2,...,n
p(X=xi)=pi,i=1,2,...,n随机变量X的熵定义为
H
(
X
)
=
−
∑
i
=
1
n
p
i
l
o
g
p
i
H(X)=-\sum_{i=1}^np_ilogp_i
H(X)=−i=1∑npilogpi
事件的信息量是抽象的概念,通常用不确定性来衡量。不确定性越大,事件发生所需的信息量越大。(与事件发生的概率p成反比)
信息增益计算
信息熵:设S是s个数据样本集合,假定类别标签有m个不同值,定义m个不同类
C
i
(
i
=
1
,
2
,
.
.
.
,
m
)
C_i(i=1,2,...,m)
Ci(i=1,2,...,m),设
S
i
S_i
Si是
C
i
C_i
Ci的样本数,该样本分类的信息熵由下式计算:
I
(
S
)
=
−
∑
i
m
p
i
l
o
g
(
p
i
)
I(S)=-\sum_i^mp_ilog(p_i)
I(S)=−i∑mpilog(pi)
p i p_i pi是任意样本属于 C i C_i Ci的概率
- 用信息熵来度量每种特征不同取值下的不确定性
- 设A有v个不同值 { a 1 , a 2 , . . . , a v } \left\{a_1,a_2,...,a_v\right\} {a1,a2,...,av}
- 某一特征A将S划分为v个不同的子集 { S 1 , S 2 , . . . , S v } \left\{S_1,S_2,...,S_v\right\} {S1,S2,...,Sv}
- 由A划分子集的熵:
E
(
A
)
=
∑
j
=
1
v
S
j
S
I
(
S
j
)
E(A)=\sum_{j=1}^v\frac{S_j}{S}I(S_j)
E(A)=j=1∑vSSjI(Sj)
I
(
S
j
)
=
−
∑
i
m
p
i
j
l
o
g
(
p
i
j
)
I(S_j)=-\sum_i^mp_{ij}log(p_{ij})
I(Sj)=−i∑mpijlog(pij)
其中 S j S_j Sj表示为A特征下的第j个特征值的样本,最后该特征A下的信息增益:
G a i n ( A ) = I ( S ) − E ( A ) Gain(A)=I(S)-E(A) Gain(A)=I(S)−E(A)
根据信息增益准则的特征选择方法是:对训练数据集D,计算每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征,信息增益越大,说明这个特征有更好的分类能力。



ID3算法的核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。具体方法是:从根节点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点,再对子结点递归的调用以上方法,构建决策树,直到所有特征的信息增益均很小或没有特征可以选择为止。最后得到一个决策树。

算法
- 创建根节点R
- 如果当前Dataset中数据是同一类,将R的节点类型标记为当前类型
- 如果当attrList为空时,将R的节点类型标记为当前Dataset中样本个数最多的类型(投票)
- 循环:
1) 从attrList中选择特征A
2)按照属性A所有的不同特征值 V i V_i Vi,将Dataset分为不同的子集 D i D_i Di,对于每个 D i D_i Di
a) 创建节点C
b) 如果 D i D_i Di为空,节点C标记为Dataset中样本个数最多的类型
c) D i D_i Di不为空,节点 C = I D 3 ( D i , a t t r L i s t − A ) C=ID3(D_i,attrList-A) C=ID3(Di,attrList−A)
d) 将节点C添加为R的子节点 - 返回R
缺点
- 分裂特征倾向于选择属性值较多的特征属性,可能不会提供太多有价值的信息(极限状态下比较)
- 贪婪性
- 奥卡姆剃刀原理:尽量用较少的东西做更多的事
- 仅支持离散特征,不支持连续特征
4. C4.5算法
改进:用信息增益率替代信息增益,信息增益率不仅考虑了信息增益对信息熵的影响,也引入了某一特征下的数据分布对信息熵的影响。
- 和ID3相比,克服了用信息增益选择特征时偏向选择特征值较多的特征的不足之处
- 支持离散特征与连续特征
信息增益率
H ( S ) = − ∑ i = 1 m p ( u i ) l o g ( p ( u i ) ) H(S)=-\sum_{i=1}^mp(u_i)log(p(u_i)) H(S)=−i=1∑mp(ui)log(p(ui)) G a i n ( S , A ) = E n t r o p y ( S ) − ∑ v ∈ V a l u e ( A ) ∣ S v ∣ S E n t r o p y ( S v ) Gain(S,A) = Entropy(S)-\sum_{v \in Value(A)}\frac{|S_v|}{S}Entropy(S_v) Gain(S,A)=Entropy(S)−v∈Value(A)∑S∣Sv∣Entropy(Sv) G a i n R a t i o ( A ) = G a i n ( A ) E n t r o p y ( A ) GainRatio(A)=\frac{Gain(A)}{Entropy(A)} GainRatio(A)=Entropy(A)Gain(A) E n t r o p y ( A ) = − ∑ i p i l o g ( p i ) Entropy(A)=-\sum_ip_ilog(p_i) Entropy(A)=−i∑pilog(pi)
pi表示为特征A下第i个特征值的样本占总样本的比例,Entropy(A)表示为A下的信息熵。属性A的分布情况,混乱度越大,ratio越小,越纯净,ratio越大。
叶子节点输出规则
- 若叶子节点的所有样本都是同一类,则输出这一类别。
- 若属性列表为空时,叶子节点输出大多数样本所属的类别。
算法
- 创建根节点R
- 如果当前Dataset中数据是同一类或其他终止条件,将R的节点类型标记为当前类型
- 如果当attrList为空时,将R的节点类型标记为当前Dataset中样本个数最多的类型(投票)
- 循环:
1) 从attrList中选择Gainratio最大的属性A
2)按照属性A所有的不同特征值 V i V_i Vi,将Dataset分为不同的子集 D i D_i Di,对于每个 D i D_i Di
a) 创建节点C
b) 如果 D i D_i Di为空,节点C标记为Dataset中样本个数最多的类型
c) D i D_i Di不为空,节点 C = C 4.5 ( D i , a t t r L i s t − A ) C=C4.5(D_i,attrList-A) C=C4.5(Di,attrList−A)
d) 将节点C添加为R的子节点 - 返回R
5. CART(classification and regression tree)
CART决策树的生成是递归的构建二叉决策树的过程。对回归树用误差平方和最小化为准则;对分类树用基尼指数为最小化准则,进行特征选择,生成二叉树。
5.1 回归树的生成
最小二乘回归树算法
当输入空间划分确定时,用误差平方和来表示回归树对于训练数据的预测误差,用平方误差最小化的准则求解每个单元上的最优输出值,单元 R m R_m Rm上的 C m C_m Cm的最优值是 R m R_m Rm上所有输入实例 x i x_i xi对应的输出 y i y_i yi的均值

-
遍历变量j以及对应的所有特征值s,选择最优切分属性j和切分点s,求解 min j , s [ min c i ∑ 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_i}\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] j,smin[ciminxi∈R1(j,s)∑(yi−c1)2+c2minxi∈R2(j,s)∑(yi−c2)2]
-
用选定的(j,s)划分区域并决定相应的输出值 R 1 ( j , s ) = x ∣ x ( j ) ≤ s , R 1 ( j , s ) = x ∣ x ( j ) > s R_1(j,s)={x|x^{(j)} \leq s},R_1(j,s)={x|x^{(j)} > s} R1(j,s)=x∣x(j)≤s,R1(j,s)=x∣x(j)>s c m = 1 N m ∑ x i ∈ R m ( j , s ) y i c_m=\frac{1}{N_m}\sum_{x_i \in R_m(j,s)}y_i cm=Nm1xi∈Rm(j,s)∑yi
-
继续对两个子区域调用步骤1.2,直至满足停止条件
-
将输入空间划分为M个区域,生成决策树。
5.2 分类树的生成
分类树用基尼指数选择最优特征,同时决定该特征的最优二值切分点。->原因:信息增益率计算 − p ( x ) l o g ( p ( x ) ) -p(x)log(p(x)) −p(x)log(p(x)), l o g log log计算增加了计算复杂度, g i n i gini gini系数 = 1 − s u m ( p 2 ) =1-sum(p^2) =1−sum(p2),虽然没有信息增益比更加准确,但是也能反映出信息熵的变化,而且基尼指数计算相对简单一些。
分类树算法

- 设节点的训练数据集为D,在所有可能的特征A以及它们对应的所有切分点a中,根据样本点对A=a的测试为“是”或“否”将D分割为 D 1 D_1 D1和 D 2 D_2 D2两部分,并计算切分后的基尼指数,选择基尼指数最小的特征以及对应切分点作为最优特征与最优切分点。
- 依据最优特征与最优切分点,将训练数据集分割到两个子节点中。
- 对两个子节点递归的调用1,2,直至满足条件为止。
- 生成CART决策树。
算法终止条件是:节点中的样本个数小于预定阈值或样本集的基尼指数小于预定阈值。
5.3 CART剪枝
CART剪枝算法由两步组成:1.先从生成算法产生的决策树 T 0 T_0 T0底端开始不断剪枝,直到 T 0 T_0 T0的根节点,形成一个子树序列 { T 0 , T 1 , . . . , T n } \left\{T_0,T_1,...,T_n\right\} {T0,T1,...,Tn}。2.通过交叉验证法在独立的验证数据集上对子树序列进行测试,从中选择最优子树
6. CART和C4.5的对比
CART算法和C4.5算法相比,采用了简化的二叉树模型,同时特征选择采用了近似的基尼系数来简化计算。而且,CART树最大的好处是支持*回归模型与分类模型,但是ID3和C4.5只支持分类模型。
防止过拟合的参数调整
- min_samples_leaf,默认值是1,经验上必须大于100,如果一个结点没有100个样本来支持它的决策,一般认为是过拟合。
- max_depth参数控制树的规模
Python实现ID3算法
import math
# 生成初始数据集
def gene_data():
data_set = [[1,1,'yes'],
[1,1,'yes'],
[1,0,'no'],
[0,1,'no'],
[0,1,'no']]
cols = ['no surfacing', 'flippers']
return data_set,cols
# 计算熵
def cal_entropy(data_set):
label_cnt = dict()
for feat in data_set:
curr_label = feat[-1]
if label_cnt.get(curr_label,-1) == -1:
label_cnt[curr_label] = 0
label_cnt[curr_label] += 1
Entropy = 0.0
for label in label_cnt:
prob = label_cnt[label] / float(len(data_set))
Entropy -= prob * math.log(prob)
return Entropy
# 将数据集按照索引为i,特征值为value进行划分
def split_data_set(data_set,i,value):
"""
:param data_set: 数据集
:param i: 数据集的特征索引
:param value: 对应索引的特征值
:return: 划分的子集
"""
splited_data = []
for data in data_set:
if data[i] == value:
proc_data = data[:i]
proc_data.extend(data[i+1:])
splited_data.append(proc_data)
return splited_data
# 从整个数据集中搜索最合适的特征作为划分依据
def choose_best_feat_to_split(dataset):
feat_cnt = len(dataset[0]) - 1
best_info_gain = 0.0
best_feature = -1
base_entropy = cal_entropy(dataset)
for i in range(feat_cnt):
new_entropy = 0.0
feat_list = [data[i] for data in dataset]
unique_feat_value = set(feat_list)
for feat_value in unique_feat_value:
split_data = split_data_set(dataset,i,feat_value)
prob = len(split_data) / float(len(dataset))
splited_entropy = cal_entropy(split_data)
new_entropy += prob * splited_entropy
info_gain = base_entropy - new_entropy
if info_gain > best_info_gain:
best_info_gain = info_gain
best_feature = i
return best_feature
# 叶子结点返回的规则:样本中类别数量最多的一个类别
def majority_class(class_list):
class_cnt = dict()
for label in class_list:
if class_cnt.get(label,-1) == -1:
class_cnt[label] = 0
class_cnt[label] += 1
return max(class_cnt.items(),key=lambda x:x[1])[0]
# 递归生成决策树
def gen_tree(dataset,cols):
label_list = [data[-1] for data in dataset]
if label_list.count(label_list[0]) == len(label_list):
return label_list[0]
if len(dataset[0]) == 1:
return majority_class(label_list)
i = choose_best_feat_to_split(dataset)
feat = cols[i]
del(cols[i])
my_tree = {feat:{}}
feat_value = [data[i] for data in dataset]
unique_value = set(feat_value)
for value in unique_value:
subcols = cols
my_tree[feat][value] = gen_tree(split_data_set(dataset,i,value),subcols)
return my_tree
if __name__ == '__main__':
dataset,cols = gene_data()
tree = gen_tree(dataset,cols)
print(tree)
调用sklearn实现DecisionTree分类
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split,cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn import tree
# 加载sklearn库中的iris数据
iris = load_iris()
# 训练集/测试集的划分
X_train,X_test,y_train,y_test = train_test_split(iris.data,iris.target,train_size=0.8,random_state=2019)
# 分类决策树的初始化
clf = DecisionTreeClassifier(criterion='gini',
splitter='best',
max_depth=None,
min_samples_split=2,
min_samples_leaf=1,
min_weight_fraction_leaf=0,
max_features=None,
random_state=None,
max_leaf_nodes=None,
class_weight=None,
presort=False)
# 训练模型
df_model = clf.fit(X_train,y_train)
# 性能评估
print(cross_val_score(clf,iris.data,iris.target,cv=10))
# 用Graphviz将模型存储为dot文件
tree.export_graphviz(df_model,
out_file='tree.dot',
max_depth=None,
feature_names=iris.feature_names,
class_names=iris.target_names,
filled=True,
rounded=True,
special_characters=True)
在终端输入脚本将dot文件转化为png图
dot -Tpng tree.dot -o tree.png
