点击获取AI摘要

决策树学习笔记

1. 什么是决策树?

决策树(Decision Tree)是一种基本的分类与回归方法。它是一种监督学习算法,其模型呈树形结构,可以看作是基于特征对实例进行分类或回归的过程。

  • 结构:
    • 根节点 (Root Node): 包含样本全集。
    • 内部节点 (Internal Node): 代表一个特征或属性的测试。
    • 分支 (Branch) / 边 (Edge): 代表测试的输出(特征的某个值或范围)。
    • 叶节点 (Leaf Node) / 终端节点 (Terminal Node): 代表最终的决策结果(类别或数值)。
  • 目标: 生成一棵泛化能力强,即处理未见示例能力强的决策树。
  • 决策过程: 从根节点开始,根据实例的特征值,沿着树的分支向下移动,直到到达叶节点,该叶节点的类别或值即为预测结果。

2. 决策树学习原理

决策树学习的本质是从训练数据中归纳出一组分类规则,或者说是由训练数据集估计条件概率模型。其核心思想是 “分而治之” (Divide and Conquer)

学习过程是一个 递归地选择最优特征,并根据该特征对训练数据进行分割,使得各个子数据集有一个最好的分类的过程。

2.1 学习步骤概览

  1. 开始: 构建根节点,所有训练数据都放在根节点。
  2. 特征选择: 选择一个最优特征,按照该特征将训练数据集分割成子集,使得各个子集在当前条件下有最好的分类。
  3. 生成子节点: 如果某个子集已能够被基本正确分类(达到停止条件),则构建叶节点,并将这些子集分到所对应的叶节点中去。
  4. 递归: 如果子集不能被基本正确分类,则对这些子集选择新的最优特征,继续对其进行分割,构建相应的节点。
  5. 结束: 递归地进行步骤 2-4,直到所有训练数据子集都被基本正确分类,或者没有合适的特征为止。

2.2 如何选择最优特征?—— 划分选择

选择最优特征是决策树学习的关键。目标是选择一个特征进行划分后,各子集的“纯度” (Purity) 最高。纯度越高,意味着子集中的样本尽可能属于同一类别。

常用的衡量纯度的指标有:

a) 信息熵 (Information Entropy)

熵是度量随机变量不确定性的指标。熵越大,随机变量的不确定性就越大,纯度越低。

假设当前样本集合 D 中第 k 类样本所占的比例为 p_k (k=1, 2, …, |Y|,|Y|是类别总数),则 D 的信息熵定义为:

Ent(D)=k=1Ypklog2(pk)\mathrm{Ent}(D) = - \sum_{k=1}^{|Y|} p_k \log_2(p_k)

  • Ent(D) 的值越小,D 的纯度越高。

b) 信息增益 (Information Gain) - ID3 算法

信息增益表示得知特征 A 的信息而使得数据集 D 的不确定性减少的程度。选择信息增益最大的特征作为划分特征。

假设用离散特征 A 对样本集 D 进行划分,AV 个可能的取值 {a¹, a², ..., aᵛ}。若使用 A 来对 D 进行划分,则会产生 V 个分支节点,其中第 v 个分支节点包含了 D 中所有在特征 A 上取值为 aᵛ 的样本,记为 Dᵛ

我们可以计算出用特征 AD 进行划分所获得的“信息增益”:

Gain(D,a)=Ent(D)v=1VDvDEnt(Dv)\text {Gain}(D, a) = \text {Ent}(D) - \sum_{v=1}^{V} \frac{|D^v|}{|D|} \text {Ent}(D^v)

  • |Dᵛ| / |D| 是分支 v 的权重,样本数越多的分支节点影响越大。
  • Ent(Dᵛ) 是分支节点 v 的信息熵。
  • ID3 算法 就是以信息增益为准则来选择划分属性的决策树算法。

缺点: 信息增益准则对可取值数目较多的特征有所偏好(例如,如果一个特征是 ID,那么每个样本一个取值,划分出的每个子集纯度都是最高的,信息增益会很大,但这没有泛化能力)。

c) 增益率 (Gain Ratio) - C4.5 算法

为了减少信息增益对多取值特征的偏好,C4.5 算法 使用“增益率”来选择最优划分特征。

增益率定义为:

Gain_ratio(D,A)=Gain(D,A)IV(A)\mathrm{Gain\_ratio}(D, A) = \frac{\mathrm{Gain}(D, A)}{IV(A)}

