【自学笔记】决策树

前言

  看过《Python机器学习》的都知道,逻辑回归后先讲了支持向量机,然后是核支持向量机,然后才是决策树。那为什么我先写决策树呢?因为前面那俩玩意一时半会儿没看懂
  此外,本篇内容也参考了西瓜书《机器学习》的相关内容

决策树

  决策树的逻辑基于问答,每一个节点针对某个特征进行询问,并根据答案走对应的分支;最终的叶子节点就是此次查询的预测值。而模型要做的就是构建一个最优的决策树

信息熵

  显然,能在有限的时间,空间内准确率更高,决策树就更优。那么,在构建整个决策树的过程中,如何量化当前状态下的决策树的优秀程度呢?很直接的想法:在同一个节点下的样本标签不同种类的数量越低,这个分支就越好。

  所以,类似线性分类的损失函数,我们为决策树引入了信息熵的概念。假定当前节点上的样本集合D中第k类样本所占的比例为 p k ( k = 1 , 2 , . . . , ∣ γ ∣ ) p_{k}(k=1, 2, ..., |\gamma|) pk(k=1,2,...,γ),则D的信息熵定义为:

E n t ( D ) = − ∑ k = 1 ∣ γ ∣ p k log ⁡ 2 p k Ent(D)=-\sum_{k=1}^{|\gamma|} p_{k}\log_{2}{p_{k}} Ent(D)=k=1γpklog2pk
规定当 p = 0 , p l o g 2 p = 0 p=0, plog_{2}{p}=0 p=0,plog2p=0

  可以发现Ent(D)的值越小,我们认为D的纯度越高

  接下来就是寻找一个策略来分离样本,减小信息熵。假定样本有一个属性a,它有 V V V个可能的取值 { a 1 , a 2 , . . . , a V } \left \{a^{1}, a^{2}, ..., a^{V}\right \} {a1,a2,...,aV},则一般会产生 V V V个分支节点,其中第 v v v个节点上包含所有属性 a a a的值为 a v a^{v} av的样本,记为 D v D^{v} Dv,则每一个子节点的信息熵为 E n t ( D v ) Ent(D^{v}) Ent(Dv)
  考虑到节点的样本数量会干扰信息熵大小的比较,我们通过赋予权重来消除样本数量的影响,然后计算前后信息熵的变化来得到这一步划分的信息增益

G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E n t ( D v ) Gain(D,a)=Ent(D)-\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}Ent(D^{v}) Gain(D,a)=Ent(D)v=1VDDvEnt(Dv)

  我们可以简单地认为,如果根据这个属性来划分,信息增益最大,我们就更倾向于用这个属性来划分当前节点。记这个最好的划分属性 a ∗ a_{*} a

操作

  决策树的生成是个递归的过程,我们记当前待处理的节点为node,样本集为 D D D,属性集为 A A A,每个样本的标签记为 C i ( i = 1 , 2 , . . . , γ ) C_{i}(i=1, 2, ..., \gamma) Ci(i=1,2,...,γ),,则每一步有以下情况:

  记以下过程为函数 G e n e r a t e ( D , A ) Generate(D, A) Generate(D,A)
  1. D D D中样本全部属于同一标签 C C C,则将node标记为 C C C类叶子节点,直接return
  2. A = ϕ A=\phi A=ϕ 或者 D D D中样本的所有属性值均相同,则无法划分,node标记为 D D D中样本数量最多的类别,直接return
  3. 开始划分:
   (1)从 A A A中找到最好的划分属性 a ∗ a_{*} a
   (2)遍历每个样本在属性 a ∗ a_{*} a下的值,记 D v D_{v} Dv为取值为 a ∗ v a_{*}^{v} av的样本子集
   (3)如果 D v = ϕ D_{v}=\phi Dv=ϕ,则当前node为叶节点,标记为 D D D中样本最多的标签。
   (4)否则,将 G e n e r a t e ( D v , A ∖ { a ∗ } ) Generate(D_{v}, A \setminus \left \{a_{*}\right \}) Generate(Dv,A{a}) 记为子树。
  4. return

