【决策树算法实战指南:从原理到Python实现】

一、决策树基础核心

  1. 定义与核心逻辑本质:属于有监督学习,通过学习训练样本建立分类规则,对新样本进行分类预测。核心流程:所有数据从根节点出发,经非叶子节点(中间节点)逐步分裂,最终落到叶子节点

二、三大核心分类算法(节点分裂标准)

1. ID3 算法

  • 核心衡量指标信息增益(基于熵值计算),选择信息增益最大的特征作为分裂属性。
  • 关键概念:
    • 熵值:表示随机变量的不确定性(内部混乱程度),熵值越小,节点越 "纯";
  • 熵值计算公式:

  • 为某类别在样本中的占比)
  • 计算步骤:
    1. 计算标签(分类结果)的类别熵;
    2. 按每个特征划分样本,计算该特征对应的属性熵;
    3. 信息增益 = 类别熵 - 属性熵,取信息增益最大的特征分裂。

2. C4.5 算法

  • 核心改进:解决 ID3 算法对稀疏向量(如编号类特征)的偏好问题。
  • 核心衡量指标信息增益率(信息增益与特征自身熵的比值),选择信息增益率最大的特征分裂。
  • 计算步骤:
  • 沿用 ID3 步骤计算类别熵、属性熵、信息增益;
  • 计算该特征的自身熵(描述特征自身取值的不确定性);
  • 信息增益率 = 信息增益 ÷ 特征自身熵。

3. CART 决策树

  • 核心衡量指标基尼系数(衡量样本集合的不确定性,与熵值趋势一致,计算更高效),选择基尼系数最小的特征及切分点分裂。
  • 关键概念:

示例:贷款申请数据集,通过计算各特征(年龄、有工作、有房等)的基尼系数,选择基尼系数最小的特征(如 "有工作")作为最优分裂属性。

三、决策树剪枝(防止过拟合)

1. 剪枝目的

避免模型过度贴合训练数据,提升对新样本的泛化能力。

2. 剪枝方式

  • 预剪枝 (训练中停止分裂):
    1. 限制树的最大深度;
    2. 限制叶子节点的个数或每个叶子节点的最小样本数;
    3. 设定基尼系数阈值,当分裂后基尼系数无明显降低时停止分裂。
  • 后剪枝(训练完剪去冗余分支):文档未展开具体步骤,仅明确为核心剪枝方式之一。

四、经典示例数据集

  1. 贷款预测数据集:特征包括房产、车辆、年收入,分类结果为 "可以贷款""不可贷款";
  2. 外出打球数据集:特征包括天气(晴天 / 阴天 / 雨天)、温度(热 / 温和 / 凉爽)、湿度(高 / 正常)、有风(是 / 否),分类结果为 "打球""不打球";
  3. 贷款申请详细数据集:特征包括年龄、有工作、有自己的房子、信贷情况,分类结果为 "同意贷款""拒绝贷款"。

三大算法对比表格:

|----------|------------|----------------------------------------------------------------------------------------------------------------------------|--------------------------|----------------------|
| 算法类型 | 核心衡量指标 | 核心公式 / 计算逻辑 | 关键特点 | 适用场景 |
| ID3 算法 | 信息增益(基于熵值) | 1.熵值:H(U)= 2. 信息增益 = 类别熵 - 属性熵 | 熵值越小节点越 "纯",优先选信息增益最大的特征 | 特征取值较少、无稀疏向量的场景 |
| C4.5 算法 | 信息增益率 | 1. 信息增益(同 ID3) 2. 信息增益率 = 信息增益 ÷ 特征自身熵 | 解决 ID3 对稀疏向量的偏好问题,更稳定 | 存在编号类稀疏特征、特征取值不均衡的场景 |
| CART 决策树 | 基尼系数 | 1. 基尼系数:(二分类化简为2p(1-p)) 2.分裂后基尼系数=子集占比*子集基尼系数之和 | 计算效率高于熵值,支持二分类 / 回归 | 追求高效计算、需要处理回归任务的场景 |

三大算法 Python 完整实现代码(可直接运行)

基于经典外出打球数据集实现 ID3、C4.5、CART 决策树,包含数据预处理、核心指标计算、树的构建与预测

  1. 环境依赖(提前安装)
bash 复制代码
pip install pandas numpy scikit-learn
  1. 完整实现代码
python 复制代码
import pandas as pd
import numpy as np
from math import log2