其中 IV(A) 称为特征 A 的“固有值” (Intrinsic Value):

IV(A)=v=1VDvDlog2(DvD)IV(A) = - \sum_{v=1}^{V} \frac{|D^v|}{|D|} \log_2 \left( \frac{|D^v|}{|D|} \right)

  • 特征 A 的可能取值数目越多(即 V 越大),IV(A) 的值通常会越大。

注意: 增益率准则对可取值数目较少的特征有所偏好。因此 C4.5 算法并非直接选择增益率最大的特征,而是使用一个启发式:先从候选划分特征中找出信息增益高于平均水平的特征,再从中选择增益率最高的。

d) 基尼指数 (Gini Index) - CART 算法

CART (Classification and Regression Tree) 算法使用“基尼指数”来选择划分属性。

数据集 D 的纯度也可以用基尼值来度量:

Gini(D)=k=1Ykkpkpk=1k=1Ypk2Gini(D) = \sum_{k=1}^{|Y|} \sum_{k' \neq k} p_k p_{k'} = 1 - \sum_{k=1}^{|Y|} p_k^2

  • Gini(D) 反映了从数据集 D 中随机抽取两个样本,其类别标记不一致的概率。
  • Gini(D) 越小,数据集 D 的纯度越高。

特征 A 的基尼指数定义为 (假设 A 是离散特征,有 V 个取值):

Gini_index(D,A)=v=1VDvDGini(Dv)Gini\_index(D, A) = \sum_{v=1}^{V} \frac{|D^v|}{|D|} \cdot Gini(D^v)

  • 选择那个使得划分后基尼指数最小的特征作为最优划分特征,即 A_* = arg min_{A} Gini_index(D, A)

特点: CART 生成的是 二叉树。对于连续特征,它会尝试所有可能的二分点;对于离散特征,它也会找出最优的二分组合。

2.2.1 连续特征的处理

在决策树中,特征可以是离散的(类别型)或连续的。对于连续特征,决策树的算法需要确定一个分割点,即根据特征值的大小将数据划分为两部分。

  • 对于连续特征 (A),先对所有样本按该特征值排序,记排序后的不同取值为

    {v1,v2,,vm}\{v_1, v_2, \dots, v_m\}

  • 决策树在构建过程中会选择一个合适的分割点 t(即 A ≤ t)来将数据分为两部分

    (vi,vi+1)之间取中点(t=(vi+vi+1)/2),(v_i, v_{i+1}) \text{之间取中点} (t = (v_i + v_{i+1})/2),

    将数据分为(At)(A>t)两部分。\text{将数据分为} (A \le t) \text{和} (A > t) \text{两部分}。

  • 选择 t 的方法通常是通过计算划分后的纯度指标(如信息增益、增益率、基尼指数或其他标准),选出最优的 tt^*

2.2.2 线搜索 (Line Search) 过程

线搜索(Line Search)是一种通过遍历所有可能的分割点来寻找最优分割点的过程。对于每个连续特征,我们会按照其特征值进行排序,然后计算每个分割点的纯度(例如信息增益或基尼指数),并选择使得纯度最大化的分割点。

2.2.2 二类分类中三种纯度度量的关系

  • 基尼指数 Gini(p)=2p(1p)Gini(p) = 2p(1-p)

    其中 p1-p 分别是属于两个类别的样本的比例。当 p1-p 趋近于 0 时,基尼指数会接近 0,说明数据集非常纯净。

  • Ent(p)=plog2p(1p)log2(1p)Ent(p) = -p\log_2 p - (1-p)\log_2(1-p)

    同样,当 p1-p 为 0 或 1 时,熵的值接近 0,表明数据集是纯净的。

  • 分类误差率 Err(p)=1max(p,1p)Err(p) = 1 - \max(p,1-p)

    它的特点是非常直观,但对于不平衡数据(例如,一个类别占大多数)可能会给出较差的评估。

熵和基尼指数都能很好地衡量不确定性,二者通常产生相似的结果。

分类误差率通常不会作为选择特征的标准,因为它在纯度较高时变化较小,无法敏感地反映数据的变化。

对二分类问题,三者随 p[0,1]p\in[0,1] 的曲线关系为

Err(p)12Ent(p)Gini(p)p,Err(p)\le \frac{1}{2}Ent(p)\le Gini(p)\quad\forall\,p,

二分类三种纯度度量

2.3 停止条件

