决策树(Decision tree)算法详解(ID3、C4.5、CART)

文章目录

    • 一、决策树介绍
      • 1.1 决策树的结构特征
      • 1.2 决策树的构建三步骤
      • 1.3 决策树构建例子
    • 二、ID3决策树:基于信息增益的决策模型
      • 2.1 信息增益的公式与符号解析
      • 2.2 信息增益的意义
      • 2.3 ID3决策树案例演示:贷款申请分类
      • 2.4 ID3决策树缺陷
    • 三、C4.5决策树:信息增益率的优化
      • 3.1 信息增益率的公式与符号解析
      • 3.2 信息增益率的意义
      • 3.3 C4.5决策树:信息增益率修正ID3的过拟合缺陷
      • 3.4 结论:C4.5如何解决过拟合
      • 3.5 C4.5的局限性
    • 四、CART决策树:基于基尼指数的二叉树模型
      • 4.1 基尼指数的公式与符号解析
      • 4.2 基尼指数的意义
      • 4.3 CART决策树案例演示:贷款申请的基尼指数计算
      • 4.4 Scikit-learn 中 CART 决策树的 API
      • 4.5 小结
    • 五、三大决策树模型对比总结
    • 六、其他重要决策树算法
      • 6.1 CHAID:基于统计检验的决策树
      • 6.2 QUEST:快速无偏决策树
      • 6.3 条件推断树:统计驱动的决策树
      • 6.4 斜决策树(Oblique Decision Tree)
      • 6.5 流数据决策树(Hoeffding Tree)

一、决策树介绍

决策树是一种树形结构的监督学习算法,其核心思想是通过一系列条件判断对数据进行分类,就像人类在做决策时的逻辑判断过程。

1.1 决策树的结构特征

  • 内部节点:表示一个特征上的判断(如"年龄是否大于30")
  • 分支:表示判断结果的输出(如"是"或"否")
  • 叶子节点:表示最终的分类结果(如"同意贷款"或"拒绝贷款")

1.2 决策树的构建三步骤

  1. 特征选择:从众多特征中挑选分类能力最强的特征
  2. 树的生成:基于选择的特征递归构建决策树
  3. 剪枝优化:防止过拟合,提升模型泛化能力

1.3 决策树构建例子

你有一个朋友正在考虑是否要去参加一个社交活动,他/她/他们可能会这样思考:
朋友问你:这个活动值得去吗?
于是你开始了解情况:
问:这个活动是线上还是线下?
答:线下,在市中心的一个咖啡馆。
问:天气怎么样?
答:那天天气不错。
问:有没有感兴趣的人会去?
答:有,你喜欢的那位也会去。
问:交通方便吗?
答:地铁直达,很方便。
……
于是你的大脑里就构建了这样一棵“决策树”:
在这里插入图片描述
绘图代码:

import graphviz# 创建有向图,并设置中文字体
# 1. 导入Graphviz库:
#    graphviz是Python与Graphviz软件交互的接口,负责生成可视化图形(需提前安装Graphviz软件)
import graphviz  # 2. 创建有向图对象,并配置中文显示:
#    - comment:给图加注释(仅文档用途,不影响渲染)
#    - format:指定输出文件格式(如png、pdf等)
#    - node_attr/edge_attr/graph_attr:设置节点、边、全局的字体为"SimHei"(微软雅黑),解决中文乱码问题
dot = graphviz.Digraph(comment='社交活动决策树',       # 图的描述信息(可选)format='png',                  # 输出文件格式node_attr={'fontname': 'SimHei'},  # 节点文字用微软雅黑(支持中文)edge_attr={'fontname': 'SimHei'},  # 边的标签文字用微软雅黑graph_attr={'fontname': 'SimHei'}  # 全局文字(如标题)用微软雅黑
)# 3. 添加节点(node):
#    格式:dot.node(节点唯一标识, 显示的文字)
#    节点ID(如'A')是内部标记,显示文字是图形中实际展示的内容
dot.node('A', '活动类型')         # 根节点:决策起点(活动类型)
dot.node('B', '线上')             # 分支1:活动类型为线上
dot.node('C', '线下')             # 分支2:活动类型为线下
dot.node('D', '天气是否好?')     # 线下活动的子判断:天气条件
dot.node('E', '否')               # 天气分支:不好
dot.node('F', '是')               # 天气分支:好
dot.node('G', '有没有感兴趣的人?') # 天气好时的子判断:兴趣条件
dot.node('H', '否')               # 兴趣分支:没有
dot.node('I', '有')               # 兴趣分支:有
dot.node('J', '去参加')           # 最终决策:参加
dot.node('K', '考虑其他安排')     # 最终决策:不参加# 4. 添加边(edge):
#    格式:dot.edge(起点ID, 终点ID, label=边的说明文字)
#    label可选,用于解释分支条件
dot.edge('A', 'B')                # 活动类型 → 线上(无额外条件,直接分支)
dot.edge('A', 'C')                # 活动类型 → 线下(无额外条件,直接分支)
dot.edge('C', 'D')                # 线下活动 → 判断天气
dot.edge('D', 'E', label='否')    # 天气判断 → 否(边标注“否”)
dot.edge('D', 'F', label='是')    # 天气判断 → 是(边标注“是”)
dot.edge('F', 'G')                # 天气好 → 判断是否有感兴趣的人
dot.edge('G', 'H', label='否')    # 兴趣判断 → 否(边标注“否”)
dot.edge('G', 'I', label='是')    # 兴趣判断 → 是(边标注“是”)
dot.edge('I', 'J')                # 有感兴趣的人 → 去参加(最终决策)
dot.edge('H', 'K')                # 没有感兴趣的人 → 考虑其他安排(最终决策)# 5. 渲染并查看图形:
#    - 第一个参数:输出文件名(生成的文件会是 decision_tree_social_event.png)
#    - view=True:生成后自动用系统默认程序打开图片
#    注意:运行前需确保:
#      ① 系统安装了Graphviz软件(https://graphviz.org/download/)
#      ② Graphviz的bin目录已加入系统环境变量(如 C:\Program Files\Graphviz\bin)
dot.render('decision_tree_social_event', view=True)

二、ID3决策树:基于信息增益的决策模型

2.1 信息增益的公式与符号解析

信息增益公式
g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A) = H(D) - H(D|A) g(D,A)=H(D)H(DA)

  • g ( D , A ) g(D, A) g(D,A):特征 A A A对数据集 D D D的信息增益
  • H ( D ) H(D) H(D):数据集 D D D的经验熵,衡量数据的不确定性
    H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ H(D) = -\sum_{k=1}^{K}\frac{|C_k|}{|D|}\log_2\frac{|C_k|}{|D|} H(D)=k=1KDCklog2DCk
    其中, K K K为类别数, ∣ C k ∣ |C_k| Ck为第 k k k类的样本数, ∣ D ∣ |D| D为总样本数。
  • H ( D ∣ A ) H(D|A) H(DA):特征 A A A给定条件下 D D D的经验条件熵
    H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 K ∣ D i k ∣ ∣ D i ∣ log ⁡ 2 ∣ D i k ∣ ∣ D i ∣ H(D|A) = \sum_{i=1}^{n}\frac{|D_i|}{|D|}H(D_i) = -\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(DA)=i=1nDDiH(Di)=i=1nDDik=1KDiDiklog2DiDik
    其中, n n n为特征 A A A的取值个数, ∣ D i ∣ |D_i| Di为特征 A A A取第 i i i个值的样本数, ∣ D i k ∣ |D_{ik}| Dik D i D_i Di中属于第 k k k类的样本数。

2.2 信息增益的意义

信息增益衡量的是某个特征对数据分类不确定性的减少程度。直观来说,它反映了在已知某个特征的取值后,我们对样本分类结果的不确定性降低了多少。
例如,在判断一个人是否适合贷款时,如果“是否有房”这一特征能显著帮助我们确定贷款审批结果,那么这个特征的信息增益就较大。换句话说,信息增益越大,该特征在分类任务中的判别能力越强。

2.3 ID3决策树案例演示:贷款申请分类

以贷款申请数据为例,计算各特征的信息增益并构建ID3决策树。

数据集

年龄有工作有房子信贷情况类别
青年一般拒绝
青年拒绝
青年同意
青年一般同意
青年一般拒绝
中年一般拒绝
中年拒绝
中年同意
中年非常好同意
中年非常好同意
老年非常好同意
老年同意
老年同意
老年非常好同意
老年一般拒绝