优化:增益率

  我们考虑给样本编号,如果将编号当作属性加入候选划分属性,我们会发现它的信息增益远大于其他的属性,划分后每个节点只包含一个节点,熵降到最低。这显然不是我们想要的。

  分析这一现象,我们发现上述的决策会偏向可取值数目较多的属性。所以,我们引入增益率替代信息增益,来减少不利的影响:

G a i n    ‾ r a d i o ( D , a ) = G a i n ( D , a ) I V ( a ) Gain\underline{~~}radio(D,a)=\frac{Gain(D,a)}{IV(a)} Gain  radio(D,a)=IV(a)Gain(D,a)

  其中 I V ( A ) IV(A) IV(A)称为属性a的固有值

I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ log ⁡ 2 ∣ D v ∣ ∣ D ∣ IV(a)=-\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}\log_{2}{\frac{|D^{v}|}{|D|}} IV(a)=v=1VDDvlog2DDv

  可以发现,属性a的取值的可能取值数目越多(即 V V V越大),则 I V ( a ) IV(a) IV(a)通常会越大。

  此外需要注意的是,增益率准则会偏好数目较少的属性,所以我们采取启发式算法,先选出信息增益高于平均水平的属性,再从中选取增益率最高的。

基尼指数

  决策树有很多种,区别就在于策略不同。CART决策树使用基尼指数来划分属性,数据集 D D D的纯度可以用基尼值来衡量:

G i n i ( D ) = ∑ k = 1 ∣ γ ∣ ∑ k ′ ≠ k p k p k ′ = 1 − ∑ k = 1 ∣ γ ∣ p k 2 Gini(D)=\sum_{k=1}^{|\gamma|}\sum_{k'\ne k}^{}p_{k}p_{k'}=1-\sum_{k=1}^{|\gamma|}p_{k}^{2} Gini(D)=k=1γk=kpkpk=1k=1γpk2

  其中, p k p_{k} pk表示该节点属于k类样本的概率, p k ′ = 1 − p k p_{k'}=1-p_{k} pk=1pk

  类似信息增益,某属性a的基尼指数定义为

G i n i    ‾ i n d e x ( D , a ) = ∑ v = 1 V ∣ D v ∣ ∣ D ∣ G i n i ( D v ) Gini\underline{~~}index(D,a)=\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}Gini(D^{v}) Gini  index(D,a)=v=1VDDvGini(Dv)

  于是,我们可以用基尼指数来作为划分的指标,基尼指数越小,划分越优秀

剪枝

  过拟合永远是模型逃不掉的问题,显然如果树过于复杂,分类过于具体,会导致过拟合,所以我们需要去掉一些树枝。

预剪枝

  即在树生长过程中中断递归过程,我们只需要设置一些停止条件,如:

  (1)如果当前节点数据集的信息增益低于预设的阈值,停止向下分裂。
  (2)如果当前节点的数据集已经达到足够纯度(例如,所有数据属于同一类),则停止分裂。
  (3)如果当前节点包含的数据量少于预设的最小数据量,停止分裂。
  (4)限制决策树的最大深度,当达到预设的深度时,停止分裂。
  (5)如果向下分裂后准确率降低,则不分裂。注意,这种方式用贪心的思想抑制决策树分裂,有导致欠拟合的风险。

后剪枝

  即先生成一个完整的决策树,再根据需求删减节点。
  
  (1)根据需求,回溯时将子树退化成叶子节点,标记为该标签的样本数量最多的。
  (2)如果模型准确率提高,则更改,否则保留原来的子树。

  注意,后剪枝也有欠拟合的风险,但风险比预剪枝小;代价是时间消耗变大。