递归划分过程何时停止?

  1. 当前节点包含的样本全属于同一类别: 无需划分,该节点成为叶节点。
  2. 当前属性集为空,或者所有样本在所有属性上取值相同: 无法划分,将该节点标记为叶节点,类别设定为该节点所含样本最多的类别(或根据具体问题定义)。
  3. 当前节点包含的样本集合为空: 不能划分,标记为叶节点,类别设定为其父节点所含样本最多的类别。

2.4 缺失值的处理

在决策树学习中,处理缺失值是一个需要解决的问题。不同的算法有不同的处理方式。

2.4.1 在算法层面如何处理缺失值

有几种常见的方法来处理缺失值:

  • 数据插补: 在训练决策树之前,将缺失的值使用该特征的均值、中位数或众数进行填充。

    注意: scikit-learn 的决策树实现不内置对缺失值的处理,需要用户在训练前进行预处理(如使用 SimpleImputer)。

  • 忽略样本: 直接忽略包含缺失值的样本。如果缺失值不多,这可能是一个简单有效的方案,但如果缺失样本较多,可能丢失大量信息。

  • 基于树的缺失值处理方法(某些算法支持): 一些决策树实现(如 C4.5)允许在划分时处理缺失值。

    • 计算纯度时: 仅使用该特征上非缺失的样本子集来计算信息增益(或其他纯度指标)。
    • 划分样本时: 对于在当前划分特征上有缺失值的样本,可以采用以下策略分配到子节点:
      • 将其分配到最有可能的分支(例如,根据已知值样本在各分支的比例)。
      • 将其分配到多个子节点,并赋予不同的权重。例如,如果一个样本在该特征上缺失,且已知值样本根据该特征分成了 A、B 两个分支,已知值样本中 70%去了 A,30%去了 B,那么可以将这个缺失值样本以 0.7 的权重分到 A 分支,以 0.3 的权重分到 B 分支。
      • 将“缺失”本身视为一个独立的特征取值或分支。

2.4.2 如何选择划分特征(当特征有缺失值时)

当考虑用一个有缺失值的特征进行划分时,传统的纯度计算方法需要调整:

  • 计算缺失值的概率: 可以在计算纯度指标时,对特征的缺失值情况进行惩罚。例如,C4.5 在计算增益率时会考虑样本在当前特征上已知值的比例。
  • 调整纯度计算: 只使用该特征上非缺失值的样本来计算纯度(如信息增益、基尼指数)。计算信息增益时,通常会乘以一个系数,这个系数等于当前节点中该特征非缺失样本的比例。

2.5 处理过拟合 (剪枝与 Bagging)

为了防止决策树 过拟合 (Overfitting),即模型在训练数据上表现很好,但在新数据上表现差,需要进行剪枝。过拟合通常是因为树生长得过于复杂,学习了训练数据中过多的噪声或特性。

a) 预剪枝 (Pre-pruning)

在决策树生成过程中,对每个节点在划分前先进行估计。若当前节点的划分不能带来决策树泛化性能提升(例如,在验证集上精度下降),则停止划分并将当前节点标记为叶节点。

  • 优点: 降低过拟合风险,减少训练时间和测试时间开销。
  • 缺点: 基于“贪心”本质,可能带来欠拟合风险(有些划分暂时看可能不优,但后续划分可能显著提升性能)。

常用判断条件:

  • 节点内样本数量小于阈值 (min_samples_split, min_samples_leaf)。
  • 树的深度达到预设值 (max_depth)。
  • 划分后信息增益(或其他指标)的提升小于阈值。
  • 划分后在独立的验证集上精度下降。

b) 后剪枝 (Post-pruning)

先从训练集生成一棵完整的决策树,然后自底向上地对非叶节点进行考察。若将该节点对应的子树替换为叶节点能带来决策树泛化性能提升,则将该子树替换为叶节点。

  • 优点: 通常比预剪枝保留了更多分支,欠拟合风险小,泛化性能往往优于预剪枝决策树。
  • 缺点: 训练时间开销比未剪枝和预剪枝决策树都要大得多。

常用方法:

  • 降低错误剪枝 (Reduced Error Pruning, REP): 使用验证集,将子树替换为叶节点后,如果验证集错误率降低或不变,则剪枝。
  • 代价复杂度剪枝 (Cost Complexity Pruning, CCP): 定义损失函数=经验熵+正则化项(树的复杂度),通过调整正则化系数 α (ccp_alpha) 来权衡模型复杂度和拟合度,生成一系列树,最后通过交叉验证选择最优子树。Scikit-learn 中常用此方法。