步骤1:计算数据集D的经验熵H(D)
数据集中共有15条样本,其中"同意"类5条,"拒绝"类10条:
H ( D ) = − 5 15 log ⁡ 2 5 15 − 10 15 log ⁡ 2 10 15 = 0.9183 H(D) = -\frac{5}{15}\log_2\frac{5}{15} - \frac{10}{15}\log_2\frac{10}{15} = 0.9183 H(D)=155log21551510log21510=0.9183

步骤2:计算各特征的条件熵和信息增益
以"年龄"特征为例,其取值为{青年, 中年, 老年}:

  • 青年样本:5条(ID1-5),其中拒绝4条,同意1条
    H ( 青年 ) = − 4 5 log ⁡ 2 4 5 − 1 5 log ⁡ 2 1 5 = 0.7219 H(青年) = -\frac{4}{5}\log_2\frac{4}{5} - \frac{1}{5}\log_2\frac{1}{5} = 0.7219 H(青年)=54log25451log251=0.7219
  • 中年样本:5条(ID6-10),其中拒绝2条,同意3条
    H ( 中年 ) = − 2 5 log ⁡ 2 2 5 − 3 5 log ⁡ 2 3 5 = 0.9709 H(中年) = -\frac{2}{5}\log_2\frac{2}{5} - \frac{3}{5}\log_2\frac{3}{5} = 0.9709 H(中年)=52log25253log253=0.9709
  • 老年样本:5条(ID11-15),其中拒绝1条,同意4条
    H ( 老年 ) = − 1 5 log ⁡ 2 1 5 − 4 5 log ⁡ 2 4 5 = 0.7219 H(老年) = -\frac{1}{5}\log_2\frac{1}{5} - \frac{4}{5}\log_2\frac{4}{5} = 0.7219 H(老年)=51log25154log254=0.7219
  • 条件熵:
    H ( D ∣ 年龄 ) = 5 15 × 0.7219 + 5 15 × 0.9709 + 5 15 × 0.7219 = 0.8049 H(D|年龄) = \frac{5}{15} \times 0.7219 + \frac{5}{15} \times 0.9709 + \frac{5}{15} \times 0.7219 = 0.8049 H(D年龄)=155×0.7219+155×0.9709+155×0.7219=0.8049
  • 信息增益:
    g ( D , 年龄 ) = 0.9183 − 0.8049 = 0.1134 g(D, 年龄) = 0.9183 - 0.8049 = 0.1134 g(D,年龄)=0.91830.8049=0.1134

同理计算其他特征的信息增益:

  • g ( D , 有工作 ) = 0.3236 g(D, 有工作) = 0.3236 g(D,有工作)=0.3236
  • g ( D , 有房子 ) = 0.4208 g(D, 有房子) = 0.4208 g(D,有房子)=0.4208
  • g ( D , 信贷情况 ) = 0.3623 g(D, 信贷情况) = 0.3623 g(D,信贷情况)=0.3623

步骤3:选择信息增益最大的特征分裂
"有房子"的信息增益最大(0.4208),作为根节点分裂,得到左右子树:

  • 有房子:7条样本(ID4,8,9,10,11,12,14),其中6条同意,1条拒绝
  • 无房子:8条样本(ID1,2,3,5,6,7,13,15),其中2条同意,6条拒绝

步骤4:递归构建子树
对每个子树重复上述过程,最终生成完整的ID3决策树。

在这里插入图片描述

这是一棵用于 贷款申请分类 的决策树(基于ID3算法,通过信息熵选择分裂特征),核心判断逻辑如下:

根节点:判断“是否有房子”

  • 条件:按特征取值分裂(右分支为 有房子 > 0.5,左分支为 有房子 ≤ 0.5)。
  • 信息增益:0.42(衡量该特征对分类的贡献度)。
  • 样本数:15(全量样本)。
  • 类别分布:拒绝6人,同意9人(分布:拒绝=6,同意=9)。

分支逻辑

  • 右分支(有房子 > 0.5)

    • 样本:6人(所有“有房”申请人)。
    • 类别分布:同意6人,拒绝0人。
    • 决策:直接判定为 同意(叶子节点)。
  • 左分支(有房子 ≤ 0.5)

    • 进入下一层判断 “是否有工作”

第二层节点:判断“是否有工作”

  • 条件:按特征取值分裂(右分支为 有工作 > 0.5,左分支为 有工作 ≤ 0.5)。
  • 信息增益:0.918(该特征对剩余样本的分类贡献度)。
  • 样本数:9人(所有“无房”申请人)。
  • 类别分布:拒绝6人,同意3人(分布:拒绝=6,同意=3)。

最终叶子节点

  • 左子节点(有工作 ≤ 0.5)

    • 样本:6人(无房且无工作的申请人)。
    • 类别分布:同意0人,拒绝6人。
    • 决策拒绝(叶子节点)。
  • 右子节点(有工作 > 0.5)

    • 样本:3人(无房但有工作的申请人)。
    • 类别分布:同意3人,拒绝0人。
    • 决策同意(叶子节点)。

决策树通过 “有房→有工作” 的两步判断,输出贷款决策:

  • 有房 → 直接同意;
  • 无房但有工作 → 同意;
  • 无房且无工作 → 拒绝。

(注:LabelEncoder 按字母序编码类别,此处“同意”为0,“拒绝”为1,节点分布以 拒绝=X,同意=Y 格式展示。)

绘图代码:

