1 决策树原理
分类决策树模型是表示基于特征对实例进行分类的树形结构。决策树可以转换成一个if-then规则的集合,也可以看作是定义在特征空间划分上的类的条件概率分布。决策树的核心思想是基于树结构对数据进行划分,这种思想是人类处理问题时的本能方法。
决策树学习旨在构建一个与训练数据拟合很好,并且复杂度小的决策树。因为从可能的决策树中直接选取最优决策树是NP完全问题。现实中采用启发式方法学习次优的决策树。
决策树学习算法包括3部分:特征选择、树的生成和树的剪枝。常用的算法有ID3、C4.5和CART。
1.1 特征选择
通常使用信息增益最大、信息增益比最大或基尼指数最小作为特征选择的准则。决策树的生成往往通过计算信息增益或其他指标,从根结点开始,递归地产生决策树。这相当于用信息增益或其他准则不断地选取局部最优的特征,或将训练集分割为能够基本正确分类的子集。
- 信息增益
信息熵是一种衡量数据混乱程度的指标,信息熵越小,则数据的“纯度”越高
Ent ( D ) = − ∑ k = 1 ∣ Y ∣ p k log 2 p k \operatorname{Ent}(D)=-\sum_{k=1}^{|\mathcal{Y}|} p_{k} \log _{2} p_{k} Ent(D)=−∑k=1∣Y∣pklog2pk
其中 p k p_k pk代表了第 k k k类样本在 D D D中占有的比例。
假设离散属性 a a a有 V V V个可能的取值{ a 1 a^1 a1, a 2 a^2 a2,…, a V a^V aV},若使用 a a a对数据集 D D D进行划分,则产生 D D D个分支节点,记为 D v D^v Dv。则使用 a a a对数据集进行划分所带来的信息增益被定义为:
Gain ( D , a ) = Ent ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ Ent ( D v ) \operatorname{Gain}(D, a)=\operatorname{Ent}(D)-\sum_{v=1}^{V} \frac{\left|D^{v}\right|}{|D|} \operatorname{Ent}\left(D^{v}\right) Gain(D,a)=Ent(D)−∑v=1V∣D∣∣Dv∣Ent(Dv)
一般的信息增益越大,则意味着使用特征 a a a来进行划分的效果越好。
- 基尼指数
Gini ( D ) = ∑ k = 1 ∣ Y ∣ ∑ k ′ ≠ k p k p k ′ = 1 − ∑ k = 1 ∣ Y ∣ p k 2 \begin{aligned} \operatorname{Gini}(D) &=\sum_{k=1}^{|\mathcal{Y}|} \sum_{k^{\prime} \neq k} p_{k} p_{k^{\prime}} \\ &=1-\sum_{k=1}^{|\mathcal{Y}|} p_{k}^{2} \end{aligned} Gini(D)=k=1∑∣Y∣k′=k∑pkpk′=1−k=1∑∣Y∣pk2
基尼指数反映了从数据集 D D D中随机抽取两个的类别标记不一致的概率。
Gini index ( D , a ) = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ Gini ( D v ) \operatorname{Gini}\operatorname{index}(D, a)=\sum_{v=1}^{V} \frac{\left|D^{v}\right|}{|D|} \operatorname{Gini}\left(D^{v}\right) Giniindex(D,a)=∑v=1V∣D∣∣Dv∣Gini(Dv)
使用特征 a a a对数据集 D D D划分的基尼指数定义为上。
1.2 决策树的生成伪代码
输入: 训练集D={(
x
1
x_1
x1,
y
1
y_1
y1),(
x
2
x_2
x2,
y
2
y_2
y2),…,(
x
m
x_m
xm,
y
m
y_m
ym)};
特征集A={
a
1
a_1
a1,
a
2
a_2
a2,…,
a
d
a_d
ad}
输出: 以node为根节点的一颗决策树
过程:函数TreeGenerate( D D D, A A A)
- 生成节点node
- i f if if D D D中样本全书属于同一类别 C C C t h e n then then:
- ----将node标记为 C C C类叶节点; r e t u r n return return
- i f if if A A A = 空集 OR D中样本在 A A A上的取值相同 t h e n then then:
- ----将node标记为叶节点,其类别标记为 D D D中样本数最多的类; r e t u r n return return
- 从 A A A 中选择最优划分属性 a ∗ a_* a∗;
- f o r for for a ∗ a_* a∗ 的每一个值 a ∗ v a_*^v a∗v d o do do:
- ----为node生成一个分支,令 D v D_v Dv表示 D D D中在 a ∗ a_* a∗上取值为 a ∗ v a_*^v a∗v的样本子集;
- ---- i f if if D v D_v Dv 为空 t h e n then then:
- --------将分支节点标记为叶节点,其类别标记为 D D D中样本最多的类; t h e n then then
- ---- e l s e else else:
- --------以 TreeGenerate( D v D_v Dv, A A A{ a ∗ a_* a∗})为分支节点
决策树的构建过程是一个递归过程。函数存在三种返回状态:(1)当前节点包含的样本全部属于同一类别,无需继续划分;(2)当前属性集为空或者所有样本在某个属性上的取值相同,无法继续划分;(3)当前节点包含的样本集合为空,无法划分。
1.3 决策树剪枝
- 预剪枝
在节点划分前来确定是否继续增长,及早停止增长的主要方法有:
1 节点内数据样本低于某一阈值;
2 所有节点特征都已分裂;
3 节点划分前准确率比划分后准确率高。
预剪枝不仅可以降低过拟合的风险而且还可以减少训练时间,但另一方面它是基于“贪心”策略,会带来欠拟合风险。
- 后剪枝
在已经生成的决策树上进行剪枝,从而得到简化版的剪枝决策树。
C4.5 采用的悲观剪枝方法,用递归的方式从低往上针对每一个非叶子节点,评估用一个最佳叶子节点去代替这课子树是否有益。如果剪枝后与剪枝前相比其错误率是保持或者下降,则这棵子树就可以被替换掉。C4.5 通过训练数据集上的错误分类数量来估算未知样本上的错误率。
后剪枝决策树的欠拟合风险很小,泛化性能往往优于预剪枝决策树。但同时其训练时间会大的多。
CART采用一种“基于代价复杂度的剪枝”方法进行后剪枝,这种方法会生成一系列树,每个树都是通过将前面的树的某个或某些子树替换成一个叶节点而得到的,这一系列树中的最后一棵树仅含一个用来预测类别的叶节点。然后用一种成本复杂度的度量准则来判断哪棵子树应该被一个预测类别值的叶节点所代替。这种方法需要使用一个单独的测试数据集来评估所有的树,根据它们在测试数据集熵的分类性能选出最佳的树。代价复杂度剪枝算法即总结上的公式所示。
1.4 优缺点
决策树的主要优点:
- 具有很好的解释性,模型可以生成可以理解的规则。
- 可以发现特征的重要程度。
- 模型的计算复杂度较低。
决策树的主要缺点:
- 模型容易过拟合,需要采用减枝技术处理。
- 不能很好利用连续型特征。
- 预测能力有限,无法达到其他强监督模型效果。
- 方差较高,数据分布的轻微改变很容易造成树结构完全不同。
2 习题
- 计算信息增益
只记录关键代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
"""%matplotlib inline 可以在Ipython编译器里直接使用,功能是可以内嵌绘图,
并且可以省略掉plt.show()这一步"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
# 经验熵
def calc_ent(datasets):
data_length = len(datasets) #长度15
label_count = {}
for i in range(data_length):
label = datasets[i][-1]
if label not in label_count:
label_count[label] = 0
label_count[label] += 1
ent = -sum([(p / data_length) *log(p / data_length, 2) for p in label_count.values()]) #公式计算经验熵
# 或者np.log2(p / data_length)
return ent
# 经验条件熵
def cond_ent(datasets, axis=0): #axis表示第几个特征,并不是什么轴
data_length = len(datasets)
feature_sets = {}
for i in range(data_length):
feature = datasets[i][axis]
if feature not in feature_sets:
feature_sets[feature] = []
feature_sets[feature].append(datasets[i])
cond_ent = sum(
[(len(p) / data_length) * calc_ent(p) for p in feature_sets.values()])
return cond_ent
### 信息增益
def info_gain(ent, cond_ent):
return ent - cond_ent
def info_gain_train(datasets):
count = len(datasets[0]) - 1 #共有几个特征
ent = calc_ent(datasets)
# ent = entropy(datasets)
best_feature = []
for c in range(count):
c_info_gain = info_gain(ent, cond_ent(datasets, axis=c)) #对每个特征求信息增益
best_feature.append((c, c_info_gain))
print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))
# 比较大小
print(best_feature)
best_ = max(best_feature, key=lambda x: x[-1])
print(best_)
return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])
info_gain_train(np.array(datasets))
- 信息增益比(C4.5生成决策树)
# 数据预处理
le_x = preprocessing.LabelEncoder()
"""LabelEncoder是用来对分类型特征值进行编码,即对不连续的数值或文本进行编码。其中包含以下常用方法:
fit(y) :fit可看做一本空字典,y可看作要塞到字典中的词。
fit_transform(y):相当于先进行fit再进行transform,即把y塞到字典中去以后再进行transform得到索引值。
inverse_transform(y):根据索引值y获得原始数据。
transform(y) :将y转变成索引值。"""
le_x.fit(np.unique(X_train)) #显示唯一值标签
X_train = X_train.apply(le_x.transform)
le_y = preprocessing.LabelEncoder()
le_y.fit(np.unique(y_train))
y_train = y_train.apply(le_y.transform)
# 调用sklearn.DT建立训练模型
model_tree = DecisionTreeClassifier()
model_tree.fit(X_train, y_train)
# 可视化
dot_data = tree.export_graphviz(model_tree, out_file=None,
feature_names=features,
class_names=[str(k) for k in np.unique(y_train)],
filled=True, rounded=True,
special_characters=True)
graph = graphviz.Source(dot_data)
graph
*二叉回归树
3 实践
3.1 重要参数
- criterion
Criterion这个参数正是用来决定模型特征选择的计算方法的。sklearn提供了两种选择:
输入”entropy“,使用信息熵(Entropy)
输入”gini“,使用基尼系数(Gini Impurity)
- random_state & splitter
random_state用来设置分枝中的随机模式的参数,默认None,在高维度时随机性会表现更明显。splitter也是用来控制决策树中的随机选项的,有两种输入值,输入”best",决策树在分枝时虽然随机,但是还是会优先选择更重要的特征进行分枝(重要性可以通过属性feature_importances_查看),输入“random",决策树在分枝时会更加随机,树会因为含有更多的不必要信息而更深更大,并因这些不必要信息而降低对训练集的拟合。
- max_depth
限制树的最大深度,超过设定深度的树枝全部剪掉。这是用得最广泛的剪枝参数,在高维度低样本量时非常有效。决策树多生长一层,对样本量的需求会增加一倍,所以限制树深度能够有效地限制过拟合。
- min_samples_leaf
min_samples_leaf 限定,一个节点在分枝后的每个子节点都必须包含至少min_samples_leaf个训练样本,否则分枝就不会发生,或者,分枝会朝着满足每个子节点都包含min_samples_leaf个样本的方向去发生。一般搭配max_depth使用,在回归树中有神奇的效果,可以让模型变得更加平滑。这个参数的数量设置得太小会引起过拟合,设置得太大就会阻止模型学习数据。
3.2 算法应用
基于鸢尾花数据集的决策树实战。
# data
def create_data():
iris = load_iris()
df = pd.DataFrame(iris.data,) # columns=iris.feature_names iris有feature_names属性
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data = np.array(df.iloc[:100, [0, 1, -1]])
# print(data)
return data[:, :2], data[:, -1]
X, y = create_data()
# print(X,y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
import graphviz
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train,)
clf.score(X_test, y_test)
tree_pic = export_graphviz(clf, out_file="mytree.pdf") #训练好fit的模型clf
with open('mytree.pdf') as f:
dot_graph = f.read()
graphviz.Source(dot_graph)
参考:graphviz安装入门:https://www.cnblogs.com/onemorepoint/p/8310996.html
代码参考地址:https://github.com/fengdu78/lihang-code
经典问题:https://mp.weixin.qq.com/s/vkbZweJ5oRo4IPt-3kg64g
算法详解:https://mp.weixin.qq.com/s/jj3BtmnWRAwCS56ZU3ZXZA