# ===================== 1. 构建经典外出打球数据集 =====================
def create_play_tennis_data():
    """创建外出打球数据集:特征为天气、温度、湿度、有风,标签为是否打球"""
    data = {
        '天气': ['晴天', '晴天', '阴天', '雨天', '雨天', '雨天', '阴天', '晴天', '晴天', '雨天', '晴天', '阴天', '阴天', '雨天'],
        '温度': ['热', '热', '热', '温和', '凉', '凉', '凉', '温和', '凉', '温和', '温和', '温和', '热', '温和'],
        '湿度': ['高', '高', '高', '高', '正常', '正常', '正常', '高', '正常', '正常', '正常', '高', '正常', '高'],
        '有风': ['否', '是', '否', '否', '否', '是', '是', '否', '否', '否', '是', '是', '否', '是'],
        '是否打球': ['否', '否', '是', '是', '是', '否', '是', '否', '是', '是', '是', '是', '是', '否']
    }
    df = pd.DataFrame(data)
    # 特征列 + 标签列
    features = df.columns[:-1].tolist()
    label = df.columns[-1]
    return df, features, label

# ===================== 2. 通用工具函数 =====================
def get_unique_label_count(data, label):
    """统计标签列的类别及数量,返回{类别:数量}"""
    return data[label].value_counts().to_dict()

def choose_best_feature(data, features, label, method='ID3'):
    """
    选择最优分裂特征
    :param method: 算法类型 ID3/C4.5/CART
    :return: 最优特征名
    """
    # 总样本数
    total_samples = len(data)
    # 1. 计算标签的基础指标(ID3/C4.5:熵值;CART:基尼系数)
    if method in ['ID3', 'C4.5']:
        base_metric = calculate_entropy(data, label)
    else:
        base_metric = calculate_gini(data, label)
    
    best_metric = -float('inf')  # 最优指标值
    best_feature = None          # 最优特征
    
    for feat in features:
        # 按当前特征的不同取值划分子集
        feat_unique_vals = data[feat].unique()
        sub_metric_sum = 0  # 子集指标加权和
        feat_entropy = 0    # C4.5需要:特征自身的熵值
        
        for val in feat_unique_vals:
            # 特征取值为val的子集
            sub_data = data[data[feat] == val]
            sub_samples = len(sub_data)
            # 子集占比
            ratio = sub_samples / total_samples
            
            # 计算子集的指标并加权求和
            if method in ['ID3', 'C4.5']:
                sub_ent = calculate_entropy(sub_data, label)
                sub_metric_sum += ratio * sub_ent
                # C4.5:计算特征自身的熵值
                feat_entropy += -ratio * log2(ratio) if ratio != 0 else 0
            else:
                sub_gini = calculate_gini(sub_data, label)
                sub_metric_sum += ratio * sub_gini
        
        # 计算当前特征的核心指标
        if method == 'ID3':
            # ID3:信息增益 = 基础熵 - 子集熵加权和
            current_metric = base_metric - sub_metric_sum
        elif method == 'C4.5':
            # C4.5:信息增益率 = 信息增益 / 特征自身熵(避免除0)
            info_gain = base_metric - sub_metric_sum
            current_metric = info_gain / (feat_entropy + 1e-8)
        else:
            # CART:基尼系数减少量 = 基础基尼 - 子集基尼加权和(越大越优)
            current_metric = base_metric - sub_metric_sum
        
        # 更新最优特征
        if current_metric > best_metric:
            best_metric = current_metric
            best_feature = feat
    
    return best_feature

def majority_vote(label_count):
    """投票法:标签数量最多的类别(用于叶子节点无特征可分的情况)"""
    return max(label_count, key=label_count.get)

# ===================== 3. ID3/C4.5 核心指标:熵值 + 信息增益 =====================
def calculate_entropy(data, label):
    """计算熵值 H = -Σ(p_i * log2(p_i)),p_i为类别i的占比"""
    label_count = get_unique_label_count(data, label)
    entropy = 0.0
    total = len(data)
    for count in label_count.values():
        p = count / total
        entropy -= p * log2(p) if p != 0 else 0
    return entropy

# ===================== 4. CART 核心指标:基尼系数 =====================
def calculate_gini(data, label):
    """计算基尼系数 Gini = 1 - Σ(p_i^2)"""
    label_count = get_unique_label_count(data, label)
    gini = 1.0
    total = len(data)
    for count in label_count.values():
        p = count / total
        gini -= p ** 2
    return gini