连续值的处理

  如果属性a的取值为一段连续值 ( l , r ) (l, r) (l,r),节点上的样本在属性a上的取值分别为 { a 1 , a 2 , . . . a n } \left \{ a^{1},a^{2},...a_{n} \right \} {a1,a2,...an}。我们选取一个划分点 t t t,将 D D D划分为子集 D − D^{-} D D + D^{+} D+,其中 D − D^{-} D包含了那些在属性a上取值小于等于 t t t的样本, D + D^{+} D+包含了那些在属性a上取值大于 t t t的样本。

  显然,对于相邻的两个属性取值 a i , a i + 1 a^{i}, a^{i+1} ai,ai+1 t t t取区间 [ a i , a i + 1 ) [a^{i}, a^{i+1}) [ai,ai+1)中的任意值产生的划分相同。所以,我们另该节点的划分点 t t t在以下区间 T a T_{a} Ta中取值:

T a = { a i + a i + 1 2 ∣ 1 ≤ i ≤ n − 1 } T_{a}=\left \{ \frac{a^{i}+a^{i+1}}{2}|1\le i\le n-1 \right \} Ta={2ai+ai+1∣1in1}

然后我们考察并选取最优的划分点,满足:

G a i n ( D , a ) = max ⁡ t ∈ T a G a i n ( D , a , t ) Gain(D,a)=\max_{t\in T_{a}}Gain(D,a,t) Gain(D,a)=maxtTaGain(D,a,t)

其中 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t)是样本集 D D D基于划分点 t t t二分后的信息增益,然后我们选择使 G a i n ( D , a , t ) Gain(D,a,t) Gain(D,a,t)最大的划分点。

  特别要注意的是,连续属性 a a a在划分后在之后的选择中还可以当作候选属性(也就是可以划分多次)

多变量决策树

  当属性种类很多时,往往会使决策树变得庞大,预测时间开销很大。我们可以采用多变量决策树来解决这个问题。
  我们将每一个非叶子节点进行修改,让它不再只依据一个属性进行划分,而是变成一个形如 ∑ i = 1 d w i a i = t \sum_{i=1}^{d}w_{i}a_{i}=t i=1dwiai=t的分类器,其中 w i w_{i} wi为属性 a i a_{i} ai的权重,训练时可以从该节点上的样本集和测试集来学习这些 w i w_{i} wi t t t

简单的决策树的代码实现

  构建决策树:

# 导入决策树分类器
from sklearn.tree import DecisionTreeClassifier
# 创建决策树分类器实例,使用'gini'不纯度作为节点分裂的标准,最大深度限制为4,随机种子设置为1
tree_model = DecisionTreeClassifier(criterion='gini', 
                                    max_depth=4, 
                                    random_state=1)
# 使用训练数据集X_train和对应的标签y_train对决策树分类器进行拟合
tree_model.fit(X_train, y_train)

# 将训练集和测试集的特征向量堆叠在一起,形成一个组合数据集
X_combined = np.vstack((X_train, X_test))

# 将训练集和测试集的标签堆叠在一起,形成一个组合的标签集(为了可视化)
y_combined = np.hstack((y_train, y_test))
# 使用plot_decision_regions函数来可视化决策区域,传入组合后的特征和标签数据
# classifier参数指定了要可视化的分类器,test_idx参数指定了测试数据在组合数据中的索引范围
plot_decision_regions(X_combined, y_combined, 
                      classifier=tree_model,
                      test_idx=range(105, 150))

# 设置X轴的标签
plt.xlabel('petal length [cm]')
# 设置Y轴的标签
plt.ylabel('petal width [cm]')

# 在图上添加图例,并设置其位置为左上角
plt.legend(loc='upper left')
# 调整布局以适应坐标轴标签和图例
plt.tight_layout()
# 保存图像
# plt.savefig('images/03_20.png', dpi=300)
# 显示图像

plt.show()