# 导入必要的库
import pandas as pd  # 用于数据处理和构建 DataFrame
from collections import Counter  # 用于统计样本数量
from math import log2  # 用于计算对数
from graphviz import Digraph  # 用于可视化决策树
from sklearn.preprocessing import LabelEncoder  # 用于将字符串特征编码为数字# =============================================
# 1. 数据准备
# =============================================
# 构建训练数据集,包含 5 个特征和 1 个目标变量
data = {# 每个样本的唯一编号,容易造成过拟合# "ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],# 年龄:青年、中年、老年"年龄": ["青年", "青年", "青年", "青年", "青年", "中年", "中年", "中年", "中年", "中年","老年", "老年", "老年", "老年", "老年"],# 是否有工作:是、否"有工作": ["否", "否", "是", "是", "否", "否", "否", "是", "否", "否","否", "否", "是", "是", "否"],# 是否有房子:是、否"有房子": ["否", "否", "否", "是", "否", "否", "否", "是", "是", "是","是", "是", "否", "否", "否"],# 信贷情况:一般、好、非常好"信贷情况": ["一般", "好", "好", "一般", "一般", "一般", "好", "好", "非常好", "非常好","非常好", "好", "好", "非常好", "一般"],# 类别:是否贷款成功(同意/拒绝)"类别": ["拒绝", "拒绝", "同意", "同意", "拒绝", "拒绝", "拒绝", "同意", "同意", "同意","同意", "同意", "同意", "同意", "拒绝"]
}# 将数据转换为 pandas 的 DataFrame 格式
df = pd.DataFrame(data)# 提取特征列名列表(所有列除了最后一列)
features = list(df.columns[:-1])  # 特征:ID、年龄、有工作、有房子、信贷情况# 目标变量列名(最后一列)
target = df.columns[-1]  # 类别列名# =============================================
# 2. 信息熵与信息增益计算
# =============================================def entropy(y):"""计算信息熵 H(Y)y: 当前样本的目标变量值(如:['拒绝', '同意', ...])返回:当前样本的信息熵"""counts = Counter(y)  # 统计每个类别的数量total = len(y)  # 总样本数if total == 0:return 0  # 防止除以 0# 计算 -Σ(p * log2(p))return -sum((count / total) * log2(count / total) for count in counts.values())def conditional_entropy(X_col, y):"""计算条件熵 H(Y|X)X_col: 某个特征列的值(如:['青年', '中年', ...])y: 对应的目标变量值返回:给定该特征下目标变量的条件熵"""values = set(X_col)  # 获取该特征的所有唯一值total = len(y)  # 总样本数ent = 0for v in values:mask = (X_col == v)  # 找到该特征值对应的样本y_sub = y[mask]  # 取出这些样本的目标变量ent += len(y_sub) / total * entropy(y_sub)  # 加权求和return entdef info_gain(dataset, feature):"""计算某个特征的信息增益 IG(Y, X)dataset: 整个数据集feature: 当前特征名称(如:"年龄")返回:该特征的信息增益"""X_col = dataset[feature]  # 取出该特征列y = dataset[target]  # 取出目标变量列return entropy(y) - conditional_entropy(X_col, y)  # 信息增益 = 总熵 - 条件熵# =============================================
# 3. 手动实现 ID3 决策树
# =============================================def id3_tree(dataset, features):"""使用 ID3 算法递归构建决策树dataset: 当前数据集features: 当前可用特征列表返回:当前节点的决策树结构字典"""target_values = dataset[target].unique()  # 获取当前目标变量的唯一值# 终止条件1:当前样本全部属于同一类if len(target_values) == 1:return {'label': target_values[0]}  # 返回叶子节点标签# 终止条件2:没有可用特征if not features:most_common = dataset[target].mode().iloc[0]  # 返回多数类作为预测结果return {'label': most_common}# 选择信息增益最大的特征gains = {f: info_gain(dataset, f) for f in features}  # 计算每个特征的信息增益best_feature = max(gains, key=gains.get)  # 选出最大增益的特征# 构建当前节点信息class_counts = dict(Counter(dataset[target]))  # 统计目标变量分布node_info = {'feature': best_feature,'gain': gains[best_feature],  # 信息增益'class_dist': class_counts,  # 类别分布'n_samples': len(dataset),  # 样本数量'children': {}  # 子节点}# 递归划分每个特征值remaining_features = [f for f in features if f != best_feature]  # 剩余可用特征for raw_value in dataset[best_feature].unique():  # 遍历当前特征的每个唯一值sub_data = dataset[dataset[best_feature] == raw_value]  # 划分子数据集node_info['children'][raw_value] = id3_tree(sub_data, remaining_features)  # 递归生成子树return node_info# =============================================
# 4. 构建特征编码器(用于生成 CART 风格的分割条件)
# =============================================
# 保存每个特征的编码器,便于后续可视化时使用
feature_encoders = {}for feat in features:le = LabelEncoder()  # 创建编码器le.fit(df[feat])  # 拟合该特征的编码规则feature_encoders[feat] = le  # 保存编码器# =============================================
# 5. 决策树可视化(CART 风格,支持中文)
# =============================================
def visualize_tree_cart_style(tree, feature_encoders, graph=None, parent_id=None, edge_label=""):"""使用 Graphviz 可视化决策树(CART 风格)tree: 当前树节点结构feature_encoders: 编码器字典graph: 当前图对象parent_id: 父节点 IDedge_label: 边上的标签返回:Graphviz 图对象"""from uuid import uuid4  # 用于生成唯一节点 IDif graph is None:# 初始化一个空图,设置字体支持中文graph = Digraph('DecisionTree',format='png',node_attr={'fontname': 'SimHei'},  # 支持中文节点edge_attr={'fontname': 'SimHei'},  # 支持中文边标签graph_attr={'fontname': 'SimHei', 'rankdir': 'TB'}  # 树方向从上往下)node_id = str(uuid4())  # 生成当前节点的唯一标识符if 'label' in tree:# 如果是叶子节点,显示类别标签label = f"类别: {tree['label']}"shape = "box"  # 叶子节点用矩形else:# 如果是内部节点,显示特征、增益等信息feat_name = tree['feature']metric = round(tree.get('gain', 0), 3)  # 获取信息增益n = tree['n_samples']  # 样本数dist = ", ".join([f"{k}={v}" for k, v in tree['class_dist'].items()])  # 分布# 构造节点标签label = (f"{feat_name}\\n"f"信息增益={metric}\\n"f"样本数={n}\\n"f"分布:{dist}")shape = "ellipse"  # 内部节点用椭圆graph.node(node_id, label=label, shape=shape)  # 添加当前节点到图中if parent_id:# 如果有父节点,则画边graph.edge(parent_id, node_id, label=edge_label)if 'children' in tree:# 如果有子节点,递归绘制feat_name = tree['feature']encoder = feature_encoders[feat_name]  # 获取该特征的编码器unique_values = df[feat_name].unique()  # 获取原始特征值sorted_values = sorted(encoder.transform(unique_values))  # 编码后的排序值for raw_value, child in tree['children'].items():encoded_value = encoder.transform([raw_value])[0]  # 把原始值转为编码值display_label = get_edge_label(feat_name, encoded_value, sorted_values)  # 生成边标签# 递归绘制子树visualize_tree_cart_style(child, feature_encoders, graph, node_id, display_label)return graphdef get_edge_label(feat_name, encoded_value, sorted_values):"""生成边标签,例如:年龄 <= 0.5、年龄 > 0.5feat_name: 特征名encoded_value: 编码后的特征值sorted_values: 排序后的编码值列表返回:边标签字符串"""index = sorted_values.index(encoded_value)if index == 0:threshold = (sorted_values[index] + sorted_values[index + 1]) / 2return f"{feat_name} <= {threshold:.1f}"elif index == len(sorted_values) - 1:threshold = (sorted_values[index - 1] + sorted_values[index]) / 2return f"{feat_name} > {threshold:.1f}"else:low = (sorted_values[index - 1] + sorted_values[index]) / 2high = (sorted_values[index] + sorted_values[index + 1]) / 2return f"{low:.1f} < {feat_name} <= {high:.1f}"# =============================================
# 6. 主程序:构建并可视化 ID3 决策树
# =============================================# 构建模型
tree_id3 = id3_tree(df, features)  # 构建 ID3 决策树# 设置可视化图例格式
dot_id3 = visualize_tree_cart_style(tree_id3, feature_encoders)  # 可视化 ID3 树# 保存为 PNG 文件,并自动打开查看
dot_id3.render("id3_decision_tree_with_id", view=True)

2.4 ID3决策树缺陷

ID3决策树的核心优势是计算简单、可解释性强,但存在明显缺陷:倾向于选择取值较多的特征(如"ID"特征可能有最大信息增益),导致过拟合风险。

ID3决策树的缺陷演示:被“ID”特征误导的过拟合

  1. 构造含干扰特征的数据集
    在原有贷款数据中加入 “ID” 特征(唯一标识,与贷款结果无实际关联),数据如下:
ID年龄有工作有房子信贷情况类别
1青年一般拒绝
2青年拒绝
3青年同意

(共15条,ID=1~15,类别不变)

  1. 信息增益计算(核心对比)

    ① 总熵 ( H(D) )
    总样本15条,同意=9拒绝=6
    H ( D ) = − 9 15 log ⁡ 2 9 15 − 6 15 log ⁡ 2 6 15 ≈ 0.918 H(D) = -\frac{9}{15}\log_2\frac{9}{15} - \frac{6}{15}\log_2\frac{6}{15} \approx 0.918 H(D)=159log2159156log21560.918

    ② “ID”特征的信息增益
    “ID”有15个唯一值(每个ID对应1条样本),每个子集 D i D_i Di 只有1条样本(纯类别,熵为0):
    H ( D ∣ ID ) = ∑ i = 1 15 1 15 × 0 = 0 ⇒ g ( D , ID ) = 0.918 − 0 = 0.918 H(D|\text{ID}) = \sum_{i=1}^{15} \frac{1}{15} \times 0 = 0 \quad \Rightarrow \quad g(D, \text{ID}) = 0.918 - 0 = 0.918 H(DID)=i=115151×0=0g(D,ID)=0.9180=0.918

    ③ 有效特征“有房子”的信息增益
    “有房子”分两类(无房=8条,有房=7条),条件熵计算得 H ( D ∣ 有房子 ) ≈ 0.497 H(D|\text{有房子}) \approx 0.497 H(D有房子)0.497,故:
    g ( D , 有房子 ) ≈ 0.918 − 0.497 = 0.421 g(D, \text{有房子}) \approx 0.918 - 0.497 = 0.421 g(D,有房子)0.9180.497=0.421

  2. 缺陷暴露:被“ID”误导的过拟合

    • 增益排序g(D, ID) > g(D, 有房子),ID3会优先选 “ID” 作为根节点。
    • 逻辑荒谬:“ID”是纯标识(与贷款结果无关),但因取值唯一,决策树会分裂出15个叶子节点(每个ID对应一个结果),导致 模型仅记忆样本,无法泛化新数据(如ID=16的新样本,树无法判断)。

这就是ID3的核心缺陷:盲目追求“信息增益最大”,倾向选择取值多的特征(如ID),最终导致过拟合

三、C4.5决策树:信息增益率的优化

3.1 信息增益率的公式与符号解析