c) Bagging (Bootstrap Aggregating)

除了传统的预剪枝和后剪枝,我们还可以使用 Bagging (Bootstrap Aggregating) 技术来处理过拟合。Bagging 是通过从原始训练集中进行有放回抽样(Bootstrap),生成多个不同的训练集,然后基于每个训练集独立地训练一棵决策树,并将它们的预测结果进行集成(例如,分类任务投票,回归任务平均)来减少过拟合风险。

  • 思想: 对训练集做多次有放回抽样,训练多棵决策树,将其预测结果取平均(回归)或投票(分类)。
  • 优点: 显著降低高方差,缓解单棵树过拟合;增强了模型的稳定性;并行化简单。
  • 缺点: 牺牲了模型的解释性(因为是多个树的集成)。
  • 常用实现: sklearn.ensemble.BaggingClassifier(分类)和 sklearn.ensemble.BaggingRegressor(回归),以及特别针对决策树的 随机森林(Random Forest) (sklearn.ensemble.RandomForestClassifier/Regressor)。随机森林在 Bagging 的基础上进一步引入了特征随机性(在每个节点划分时只考虑特征的一个随机子集),进一步提升了性能和鲁棒性。。

2.6 二叉树和多叉树

2.6.1 二叉树与多叉树的区别
  • 二叉树:每个节点最多有两个子节点(通常表示为左右子节点),即每个决策都只能有两个结果。CART 算法生成的树通常是二叉树。
  • 多叉树:每个节点可以有多个子节点,表示每次决策有多个选择。ID3 和 C4.5 算法可以生成多叉树(对于离散特征,每个取值对应一个分支)。
2.6.2 选择二叉树还是多叉树
  • 二叉树 在算法实现上通常更简单,且对计算机资源的消耗更少。许多库(包括 scikit-learn)默认或只支持二叉树。
  • 多叉树 能处理更多的选择,但计算复杂度较高。对于离散特征,多叉划分虽然直观,但在某些情况下(如特征取值非常多)可能不够灵活。二叉树可以通过多次二分裂来模拟多叉分裂。

2.7 二分类和多分类

2.7.1 二分类

决策树在二分类问题中,通过划分使得子节点的样本尽可能只属于两个类别中的一个。可以使用信息增益、增益率或基尼指数作为纯度标准。常见的二分类问题如癌症检测、垃圾邮件分类等。

2.7.2 多分类

对于多分类问题,决策树会通过逐步划分的方式将样本集划分成多个子集。对于每个子集,决策树会重复上述过程,直到所有的样本被完全分类。可以使用 一对一一对多 的方法来构建多分类树。纯度计算方法(信息熵、基尼指数等)自然地扩展到多类别情况。

3. 决策树的优缺点

3.1 优点

  • 易于理解和解释: 可以可视化,符合人类的直观思维。
  • 数据预处理要求低: 不需要数据归一化或标准化(但处理缺失值仍需注意)。可以同时处理数值型和类别型数据(不同算法支持度不同)。
  • 能够处理多输出问题。
  • 计算复杂度相对较低: 预测阶段的复杂度是 O(log₂m),m 是样本数。
  • 可以验证模型: 使用统计测试来验证模型的可靠性。
  • 鲁棒性: 对于缺失值不敏感(某些算法如 C4.5 可以处理,但 scikit-learn 需要预处理)。

3.2 缺点

  • 容易过拟合: 尤其是在数据有噪声或样本量不足时,可能生成过于复杂的树。需要剪枝或集成方法来缓解。
  • 不稳定性: 数据微小的变动可能导致生成完全不同的树。可以通过集成方法(如随机森林)改善。
  • 最优决策树学习是 NP 难问题: 实际算法通常采用启发式方法(如贪心算法),只能得到局部最优解。
  • 可能创建有偏的树: 如果某些类别的样本数量远多于其他类别,生成的树可能会偏向于这些数量多的类别。建议先平衡数据集。
  • 对线性关系表达能力弱: 对于变量间存在复杂线性关系的问题,决策树可能需要很深的树才能拟合。

4. 使用 Scikit-learn 实现决策树 (Iris 数据集示例)

我们将使用经典的 Iris (鸢尾花) 数据集来演示如何用 Python 的 Scikit-learn 库构建决策树分类器。