# ===================== 5. 决策树构建(递归) =====================
def create_tree(data, features, label, method='ID3'):
    """
    递归构建决策树
    :return: 决策树字典(如{'天气': {'晴天': {'湿度': {...}}, '阴天': '是', ...}})
    """
    label_list = data[label].tolist()
    # 终止条件1:所有样本标签相同,直接返回该标签
    if label_list.count(label_list[0]) == len(label_list):
        return label_list[0]
    # 终止条件2:无特征可分,返回投票最多的标签
    if len(features) == 0:
        return majority_vote(get_unique_label_count(data, label))
    
    # 选择最优分裂特征
    best_feat = choose_best_feature(data, features, label, method)
    # 初始化决策树
    decision_tree = {best_feat: {}}
    # 移除已选最优特征(避免重复分裂)
    remaining_feats = [f for f in features if f != best_feat]
    # 按最优特征的不同取值递归构建子树
    for val in data[best_feat].unique():
        sub_data = data[data[best_feat] == val]
        decision_tree[best_feat][val] = create_tree(sub_data, remaining_feats, label, method)
    
    return decision_tree

# ===================== 6. 决策树预测 =====================
def predict(tree, sample):
    """
    单样本预测
    :param tree: 构建好的决策树
    :param sample: 单样本字典(如{'天气':'阴天', '温度':'温和', '湿度':'正常', '有风':'否'})
    :return: 预测标签(是/否)
    """
    # 根节点特征
    root_feat = next(iter(tree.keys()))
    # 根节点下的子树
    root_feat_dict = tree[root_feat]
    # 样本的根特征取值
    sample_val = sample[root_feat]
    # 递归查找预测结果
    next_node = root_feat_dict[sample_val]
    if isinstance(next_node, dict):
        # 子节点是树,继续递归
        return predict(next_node, sample)
    else:
        # 子节点是标签,返回结果
        return next_node

# ===================== 7. 主函数(测试三大算法) =====================
if __name__ == '__main__':
    # 1. 加载数据
    df, features, label = create_play_tennis_data()
    print("===== 原始数据集 =====")
    print(df)
    
    # 2. 分别构建ID3、C4.5、CART决策树
    id3_tree = create_tree(df, features, label, method='ID3')
    c45_tree = create_tree(df, features, label, method='C4.5')
    cart_tree = create_tree(df, features, label, method='CART')
    
    print("\n===== ID3决策树 =====")
    print(id3_tree)
    print("\n===== C4.5决策树 =====")
    print(c45_tree)
    print("\n===== CART决策树 =====")
    print(cart_tree)
    
    # 3. 测试预测(单样本)
    test_sample = {'天气':'阴天', '温度':'温和', '湿度':'正常', '有风':'否'}
    id3_pred = predict(id3_tree, test_sample)
    c45_pred = predict(c45_tree, test_sample)
    cart_pred = predict(cart_tree, test_sample)
    print(f"\n===== 测试样本预测 =====")
    print(f"测试样本:{test_sample}")
    print(f"ID3预测结果:{id3_pred}")
    print(f"C4.5预测结果:{c45_pred}")
    print(f"CART预测结果:{cart_pred}")
  • 输出原始数据集 、三大算法的决策树结构(字典形式,直观体现节点分裂逻辑);
  • 测试样本 {'天气':'阴天', '温度':'温和', '湿度':'正常', '有风':'否'} 三大算法均预测(符合业务逻辑);
  • 可自行修改test_sample测试不同场景,也可替换为自己的数据集(需保证特征为离散型,CART 可扩展连续型)。

运行结果:

相关推荐
马猴烧酒.2 小时前
【面试八股|Java集合】Java集合常考面试题详解
java·开发语言·python·面试·八股
天空属于哈夫克32 小时前
Java 版:利用外部群 API 实现自动“技术开课”倒计时提醒
数据库·python·mysql
喵手2 小时前
Python爬虫实战:全站 Sitemap 自动发现 - 解析 sitemap.xml → 自动生成抓取队列的工业级实现!
爬虫·python·爬虫实战·零基础python爬虫教学·sitemap·解析sitemap.xml·自动生成抓取队列实现
luoluoal2 小时前
基于深度学习的web端多格式纠错系统(源码+文档)
python·mysql·django·毕业设计·源码
深蓝海拓2 小时前
PySide6从0开始学习的笔记(二十七) 日志管理
笔记·python·学习·pyqt
天天进步20152 小时前
Python全栈项目:实时数据处理平台
开发语言·python
Tipriest_2 小时前
Python中is关键字详细说明,比较的是地址还是值
开发语言·python
sheji34162 小时前
【开题答辩全过程】以 基于Python的餐饮统计系统的设计和实 现为例,包含答辩的问题和答案
开发语言·python
遨游xyz2 小时前
排序-快速排序
开发语言·python·排序算法