信息增益率公式
Gain_Ratio ( D , A ) = g ( D , A ) IV ( A ) \text{Gain\_Ratio}(D, A) = \frac{g(D, A)}{\text{IV}(A)} Gain_Ratio(D,A)=IV(A)g(D,A)

  • Gain_Ratio ( D , A ) \text{Gain\_Ratio}(D, A) Gain_Ratio(D,A):特征 A A A的信息增益率
  • g ( D , A ) g(D, A) g(D,A):特征 A A A的信息增益
  • IV ( A ) \text{IV}(A) IV(A):特征 A A A的内在信息(分裂信息)
    IV ( A ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ log ⁡ 2 ∣ D i ∣ ∣ D ∣ \text{IV}(A) = -\sum_{i=1}^{n}\frac{|D_i|}{|D|}\log_2\frac{|D_i|}{|D|} IV(A)=i=1nDDilog2DDi
    其中, n n n为特征 A A A的取值个数, ∣ D i ∣ |D_i| Di为特征 A A A取第 i i i个值的样本数。

3.2 信息增益率的意义

信息增益率通过引入一个称为内在信息(Intrinsic Value,IV)的指标对信息增益进行归一化处理。其目的是解决ID3算法中偏向于选择取值较多特征的问题。

内在信息 IV ( A ) \text{IV}(A) IV(A) 反映了特征 A A A 取值分布的均匀程度。分布越均匀, IV ( A ) \text{IV}(A) IV(A) 的值就越大。通过将信息增益除以 IV ( A ) \text{IV}(A) IV(A),信息增益率有效降低了那些取值数量多、但划分意义不大的特征的优先级。

可以将其理解为:信息增益是一个“原始得分”,而信息增益率是根据题目难度调整后的“加权得分”。对于取值较少但具有明显分类效果的特征,它会得到更高的认可,从而避免模型错误地偏好那些取值繁多但实际判别能力一般的特征。

3.3 C4.5决策树:信息增益率修正ID3的过拟合缺陷

含ID特征的数据集构造
在原贷款数据中加入ID列(取值1~15,唯一标识,与贷款结果无关),数据集如下:

ID年龄有工作有房子信贷情况类别
1青年一般拒绝
2青年拒绝
3青年同意
4青年一般同意
5青年一般拒绝
6中年一般拒绝
7中年拒绝
8中年同意
9中年非常好同意
10中年非常好同意
11老年非常好同意
12老年同意
13老年同意
14老年非常好同意
15老年一般拒绝

步骤1:计算数据集总熵 H ( D ) H(D) H(D)
总样本15条,同意=9拒绝=6
H ( D ) = − 9 15 log ⁡ 2 9 15 − 6 15 log ⁡ 2 6 15 ≈ 0.918 H(D) = -\frac{9}{15}\log_2\frac{9}{15} - \frac{6}{15}\log_2\frac{6}{15} \approx 0.918 H(D)=159log2159156log21560.918

步骤2:计算“ID”特征的信息增益率

  • 信息增益 g ( D , ID ) g(D, \text{ID}) g(D,ID)
    ID有15个唯一值,每个子集仅1条样本(纯类别,熵为0):
    H ( D ∣ ID ) = ∑ i = 1 15 1 15 × 0 = 0 ⇒ g ( D , ID ) = 0.918 − 0 = 0.918 H(D|\text{ID}) = \sum_{i=1}^{15}\frac{1}{15} \times 0 = 0 \quad \Rightarrow \quad g(D, \text{ID}) = 0.918 - 0 = 0.918 H(DID)=i=115151×0=0g(D,ID)=0.9180=0.918

  • 内在信息 IV(ID) \text{IV(ID)} IV(ID)
    IV(ID) = − ∑ i = 1 15 1 15 log ⁡ 2 1 15 ≈ 3.907 \text{IV(ID)} = -\sum_{i=1}^{15}\frac{1}{15}\log_2\frac{1}{15} \approx 3.907 IV(ID)=i=115151log21513.907

  • 信息增益率
    Gain_Ratio ( D , ID ) = 0.918 3.907 ≈ 0.235 \text{Gain\_Ratio}(D, \text{ID}) = \frac{0.918}{3.907} \approx 0.235 Gain_Ratio(D,ID)=3.9070.9180.235

步骤3:计算“有房子”特征的信息增益率

  • 信息增益 g ( D , 有房子 ) g(D, \text{有房子}) g(D,有房子)
    有房=7条(同意6,拒绝1),无房=8条(同意2,拒绝6):
    H ( D ∣ 有房子 ) = 7 15 ( − 6 7 log ⁡ 2 6 7 − 1 7 log ⁡ 2 1 7 ) + 8 15 ( − 2 8 log ⁡ 2 2 8 − 6 8 log ⁡ 2 6 8 ) ≈ 0.497 H(D|\text{有房子}) = \frac{7}{15}\left(-\frac{6}{7}\log_2\frac{6}{7}-\frac{1}{7}\log_2\frac{1}{7}\right) + \frac{8}{15}\left(-\frac{2}{8}\log_2\frac{2}{8}-\frac{6}{8}\log_2\frac{6}{8}\right) \approx 0.497 H(D有房子)=157(76log27671log271)+158(82log28286log286)0.497

    g ( D , 有房子 ) = 0.918 − 0.497 = 0.421 g(D, \text{有房子}) = 0.918 - 0.497 = 0.421 g(D,有房子)=0.9180.497=0.421

  • 内在信息 IV(有房子) \text{IV(有房子)} IV(有房子)

    IV(有房子) = − 7 15 log ⁡ 2 7 15 − 8 15 log ⁡ 2 8 15 ≈ 0.998 \text{IV(有房子)} = -\frac{7}{15}\log_2\frac{7}{15} - \frac{8}{15}\log_2\frac{8}{15} \approx 0.998 IV(有房子)=157log2157158log21580.998

  • 信息增益率
    Gain_Ratio ( D , 有房子 ) = 0.421 0.998 ≈ 0.422 \text{Gain\_Ratio}(D, \text{有房子}) = \frac{0.421}{0.998} \approx 0.422 Gain_Ratio(D,有房子)=0.9980.4210.422

特征选择对比

特征信息增益 ( g )内在信息 IV \text{IV} IV信息增益率
ID0.9183.9070.235
有房子0.4210.9980.422

步骤4:递归构建子树

  • 分裂子节点:以信息增益率最高的特征( “有房子” )为根节点分裂后,得到两个子节点:左子节点:无房样本(8 条,同意 2,拒绝 6)右子节点:有房样本(7 条,同意 6,拒绝 1)递归应用
  • C4.5 逻辑:对每个子节点数据集,重复步骤 1-3:计算子节点数据集的总熵 H ( D i ) H(D_i) H(Di);计算各特征(年龄、有工作、信贷情况、ID)的信息增益率;选择信息增益率最大的特征分裂(若存在)。
  • 终止条件(满足任一条件则停止分裂):子节点所有样本属于同一类别(如右子节点 “有房” 样本中 6 同意 1 拒绝,若继续分裂可能得到纯类别节点);信息增益率小于预设阈值(避免无意义分裂);特征已全部使用或样本数少于最小阈值。
    ID3决策树 I D 3 决策树 ID3决策树 ID3决策树

C4.5决策树
C 4.5 决策树 C4.5决策树 C4.5决策树

绘图代码:

# 导入必要的库
import pandas as pd  # 用于数据处理和构建 DataFrame
from collections import Counter  # 用于统计样本数量
from math import log2  # 用于计算对数
from graphviz import Digraph  # 用于可视化决策树
from sklearn.preprocessing import LabelEncoder  # 用于将字符串特征编码为数字# =============================================
# 1. 数据准备(含 ID)
# =============================================
# 构建训练数据集,包含 5 个特征和 1 个目标变量
data = {# 每个样本的唯一编号,容易造成过拟合"ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],# 年龄:青年、中年、老年"年龄": ["青年", "青年", "青年", "青年", "青年", "中年", "中年", "中年", "中年", "中年","老年", "老年", "老年", "老年", "老年"],# 是否有工作:是、否"有工作": ["否", "否", "是", "是", "否", "否", "否", "是", "否", "否","否", "否", "是", "是", "否"],# 是否有房子:是、否"有房子": ["否", "否", "否", "是", "否", "否", "否", "是", "是", "是","是", "是", "否", "否", "否"],# 信贷情况:一般、好、非常好"信贷情况": ["一般", "好", "好", "一般", "一般", "一般", "好", "好", "非常好", "非常好","非常好", "好", "好", "非常好", "一般"],# 类别:是否贷款成功(同意/拒绝)"类别": ["拒绝", "拒绝", "同意", "同意", "拒绝", "拒绝", "拒绝", "同意", "同意", "同意","同意", "同意", "同意", "同意", "拒绝"]
}# 将数据转换为 pandas 的 DataFrame 格式
df = pd.DataFrame(data)# 提取特征列名列表(所有列除了最后一列)
features = list(df.columns[:-1])  # 特征:ID、年龄、有工作、有房子、信贷情况# 目标变量列名(最后一列)
target = df.columns[-1]  # 类别列名# =============================================
# 2. 信息熵与信息增益计算
# =============================================def entropy(y):"""计算信息熵 H(Y)y: 当前样本的目标变量值(如:['拒绝', '同意', ...])返回:当前样本的信息熵"""counts = Counter(y)  # 统计每个类别的数量total = len(y)  # 总样本数if total == 0:return 0  # 防止除以 0# 计算 -Σ(p * log2(p))return -sum((count / total) * log2(count / total) for count in counts.values())def conditional_entropy(X_col, y):"""计算条件熵 H(Y|X)X_col: 某个特征列的值(如:['青年', '中年', ...])y: 对应的目标变量值返回:给定该特征下目标变量的条件熵"""values = set(X_col)  # 获取该特征的所有唯一值total = len(y)  # 总样本数ent = 0for v in values:mask = (X_col == v)  # 找到该特征值对应的样本y_sub = y[mask]  # 取出这些样本的目标变量ent += len(y_sub) / total * entropy(y_sub)  # 加权求和return entdef info_gain(dataset, feature):"""计算某个特征的信息增益 IG(Y, X)dataset: 整个数据集feature: 当前特征名称(如:"年龄")返回:该特征的信息增益"""X_col = dataset[feature]  # 取出该特征列y = dataset[target]  # 取出目标变量列return entropy(y) - conditional_entropy(X_col, y)  # 信息增益 = 总熵 - 条件熵def split_info(X_col):"""计算分裂信息(Split Information),用于 C4.5 增益率计算X_col: 某个特征列的值返回:该特征的分裂信息"""counts = Counter(X_col)  # 统计每个特征值出现的次数total = len(X_col)  # 总样本数# 计算 -Σ(|D_v|/|D| * log2(|D_v|/|D|))return -sum((count / total) * log2(count / total) for count in counts.values())def gain_ratio(dataset, feature):"""计算增益率 GainRatio(Y, X)dataset: 整个数据集feature: 当前特征名称返回:该特征的增益率"""ig = info_gain(dataset, feature)  # 先计算信息增益si = split_info(dataset[feature])  # 再计算分裂信息return ig / si if si != 0 else float('inf')  # 增益率 = IG / SI# =============================================
# 3. 手动实现 ID3 和 C4.5 决策树
# =============================================def id3_tree(dataset, features):"""使用 ID3 算法递归构建决策树dataset: 当前数据集features: 当前可用特征列表返回:当前节点的决策树结构字典"""target_values = dataset[target].unique()  # 获取当前目标变量的唯一值# 终止条件1:当前样本全部属于同一类if len(target_values) == 1:return {'label': target_values[0]}  # 返回叶子节点标签# 终止条件2:没有可用特征if not features:most_common = dataset[target].mode().iloc[0]  # 返回多数类作为预测结果return {'label': most_common}# 选择信息增益最大的特征gains = {f: info_gain(dataset, f) for f in features}  # 计算每个特征的信息增益best_feature = max(gains, key=gains.get)  # 选出最大增益的特征# 构建当前节点信息class_counts = dict(Counter(dataset[target]))  # 统计目标变量分布node_info = {'feature': best_feature,'gain': gains[best_feature],  # 信息增益'class_dist': class_counts,  # 类别分布'n_samples': len(dataset),  # 样本数量'children': {}  # 子节点}# 递归划分每个特征值remaining_features = [f for f in features if f != best_feature]  # 剩余可用特征for raw_value in dataset[best_feature].unique():  # 遍历当前特征的每个唯一值sub_data = dataset[dataset[best_feature] == raw_value]  # 划分子数据集node_info['children'][raw_value] = id3_tree(sub_data, remaining_features)  # 递归生成子树return node_infodef c45_tree(dataset, features):"""使用 C4.5 算法递归构建决策树(基于增益率)dataset: 当前数据集features: 当前可用特征列表返回:当前节点的决策树结构字典"""target_values = dataset[target].unique()# 终止条件1:当前样本全部属于同一类if len(target_values) == 1:return {'label': target_values[0]}# 终止条件2:没有可用特征if not features:most_common = dataset[target].mode().iloc[0]return {'label': most_common}# 选择增益率最大的特征gains = {f: gain_ratio(dataset, f) for f in features}best_feature = max(gains, key=gains.get)# 构建当前节点信息class_counts = dict(Counter(dataset[target]))node_info = {'feature': best_feature,'gain_ratio': round(gains[best_feature], 3),'class_dist': class_counts,'n_samples': len(dataset),'children': {}}# 递归划分每个特征值remaining_features = [f for f in features if f != best_feature]for raw_value in dataset[best_feature].unique():sub_data = dataset[dataset[best_feature] == raw_value]node_info['children'][raw_value] = c45_tree(sub_data, remaining_features)return node_info# =============================================
# 4. 构建特征编码器(用于生成 CART 风格的分割条件)
# =============================================
# 保存每个特征的编码器,便于后续可视化时使用
feature_encoders = {}for feat in features:le = LabelEncoder()  # 创建编码器le.fit(df[feat])  # 拟合该特征的编码规则feature_encoders[feat] = le  # 保存编码器# =============================================
# 5. 决策树可视化(CART 风格,支持中文)
# =============================================
def visualize_tree_cart_style(tree, feature_encoders, graph=None, parent_id=None, edge_label=""):"""使用 Graphviz 可视化决策树(CART 风格)tree: 当前树节点结构feature_encoders: 编码器字典graph: 当前图对象parent_id: 父节点 IDedge_label: 边上的标签返回:Graphviz 图对象"""from uuid import uuid4  # 用于生成唯一节点 IDif graph is None:# 初始化一个空图,设置字体支持中文graph = Digraph('DecisionTree',format='png',node_attr={'fontname': 'SimHei'},  # 支持中文节点edge_attr={'fontname': 'SimHei'},  # 支持中文边标签graph_attr={'fontname': 'SimHei', 'rankdir': 'TB'}  # 树方向从上往下)node_id = str(uuid4())  # 生成当前节点的唯一标识符if 'label' in tree:# 如果是叶子节点,显示类别标签label = f"类别: {tree['label']}"shape = "box"  # 叶子节点用矩形else:# 如果是内部节点,显示特征、增益等信息feat_name = tree['feature']metric = round(tree.get('gain', tree.get('gain_ratio', 0)), 3)  # 获取指标值metric_label = "信息增益" if 'gain' in tree else "信息增益率"n = tree['n_samples']  # 样本数dist = ", ".join([f"{k}={v}" for k, v in tree['class_dist'].items()])  # 分布# 构造节点标签label = (f"{feat_name}\\n"f"{metric_label}={metric}\\n"f"样本数={n}\\n"f"分布:{dist}")shape = "ellipse"  # 内部节点用椭圆graph.node(node_id, label=label, shape=shape)  # 添加当前节点到图中if parent_id:# 如果有父节点,则画边graph.edge(parent_id, node_id, label=edge_label)if 'children' in tree:# 如果有子节点,递归绘制feat_name = tree['feature']encoder = feature_encoders[feat_name]  # 获取该特征的编码器unique_values = df[feat_name].unique()  # 获取原始特征值sorted_values = sorted(encoder.transform(unique_values))  # 编码后的排序值for raw_value, child in tree['children'].items():encoded_value = encoder.transform([raw_value])[0]  # 把原始值转为编码值display_label = get_edge_label(feat_name, encoded_value, sorted_values)  # 生成边标签# 递归绘制子树visualize_tree_cart_style(child, feature_encoders, graph, node_id, display_label)return graphdef get_edge_label(feat_name, encoded_value, sorted_values):"""生成边标签,例如:年龄 <= 0.5、年龄 > 0.5feat_name: 特征名encoded_value: 编码后的特征值sorted_values: 排序后的编码值列表返回:边标签字符串"""index = sorted_values.index(encoded_value)if index == 0:threshold = (sorted_values[index] + sorted_values[index + 1]) / 2return f"{feat_name} <= {threshold:.1f}"elif index == len(sorted_values) - 1:threshold = (sorted_values[index - 1] + sorted_values[index]) / 2return f"{feat_name} > {threshold:.1f}"else:low = (sorted_values[index - 1] + sorted_values[index]) / 2high = (sorted_values[index] + sorted_values[index + 1]) / 2return f"{low:.1f} < {feat_name} <= {high:.1f}"# =============================================
# 6. 主程序:构建并可视化两种树
# =============================================# 构建模型
tree_id3 = id3_tree(df, features)  # 构建 ID3 决策树
tree_c45 = c45_tree(df, features)  # 构建 C4.5 决策树# 设置可视化图例格式
dot_id3 = visualize_tree_cart_style(tree_id3, feature_encoders)  # 可视化 ID3 树
dot_c45 = visualize_tree_cart_style(tree_c45, feature_encoders)  # 可视化 C4.5 树# 保存为 PNG 文件,并自动打开查看
dot_id3.render("id3_decision_tree_with_id", view=True)
dot_c45.render("c45_decision_tree_with_id", view=True)

3.4 结论:C4.5如何解决过拟合

  • ID3的缺陷:ID特征因信息增益最大(0.918)被优先选择,导致树分裂为15个叶子节点,完全记忆训练数据(过拟合)。
  • C4.5的修正:ID的信息增益虽高,但内在信息 V ( I D ) V(ID) V(ID)=3.907极大,导致信息增益率(0.235)低于“有房子”(0.422),因此C4.5选择 “有房子” 作为根节点,避免被ID特征误导。
  • 本质原因:信息增益率通过IV(A)惩罚了取值多但分类意义小的特征(如ID),使模型更关注真正有判别力的特征,降低过拟合风险。

3.5 C4.5的局限性

  • 对取值少的特征仍有偏向:若某特征取值少且IV(A)小,可能被过度优先选择(如两取值特征,IV=1时信息增益率=信息增益)。
  • 计算复杂度高:需额外计算IV(A),比ID3耗时更多。

通过信息增益率的归一化,C4.5在保留ID3可解释性的同时,有效缓解了“取值多特征偏好”问题,是决策树从理论到工程应用的重要改进。

四、CART决策树:基于基尼指数的二叉树模型

CART 决策树是一种基于基尼指数(或平方误差)的二叉树模型,广泛应用于分类和回归任务中。它通过最小化基尼指数来选择最优划分特征,确保每一步划分都能带来最大纯度提升。相较于 ID3 或 C4.5 等多叉树模型,CART 结构简单、计算效率高、鲁棒性强,是工业界常用的一种决策树实现方式。

4.1 基尼指数的公式与符号解析

基尼指数相关公式

  • 数据集 D D D的基尼值:
    Gini ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 \text{Gini}(D) = 1 - \sum_{k=1}^{K}\left(\frac{|C_k|}{|D|}\right)^2 Gini(D)=1k=1K(DCk)2

  • 特征 A A A的基尼指数:
    Gini_index ( D , A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ Gini ( D i ) \text{Gini\_index}(D, A) = \sum_{i=1}^{n}\frac{|D_i|}{|D|}\text{Gini}(D_i) Gini_index(D,A)=i=1nDDiGini(Di)

  • Gini ( D ) \text{Gini}(D) Gini(D):衡量数据集 D D D的不纯度,值越小纯度越高

  • Gini_index ( D , A ) \text{Gini\_index}(D, A) Gini_index(D,A):特征 A A A的基尼指数,用于选择最优分裂特征

  • K K K:类别数, ∣ C k ∣ |C_k| Ck:第 k k k类的样本数, ∣ D ∣ |D| D:总样本数

  • n n n:特征 A A A的取值个数, ∣ D i ∣ |D_i| Di:特征 A A A取第 i i i个值的样本数

4.2 基尼指数的意义

  • 基尼指数衡量的是从数据集中随机抽取两个样本时,它们类别不一致的概率。这个指标越小,说明数据集的纯度越高;反之,则表示数据越混杂。

  • 在决策树构建过程中,当我们使用某个特征对数据集进行划分后,会分别计算每个子集的基尼指数,并通过加权求和得到整体的基尼指数(即 Gini_index ( D , A ) \text{Gini\_index}(D, A) Gini_index(D,A) )。该值越小,意味着划分后的各子集纯度越高,分类效果越好。

  • 可以形象地理解为:假设你有一袋球,颜色各异。如果你随手抓出两个球,颜色不一样的可能性越大,说明这袋球越“混乱”(基尼值高)。我们的目标是找到一种分法(特征),让每次分出来的每一小袋都尽可能接近“同色”,也就是让每袋的基尼指数尽可能小。

4.3 CART决策树案例演示:贷款申请的基尼指数计算

沿用前例,计算特征"有房子"的基尼指数(CART为二叉树,需将多取值特征转化为二叉分裂)。

步骤1:计算数据集D的基尼值
Gini ( D ) = 1 − ( 5 15 ) 2 − ( 10 15 ) 2 = 0.4444 \text{Gini}(D) = 1 - \left(\frac{5}{15}\right)^2 - \left(\frac{10}{15}\right)^2 = 0.4444 Gini(D)=1(155)2(1510)2=0.4444

步骤2:计算"有房子"特征的基尼指数

  • 有房子:7条样本,6同意,1拒绝
    Gini ( 有房子 ) = 1 − ( 6 7 ) 2 − ( 1 7 ) 2 = 0.2449 \text{Gini}(有房子) = 1 - \left(\frac{6}{7}\right)^2 - \left(\frac{1}{7}\right)^2 = 0.2449 Gini(有房子)=1(76)2(71)2=0.2449
  • 无房子:8条样本,2同意,6拒绝
    Gini ( 无房子 ) = 1 − ( 2 8 ) 2 − ( 6 8 ) 2 = 0.375 \text{Gini}(无房子) = 1 - \left(\frac{2}{8}\right)^2 - \left(\frac{6}{8}\right)^2 = 0.375 Gini(无房子)=1(82)2(86)2=0.375
  • 基尼指数:
    Gini_index ( D , 有房子 ) = 7 15 × 0.2449 + 8 15 × 0.375 = 0.3176 \text{Gini\_index}(D, 有房子) = \frac{7}{15} \times 0.2449 + \frac{8}{15} \times 0.375 = 0.3176 Gini_index(D,有房子)=157×0.2449+158×0.375=0.3176

步骤3:计算其他特征的基尼指数
以"年龄"特征为例,需转化为二叉分裂(如"青年"vs"非青年"):

  • 青年:5样本,4拒绝,1同意
    Gini ( 青年 ) = 1 − ( 4 5 ) 2 − ( 1 5 ) 2 = 0.32 \text{Gini}(青年) = 1 - \left(\frac{4}{5}\right)^2 - \left(\frac{1}{5}\right)^2 = 0.32 Gini(青年)=1(54)2(51)2=0.32
  • 非青年:10样本,6拒绝,4同意
    Gini ( 非青年 ) = 1 − ( 6 10 ) 2 − ( 4 10 ) 2 = 0.48 \text{Gini}(非青年) = 1 - \left(\frac{6}{10}\right)^2 - \left(\frac{4}{10}\right)^2 = 0.48 Gini(非青年)=1(106)2(104)2=0.48
  • 基尼指数:
    Gini_index ( D , 年龄 ) = 5 15 × 0.32 + 10 15 × 0.48 = 0.4267 \text{Gini\_index}(D, 年龄) = \frac{5}{15} \times 0.32 + \frac{10}{15} \times 0.48 = 0.4267 Gini_index(D,年龄)=155×0.32+1510×0.48=0.4267

步骤4:选择基尼指数最小的特征
"有房子"的基尼指数0.3176最小,作为分裂特征,生成二叉树:

  • 左子树:有房子,基尼值0.2449(更纯)
  • 右子树:无房子,基尼值0.375(较不纯)

步骤5:递归构建子树
在完成当前节点的最佳划分特征选择后(如“有房子”),CART 决策树会基于该特征的不同取值,将数据集划分为两个子集,并对每个子集递归地重复以下过程:

  • 判断是否满足终止条件:
    • 若当前子集中所有样本属于同一类别(基尼值为 0),则将其设为叶子节点,直接返回类别结果。
    • 若达到预设的最大深度(如 max_depth=3)或样本数过少(如小于 min_samples_split),也停止继续分裂。
  • 否则,继续进行最优划分特征的选择:
    • 对当前子集重新计算各个特征的基尼指数。
    • 选择基尼指数最小的特征作为新的划分依据。
    • 再次划分数据集并生成新的左右子节点:
    • 每个子节点代表一个划分后的子集。
    • 对每个子节点递归执行上述步骤,直到满足终止条件为止。
  • 最终形成一棵完整的二叉决策树:

在这里插入图片描述

绘图代码:

# 导入必要的库
import pandas as pd  # 用于构建 DataFrame 数据结构
from sklearn.tree import DecisionTreeClassifier, plot_tree  # 构建和可视化决策树
from sklearn.preprocessing import LabelEncoder  # 对字符串特征进行编码
import matplotlib.pyplot as plt  # 可视化工具# 设置中文字体显示(解决中文乱码问题)
plt.rcParams['font.sans-serif'] = ['SimHei']  # 将默认字体设置为"SimHei(黑体)",确保中文标签正常显示
plt.rcParams['axes.unicode_minus'] = False  # 修复负号显示为方块的问题(确保特殊符号渲染正常)# =============================================
# 1. 数据准备(含 ID)
# =============================================
# 构建训练数据集,包含 5 个特征和 1 个目标变量
data = {# 每个样本的唯一编号,容易造成过拟合"ID": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],# 年龄:青年、中年、老年"年龄": ["青年", "青年", "青年", "青年", "青年", "中年", "中年", "中年", "中年", "中年","老年", "老年", "老年", "老年", "老年"],# 是否有工作:是、否"有工作": ["否", "否", "是", "是", "否", "否", "否", "是", "否", "否","否", "否", "是", "是", "否"],# 是否有房子:是、否"有房子": ["否", "否", "否", "是", "否", "否", "否", "是", "是", "是","是", "是", "否", "否", "否"],# 信贷情况:一般、好、非常好"信贷情况": ["一般", "好", "好", "一般", "一般", "一般", "好", "好", "非常好", "非常好","非常好", "好", "好", "非常好", "一般"],# 类别:是否贷款成功(同意/拒绝)"类别": ["拒绝", "拒绝", "同意", "同意", "拒绝", "拒绝", "拒绝", "同意", "同意", "同意","同意", "同意", "同意", "同意", "拒绝"]
}# 将数据转换为 pandas 的 DataFrame 格式
df = pd.DataFrame(data)# 提取特征和目标变量
X = df.drop("类别", axis=1)  # 特征:年龄、有工作、有房子、信贷情况
y = df["类别"]  # 目标变量:类别(是否贷款成功)# =============================================
# 2. 特征编码(将字符串特征转为数字)
# =============================================
# 创建 LabelEncoder 并对每个特征列进行编码
encoders = {}  # 存储每个特征的编码器
for col in X.columns:le = LabelEncoder()X[col] = le.fit_transform(X[col])  # 转换为数字encoders[col] = le  # 保存编码器供后续反编码用# 对目标变量进行编码:'拒绝' -> 0, '同意' -> 1
y = LabelEncoder().fit_transform(y)# =============================================
# 3. 构建 CART 决策树模型
# =============================================
# 创建 CART 分类器,使用默认的 gini 指数作为划分标准
clf = DecisionTreeClassifier(criterion='gini', max_depth=3)# 训练模型
clf.fit(X, y)# =============================================
# 4. 使用 matplotlib 可视化决策树结构
# =============================================
# 设置绘图风格
plt.figure(figsize=(10, 6))  # 设置画布大小# 绘制决策树结构图
plot_tree(clf,  # 决策树模型feature_names=X.columns,  # 特征名称class_names=["拒绝", "同意"],  # 类别名称(中文支持)filled=True,  # 使用颜色填充节点fontsize=10,  # 字体大小proportion=False,  # 显示样本数量而非比例rounded=True  # 圆角矩形显示节点
)# 设置图像标题并调整布局
plt.title("CART 决策树结构(基于基尼指数)")
plt.tight_layout()  # 自动调整子图参数,防止重叠# 显示图像
plt.show()

4.4 Scikit-learn 中 CART 决策树的 API

  1. 分类树:DecisionTreeClassifier
    用于离散标签分类(如是否贷款),基于基尼指数或信息熵生成二叉树。
  • 核心参数
    criterion 控制分裂标准(gini为默认基尼指数,entropy为信息熵);
    max_depth 限制树深度(避免过拟合,如设为3-10);
    min_samples_split 规定节点分裂的最小样本数(默认2);
    min_samples_leaf 设定叶子节点最小样本数(默认1)。
  • 关键方法
    fit(X, y) 训练模型(X为特征矩阵,y为离散标签);
    predict(X) 预测类别;
    predict_proba(X) 输出类别概率;
    score(X, y) 计算准确率。
  1. 回归树:DecisionTreeRegressor
    用于连续值预测(如房价),基于均方误差或平均绝对误差分裂。
  • 核心参数
    criterion 可选mse(均方误差,默认)或mae(平均绝对误差);
    其他参数(如max_depthmin_samples_split)与分类树逻辑一致。
  • 关键方法
    fit(X, y) 训练模型(y为连续值);
    predict(X) 输出回归值;
    score(X, y) 计算R²系数(越接近1拟合越好)。
  1. 核心共通点
  • 分裂策略splitter='best'(全局最优)或'random'(随机分裂防过拟合);
  • 特征重要性:训练后通过feature_importances_属性获取;
  • 可视化:用sklearn.tree.export_graphvizplot_tree展示树结构。
  1. 分类与回归差异
    分类树用基尼/熵衡量纯度,输出类别概率;回归树用MSE/MAE衡量误差,输出连续值预测,评估指标为R²而非准确率。

4.5 小结

CART决策树的核心优势在于:

  1. 始终生成二叉树,结构更简单
  2. 基尼指数计算效率高于熵运算
  3. 同时支持分类和回归任务(回归时使用平方误差最小化)
  4. 对噪声数据和缺失值的处理更鲁棒

五、三大决策树模型对比总结

模型核心指标节点分裂方式特征处理树结构优缺点
ID3信息增益多叉分裂仅支持离散特征多叉树优点:计算简单;缺点:偏向取值多的特征,易过拟合
C4.5信息增益率多叉分裂支持离散/连续特征多叉树优点:修正ID3的偏向问题;缺点:计算复杂,对取值少的特征仍有偏向
CART基尼指数二叉分裂支持离散/连续特征二叉树优点:计算高效,泛化能力强,支持分类/回归;缺点:对高维数据表现不佳

决策树的发展历程体现了机器学习算法的优化思路——从简单有效到复杂精准,再到平衡效率与性能。实际应用中,可根据数据特点和任务需求选择合适的决策树模型,或采用集成学习(如随机森林)进一步提升模型效果。

六、其他重要决策树算法

6.1 CHAID:基于统计检验的决策树

核心思想:使用卡方检验(分类目标)或F检验(连续目标)评估特征与目标的相关性

公式示例
χ 2 = ∑ ( O i − E i ) 2 E i \chi^2 = \sum \frac{(O_i - E_i)^2}{E_i} χ2=Ei(OiEi)2
其中 O i O_i Oi为观察频数, E i E_i Ei为期望频数

优势

  • 支持多叉分裂,直观展示特征交互作用
  • 自动处理缺失值
  • 可生成非二叉树结构

应用场景:市场细分、客户画像分析

6.2 QUEST:快速无偏决策树

核心思想:通过统计检验选择分裂特征,避免CART对连续特征的偏向

算法流程

  1. 使用ANOVA F-test选择最佳分裂特征
  2. 应用二次判别分析确定最优分裂点
  3. 递归构建二叉树

优势

  • 无偏特征选择
  • 计算效率高
  • 处理高维数据能力强

应用场景:基因表达数据分析、高维生物信息学

6.3 条件推断树:统计驱动的决策树

核心思想:基于置换检验(Permutation Test)选择分裂变量和分裂点

算法特点

  1. 计算特征与目标的统计相关性
  2. 评估分裂显著性的p值
  3. 仅当p值小于阈值时才进行分裂

公式核心
p -value = P ( ∣ T ∣ ≥ ∣ t ∣ ∣ H 0 ) p\text{-value} = P(|T| \geq |t| | H_0) p-value=P(Tt∣∣H0)
其中 T T T为检验统计量, t t t为观察值, H 0 H_0 H0为无关联假设

优势

  • 有效控制变量选择偏差
  • 适用于混合类型特征(离散+连续)
  • 提供统计显著性评估

应用场景:临床试验数据分析、社会科学研究

6.4 斜决策树(Oblique Decision Tree)

核心思想:允许使用特征的线性组合进行分裂(如 0.5 × 年龄 + 0.8 × 收入 > 50 0.5 \times \text{年龄} + 0.8 \times \text{收入} > 50 0.5×年龄+0.8×收入>50

数学表示
∑ j = 1 p w j x j > θ \sum_{j=1}^{p} w_j x_j > \theta j=1pwjxj>θ
其中 w j w_j wj为特征权重, θ \theta θ为阈值

优势

  • 构建更紧凑的树结构
  • 提升模型泛化能力
  • 更好处理相关特征

挑战

  • 分裂点优化复杂度高
  • 需结合线性规划求解
  • 训练时间显著增加

应用场景:图像识别、金融风险评估

6.5 流数据决策树(Hoeffding Tree)

核心思想:基于Hoeffding不等式,在有限内存下处理无限数据流

核心公式
n > R 2 ln ⁡ ( 1 / δ ) 2 ϵ 2 n > \frac{R^2 \ln(1/\delta)}{2\epsilon^2} n>2ϵ2R2ln(1/δ)
其中 n n n为所需样本数, R R R为随机变量范围, δ \delta δ为置信度, ϵ \epsilon ϵ为误差界

优势

  • 单次遍历数据
  • 实时更新模型
  • 有限内存需求

应用场景:实时交易监控、物联网设备数据分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87321.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87321.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python基础-网络的TCP、UDP协议操作

1.tcp基本语法 # ### TCP协议 客户端 import socket # 1.创建一个socket对象 sk socket.socket() # 2.与服务端建立连接 sk.connect( ("127.0.0.1" , 9000) ) # 3.收发数据的逻辑 """发送的数据类型是二进制字节流""" ""&q…

基于spark的航班价格分析预测及可视化

基于spark的航班价格分析预测及可视化 项目概况 [&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;&#x1f447;] 点这里,查看所有项目 [&#x1f446;&#x1f446;&#x1f446;&#x1f446;&#x1f446;&#x1f446;&…

每日算法刷题Day41 6.28:leetcode前缀和2道题,用时1h20min(要加快)

5. 523.连续的子数组和(中等,学习) 523. 连续的子数组和 - 力扣&#xff08;LeetCode&#xff09; 思想 1.给你一个整数数组 nums 和一个整数 k &#xff0c;如果 nums 有一个 好的子数组 返回 true &#xff0c;否则返回 false&#xff1a; 一个 好的子数组 是&#xff1a;…

拉取vue-element-admin

这个错误表明 npm 在尝试通过 SSH 克隆 GitHub 仓库时遇到了权限问题&#xff0c;根本原因是系统无法正确处理中文用户名路径下的 SSH 配置。以下是详细的解决方案&#xff1a; 解决方案 1&#xff1a;使用 HTTPS 代替 SSH&#xff08;推荐&#xff09; 修改 Git 全局配置&…

c语言的数组注意事项

在C语言中&#xff0c;int()[5]和int是两种完全不同的指针类型&#xff0c;理解它们的区别对于正确处理数组和多维数组至关重要。下面详细解释&#xff1a; 1&#xff1a;int*&#xff08;指向整型的指针&#xff09; 含义&#xff1a;指向单个int类型数据的指针典型用法&…

在 NestJS 中优雅使用 TypeORM 进行事务管理

事务管理是数据库操作中至关重要的部分&#xff0c;它能确保一系列操作要么全部成功&#xff0c;要么全部失败。本文将详细介绍在 NestJS 框架中使用 TypeORM 进行事务管理的多种方法。 为什么需要事务管理&#xff1f; 想象一下银行转账场景&#xff1a;从一个账户扣款后&am…

给任意apk内容添加水印

1 有源码给app添加水印 使用java可以适配更多的apk&#xff0c;如果使用koltin一些老的apk就会有适配问题 通过registerActivityLifecycleCallbacks拿到activity对象设置水印 在application里面registerActivityLifecycleCallbacks就行 static class MyActivityLifecycleCallb…

扩展的Fortran在高性能计算(HPC)中助力有限元分析(FEA)、流体力学(CFD)、结构力学、复合材料和增材制造仿真的详细指南【附完整示例代码实现】

Fortran 在高性能计算(HPC)中的仿真应用 本指南深入探讨 Fortran 语言如何在高性能计算(HPC)中助力有限元分析(FEA)、流体力学(CFD)、结构力学、复合材料和增材制造仿真。每部分详细介绍,分析 Fortran 的优势、应用场景和实现细节,并附带完整的 Fortran 模拟代码(含…

Java 大视界 -- Java 大数据机器学习模型在自然语言处理中的跨语言信息检索与知识融合(331)

Java 大视界 -- Java 大数据机器学习模型在自然语言处理中的跨语言信息检索与知识融合&#xff08;331&#xff09; 引言&#xff1a;正文&#xff1a;一、Java 驱动的多语言数据处理平台1.1 分布式多语言语料智能清洗系统1.2 多语言文本分布式存储与索引优化1.3 低资源语言数据…

[2025CVPR]SEEN-DA:基于语义熵引导的领域感知注意力机制

目录 引言 研究背景 方法介绍 核心思想 语义熵&#xff08;Semantic Entropy&#xff09; 语义熵引导的注意力机制 领域感知注意力模块 实验设计 数据集 实现细节 结果与分析 对比实验结果 消融实验 代码实现 结论 引言 领域自适应目标检测&#xff08;Domain …

你的RAG系统安全么?

生成式人工智能&#xff08;GenAI&#xff09;近年来发展迅速&#xff0c;大语言模型成为这一浪潮的核心力量。无论是商业还是开源模型&#xff0c;它们都具备强大的语言理解与生成能力&#xff0c;正广泛应用于内容创作、聊天机器人等场景&#xff0c;让企业更容易落地智能应用…

【2.3 漫画SpringSecurity - 守护应用安全的钢铁卫士】

🔐 漫画SpringSecurity - 守护应用安全的钢铁卫士 📚 目录 记忆口诀可视化图表形象比喻数字记忆实战案例记忆卡片总结诗句面试准备🎪 记忆口诀 🏗️ SpringSecurity核心 - “认证授权过滤链” 认证Authentication确身份,用户名密码验证真 授权Authorization控权限,…

ModbusRTU转Profinet网关在电子天平与PLC系统集成中的应用

ModbusRTU转Profinet网关在电子天平与PLC系统集成中的应用 工业自动化场景中&#xff0c;设备通信协议差异常成为系统集成的隐形壁垒。某精密制造企业近期遇到的奥豪斯电子天平与西门子PLC通讯难题&#xff0c;正是这一矛盾的典型缩影。奥豪斯天平采用ModbusRTU协议&#xff0…

js代码后续

这是一个非常棒的问题&#xff0c;也是每个学完一个系统课程的人都会问的问题。 答案是&#xff1a;不&#xff0c;你没有学完“所有”的 JavaScript 知识&#xff0c;但你已经出色地完成了成为一名合格 JavaScript 开发者的所有“必修课”。 让我用一个比喻来解释&#xff1…

百度文心大模型 4.5 系列全面开源 英特尔同步支持端侧部署

2025 年 6 月 30 日&#xff0c;百度如期兑现 2 月 14 日的预告&#xff0c;正式开源文心大模型 4.5&#xff08;ERNIE 4.5&#xff09;系列&#xff0c;涵盖 10 款不同参数规模的模型&#xff0c;包括 470 亿参数混合专家&#xff08;MoE&#xff09;模型、30 亿参数 MoE 模型…

Google AI Edge Function Calling: Android 端模型也能调用工具函数

大家好&#xff0c;我是拭心。 上篇文章我们了解了如何在 Android 手机上实现 RAG。这篇文章我们来聊聊端上大模型应用开发的核心概念&#xff1a;Function Calling&#xff08;函数调用能力&#xff0c;简写为 FC&#xff09;。 Function Calling 本质上是让大模型在回答过程…

模型调试实用技巧 (Pytorch Lightning)

【PL 基础】模型调试实用技巧 摘要1. 设置断点2. 快速运行所有模型代码一次3. 缩短 epoch 长度4. 运行健全性检查5. 打印 LightningModule 权重摘要6. 打印输入输出层尺寸 摘要 本文总结了6种实用的模型调试技巧&#xff1a;1&#xff09;通过设置断点逐行检查代码&#xff1b;…

计算机网络(四)网际层IP

目录 一、概念 ​编辑 二、网际层和数据链路层的关系​ 三、IP地址的基础认识 四、IP地址的分类 五、无分类地址CIDR 六、子网掩码 七、为什么要分离网络号和主机号 八、公有IP和私有IP ​编辑 九、IP地址与路由控制 十、IP分片和重组 十一、IPv6 十二、IP协议…

Java--多态--向上转型--动态绑定机制--断点调试--向下转型

目录 1. 向上转型 2. 向下转型 3. java的动态绑定机制&#xff1a; 4. Object类讲解 5. 断点调试 1. 向上转型 提前&#xff1a;俩个对象&#xff08;类&#xff09;存在继承关系 本质&#xff1a;父类的引用指向了子类的对象 语法&#xff1a;父类 类型 引用名 new…

Python爬虫实战:研究urllib 库相关技术

1. 引言 1.1 研究背景与意义 互联网每天产生海量数据,如何高效获取和利用这些数据成为重要研究方向。网页爬虫作为自动获取网络信息的核心技术,在市场调研、舆情分析、学术研究等领域具有广泛应用。Python 凭借其简洁语法和丰富库支持,成为爬虫开发的首选语言。 1.2 相关…