目标: 根据鸢尾花的花萼长度 (Sepal Length)、花萼宽度 (Sepal Width)、花瓣长度 (Petal Length)、花瓣宽度 (Petal Width) 这四个特征来预测鸢尾花的种类 (Setosa, Versicolour, Virginica)。

4.0 决策树在 Scikit-learn 中的主要参数、属性与方法

在 Scikit-learn 中,DecisionTreeClassifierDecisionTreeRegressor 类提供了多种超参数来调整决策树的构建方式。

  • 参数(DecisionTreeClassifier/DecisionTreeRegressor
    • criterion:用于划分的标准。
      分类树可选 'gini'(基尼指数)或 'entropy'(信息增益)。
      回归树可选 'mse' (均方误差, 已废弃, 推荐使用 'squared_error'),'friedman_mse''mae' (平均绝对误差)。
      默认是分类树 'gini',回归树 'squared_error'
    • splitter:划分策略。'best' 表示选择最好的特征进行划分(默认)。
      'random' 表示随机选择部分特征后再从中选择最好的进行划分(随机森林常用)适用于样本量非常大的情况。
    • max_depth: 决策树的最大深度。控制树的复杂度,避免过拟合(预剪枝)。默认 None,表示不限制深度。
    • min_samples_split:内部节点再划分所需最小样本数。
      较高的值有助于避免过拟合(预剪枝)。可以是整数或小数(表示比例)。默认是 2。
    • min_samples_leaf:叶子节点最少样本数。
      一个分割点只有在左右分支都满足这个最少样本数条件时,才能进行划分(预剪枝)。可以是整数或小数。默认是 1。
    • min_weight_fraction_leaf:叶子节点最小的样本权重和,小于这个值会和兄弟节点一起被剪枝,默认为0不考虑权重,当较多样本有缺失值或样本分布类别偏差很大需要考虑
    • max_features:每次分割考虑的最大特征数(随机森林常用)。
      可以是整数、浮点数(比例)、"auto"/"sqrt" (等于 sqrt(n_features))、"log2" (等于 log2(n_features)) 或 None (考虑所有特征)。
    • max_leaf_nodes:最大叶子节点数,可以防止过拟合,默认None
    • min_impurity_decrease:分裂所减小的不纯度小于等于该值才会分裂
    • min_impurity_split:建树时候早停的基尼不纯度阈值,限制决策树的增长
    • class_weight:类别样重,指定样本各类别的权重
    • ccp_alpha:代价复杂度剪枝参数(后剪枝)。取值越大,剪枝越强。默认是 0.0 (不剪枝)。
  • 重要属性 (训练后可用)
    • feature_importances_: 一个 numpy 数组,表示每个特征的重要性评分(总和为 1)。重要性基于该特征在树中降低纯度(信息增益或基尼不纯度)的总量。
    • tree_:底层的 _tree.Tree 对象,包含了树结构的详细信息(节点索引、左右子节点、特征索引、阈值、不纯度等)。
    • n_features_in_: 训练时输入的特征数量。
    • classes_: 分类树独有,训练期间遇到的类标签数组。
    • n_classes_: 分类树独有,类别的数量。、
    • n_outputs_: 模型输出的数量(对于多输出问题)。
    • n_node_samples_: 每个节点中的训练样本数量数组。
  • 常用方法
    • .fit(X, y):使用训练数据 X 和目标变量 y 训练决策树模型。
    • .predict(X):对新数据 X 进行预测。分类树返回类别标签,回归树返回预测值。
    • .predict_proba(X)分类树独有,预测每个样本属于各个类别的概率。
    • .score(X, y):返回模型的评估分数。分类树返回在 (X, y) 上的准确率,回归树返回 R2R^2 分数。
    • apply(X): 返回每个样本所属的叶节点索引。
    • get_params([deep]): 获取模型的超参数。
    • set_params(**params): 设置模型的超参数。
    • export_text(decision_tree[, …])、将决策树导出为文本规则表示。
    • plot_tree(decision_tree[, …]):将决策树可视化(需要 Matplotlib)。

4.1 导入所需库

1
2
3
4
5
6
7
8
9
import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
# 注意:Scikit-learn >= 0.20 版本需要
# from sklearn.impute import SimpleImputer

4.2 加载和准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 加载 Iris 数据集
iris = load_iris()
X = iris.data # 特征数据 (numpy array)
y = iris.target # 目标标签 (numpy array)
feature_names = iris.feature_names # 特征名称
target_names = iris.target_names # 类别名称

# (可选)处理缺失值示例 (如果数据有缺失值)
# 例如,使用均值填充
# imputer = SimpleImputer(missing_values=np.nan, strategy='mean')
# X_imputed = imputer.fit_transform(X)
# X = X_imputed # 使用填充后的数据

# (可选)将数据转换为 Pandas DataFrame 以便查看
# df = pd.DataFrame(X, columns=feature_names)
# df['species'] = y
# df['species_name'] = df['species'].map({0: target_names[0], 1: target_names[1], 2: target_names[2]})
# print(df.head())
# print(df['species_name'].value_counts())

# 分割数据集为训练集和测试集
# random_state 保证每次分割结果一致,便于复现
# stratify=y 保证训练集和测试集中各类样本的比例与原始数据集一致 (重要!)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)

print(f"训练集大小: {X_train.shape[0]} samples")
print(f"测试集大小: {X_test.shape[0]} samples")

4.3 创建和训练决策树模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建决策树分类器实例
# criterion='gini': 使用基尼指数作为划分标准 (默认)
# criterion='entropy': 使用信息增益作为划分标准
# max_depth: 树的最大深度 (预剪枝参数)
# min_samples_split: 内部节点再划分所需最小样本数 (预剪枝参数)
# min_samples_leaf: 叶子节点最少样本数 (预剪枝参数)
# ccp_alpha: 代价复杂度剪枝的参数 (后剪枝相关)
dt_classifier = DecisionTreeClassifier(criterion='gini', max_depth=None, random_state=42) # 先不加预剪枝

# 使用训练数据训练模型
dt_classifier.fit(X_train, y_train)

print("决策树模型训练完成!")

# 查看特征重要性 (属性示例)
print("\n特征重要性:")
for name, importance in zip(feature_names, dt_classifier.feature_importances_):
print(f"{name}: {importance:.4f}")

4.4 模型预测与评估

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 使用训练好的模型对测试集进行预测
y_pred = dt_classifier.predict(X_test)

# 评估模型性能
# 准确率
accuracy = accuracy_score(y_test, y_pred)
print(f"\n模型在测试集上的准确率: {accuracy:.4f}")

# 分类报告 (包含精确率、召回率、F1分数)
print("\n分类报告:")
print(classification_report(y_test, y_pred, target_names=target_names))

# 混淆矩阵
print("\n混淆矩阵:")
cm = confusion_matrix(y_test, y_pred)
print(cm)

# 可视化混淆矩阵 (可选)
# import seaborn as sns
# plt.figure(figsize=(6, 4))
# sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=target_names, yticklabels=target_names)
# plt.xlabel('Predicted Label')
# plt.ylabel('True Label')
# plt.title('Confusion Matrix')
# plt.show()

4.5 可视化决策树

理解决策树如何做出决策的一个好方法是将其可视化。

a) 使用 plot_tree (需要 Matplotlib)