在这里插入图片描述
  可视化决策树:

from sklearn import tree

tree.plot_tree(tree_model)
#plt.savefig('images/03_21_1.pdf')
plt.show()

在这里插入图片描述
  更好地呈现图像:

# 导入用于从DOT数据创建图形的函数
from pydotplus import graph_from_dot_data

# 导入用于从决策树模型导出DOT格式数据的函数
from sklearn.tree import export_graphviz

# 使用export_graphviz函数从决策树模型导出DOT数据
# filled=True, rounded=True选项使节点填充颜色并显示圆角,以增加可读性
# class_names参数指定了类别名称,用于节点的标签
# feature_names参数指定了特征名称,用于描述树中的分裂条件
# out_file=None表示不直接写入文件,而是返回DOT数据的字符串形式
dot_data = export_graphviz(tree_model,
                           filled=True, 
                           rounded=True,
                           class_names=['Setosa', 
                                        'Versicolor',
                                        'Virginica'],
                           feature_names=['petal length', 
                                          'petal width'],
                           out_file=None) 

# 使用graph_from_dot_data函数将DOT数据转换为Graphviz图形对象
graph = graph_from_dot_data(dot_data) 

# 使用write_png方法将Graphviz图形对象导出为PNG图像文件

graph.write_png('tree.png')

在这里插入图片描述

决策森林

  当数据集足够大时(相比起属性种类而言),单颗决策树的性能提升会越来越小,浪费了大量数据。我们可以使用随机森林算法来更好地处理数据和减少过拟合的概率。

步骤

  (1)从训练集 D D D有放回选取大小为 d d d的子集 D i D_{i} Di
  (2)基于样本 D i D_{i} Di生成决策树 T r e e i Tree_{i} Treei,在每个节点下做以下任务:
    (a)不放回随机选择 k k k个特征。
    (b)选取最佳特征分裂节点。
  (3)聚合每棵树的预测结果,以多数投票制来确定标签分类。

  PS: 步骤1中“有放回”的原因是为了让每棵树的训练集保持独立;步骤2中“无放回”的原因暂未找到解答。

优缺点

  优势:不必关心超参数的选择(即学习率,迭代次数,正则化等参数),我们唯一需要关心的是森林中树的数量(通常选取 m \sqrt{m} m ,其中 m m m为训练集的大小);
  此外我们通常不需要修剪随机森林,因为集成模型对单个决策树的噪声具有较强的抵抗力。

  缺点:计算成本随树的增多而增大;可解释性不如普通决策树

ski-learn上实现

  现成的模型RandomForestClassifier

# 导入随机森林分类器类
from sklearn.ensemble import RandomForestClassifier

# 初始化随机森林分类器
# 使用基尼不纯度作为质量度量划分决策树节点
# 设置25棵树,随机状态为1以确保实验可复现,n_jobs=2使用2个CPU核心并行训练
forest = RandomForestClassifier(criterion='gini',
                                n_estimators=25, 
                                random_state=1,
                                n_jobs=2)

# 使用训练数据拟合模型
# X_train是特征数据,y_train是对应的标签数据
forest.fit(X_train, y_train)

# 绘制决策区域边界
# X_combined和y_combined是组合了训练和测试数据的特征和标签
# classifier参数是已经训练好的随机森林模型
# test_idx参数标识出测试数据点的索引范围

# 这将可视化出训练和测试数据点,并展示出决策边界
plot_decision_regions(X_combined, y_combined, 
                      classifier=forest, test_idx=range(105, 150))

# 设置标签
plt.xlabel('petal length [cm]')
plt.ylabel('petal width [cm]')

# 添加图例,位置在左上角
plt.legend(loc='upper left')

# 调整图形布局,使得子图和标签更紧凑
plt.tight_layout()

# 保存图形到本地文件
# plt.savefig('images/03_22.png', dpi=300)

# 显示图形
plt.show()

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值