1
2
3
4
5
6
7
8
9
plt.figure(figsize=(20, 10)) # 设置图形大小
plot_tree(dt_classifier,
filled=True, # 填充颜色以表示类别纯度
feature_names=feature_names, # 显示特征名称
class_names=target_names, # 显示类别名称
rounded=True, # 节点框使用圆角
fontsize=10) # 字体大小
plt.title("Decision Tree for Iris Classification (Gini)")
plt.show()

b) 使用 export_text 输出文本表示

1
2
3
tree_rules = export_text(dt_classifier, feature_names=list(feature_names))
print("\n决策树规则 (文本表示):")
print(tree_rules)

4.6 探索剪枝 (示例)

尝试添加预剪枝参数,例如限制最大深度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 创建带预剪枝的决策树
dt_classifier_pruned = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)

# 训练
dt_classifier_pruned.fit(X_train, y_train)

# 预测
y_pred_pruned = dt_classifier_pruned.predict(X_test)

# 评估
accuracy_pruned = accuracy_score(y_test, y_pred_pruned)
print(f"\n剪枝后 (max_depth=3) 模型准确率: {accuracy_pruned:.4f}")

# 可视化剪枝后的树
plt.figure(figsize=(12, 6))
plot_tree(dt_classifier_pruned, filled=True, feature_names=feature_names, class_names=target_names, rounded=True, fontsize=10)
plt.title("Pruned Decision Tree (max_depth=3)")
plt.show()

剪枝后的树通常更简单,有时准确率可能会略有下降,但泛化能力可能更好(在这个简单的 Iris 数据集上可能不明显)。

5. 决策树回归

决策树不仅能用于分类问题,还能用于回归问题。**

5.1 最小二乘回归树

对于回归问题,决策树会在每个叶节点保存该节点的均值。在构建过程中,决策树将选择能够最小化节点内部 均方误差(MSE) 的特征和分割点作为划分标准。

  • 目标: 通过递归划分,使得每个子集(节点)内的目标值(y)尽可能接近其平均值,从而最小化整体的平方误差。

  • 损失函数: 用于衡量一个节点(数据集 D)的“不纯度”或方差,即节点内所有样本目标值与该节点目标值均值之间的平方误差之和。

    L(D)=iD(yiyˉD)2L(D) = \sum_{i \in D} (y_i - \bar{y}_D)^2

    其中,yiy_i 是节点 D 中第 i 个样本的真实目标值,yˉD\bar{y}_D 是节点 D 中所有样本目标值的均值。

  • 划分标准: 在选择特征和分割点时,回归树会遍历所有可能的特征和分割点,计算按该点划分后,左右子节点加权后的总均方误差(或称为平方误差的减少量)。选择使总均方误差最小的划分点。

    minA,s[iD1(A,s)(yiyˉD1)2+iD2(A,s)(yiyˉD2)2]\min_{A, s} \left[ \sum_{i \in D_1(A, s)} (y_i - \bar{y}_{D_1})^2 + \sum_{i \in D_2(A, s)} (y_i - \bar{y}_{D_2})^2 \right]

    其中,AA 是特征,ss 是分割点,D1D_1D2D_2 是划分后的两个子集,yˉD1\bar{y}*{D_1}yˉD2\bar{y}*{D_2} 分别是子集 D1D_1D2D_2 的目标值均值。

  • 实现示例: Scikit-learn 中使用 DecisionTreeRegressor 类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from sklearn.tree import DecisionTreeRegressor
    from sklearn.metrics import mean_squared_error, r2_score

    # 假设 y_train_continuous 是连续的目标值
    # reg = DecisionTreeRegressor(criterion='squared_error', max_depth=5, random_state=42)
    # reg.fit(X_train, y_train_continuous)
    # y_pred_reg = reg.predict(X_test)

    # 评估回归模型
    # mse = mean_squared_error(y_test_continuous, y_pred_reg)
    # r2 = r2_score(y_test_continuous, y_pred_reg)
    # print(f"\n回归树 MSE: {mse:.4f}, R2: {r2:.4f}")

  • 评估指标: 均方误差(MSE)、均方根误差(RMSE)、R2R^2 分数。

5.2 分类树与回归树的区别

  • 目标变量类型:
    • 分类树: 离散型(类别标签)。
    • 回归树: 连续型(数值)。
  • 叶节点输出:
    • 分类树: 叶节点通常代表一个类别标签(该节点中样本最多的类别)或类别的概率分布。
    • 回归树: 叶节点代表一个预测数值,通常是该节点中所有样本目标值的均值。
  • 划分标准/损失函数:
    • 分类树: 使用衡量“纯度”的指标,如信息增益、增益率、基尼指数,目标是最大化纯度(最小化不纯度)。
    • 回归树: 使用衡量“方差”或“误差”的指标,如均方误差(MSE)或平均绝对误差(MAE),目标是最小化节点内误差。
对比维度 分类树(Classification Tree) 回归树(Regression Tree)
目标变量类型 离散型(类别标签) 连续型(数值)
叶节点输出 类别标签(或类别概率分布) 预测数值(如均值、中位数等)
划分标准/损失函数 信息增益、增益率、基尼指数(衡量纯度) 均方误差(MSE)、平均绝对误差(MAE)(衡量误差)
适用问题类型 分类问题(如判断是否购买商品) 回归问题(如预测房价、温度等)
示例应用场景 客户流失预测、垃圾邮件识别 房价预测、销售额预测

6. 小结

决策树是一种强大且直观的机器学习模型。理解其核心原理(信息熵、信息增益、基尼指数)、构建过程(递归划分)以及防止过拟合的方法(剪枝)至关重要。通过 Scikit-learn 等库可以方便地实现和应用决策树解决分类和回归问题。