C4.5决策树算法头歌实战:从原理到Python实现

1次阅读
没有评论

共计 1954 个字符,预计需要花费 5 分钟才能阅读完成。

image.webp

决策树作为最直观的机器学习算法之一,因其模型可解释性强、接近人类决策过程的特点,成为分类问题中的入门首选工具。今天我们就用 Python 手把手实现经典改进版本——C4.5 算法。

C4.5 决策树算法头歌实战:从原理到 Python 实现

为什么选择 C4.5 而不是 ID3?

C4.5 在 ID3 基础上做了两个关键改进:

  1. 用信息增益比替代信息增益,解决 ID3 对取值多属性的偏好问题
  2. 增加对连续特征的处理能力

信息增益比的计算分三步走:

  • 先计算信息增益 $Gain(D,a)$
  • 再计算属性 a 的固有值 $IV(a)$(类似熵的计算)
  • 最后求比值 $Gain\_ratio(D,a) = \frac{Gain(D,a)}{IV(a)}$

Python 实现全流程

数据预处理关键步骤

处理连续值时,需要先排序然后寻找最佳分割点:

def choose_best_split(feature_values):
    unique_vals = sorted(set(feature_values))
    split_points = [(unique_vals[i]+unique_vals[i+1])/2 
                   for i in range(len(unique_vals)-1)]
    # 计算每个分割点的信息增益比
    best_gain_ratio = 0
    for point in split_points:
        # 分割后计算信息增益比...
        current_ratio = calc_gain_ratio(...)
        if current_ratio > best_gain_ratio:
            best_point = point
    return best_point

递归建树核心逻辑

def create_tree(dataset, features):
    # 终止条件 1:所有样本属于同一类别
    if len(set(dataset[-1])) == 1:
        return dataset[-1][0]

    # 终止条件 2:没有可用特征
    if not features:
        return majority_vote(dataset[-1])

    # 选择最佳划分特征
    best_feature = select_best_feature(dataset)
    tree = {best_feature: {}}

    # 递归构建子树
    for value in get_values(dataset, best_feature):
        sub_dataset = split_dataset(dataset, best_feature, value)
        tree[best_feature][value] = create_tree(sub_dataset, 
                                              features.remove(best_feature))
    return tree

可视化决策树(使用 graphviz)

import graphviz 

def visualize_tree(tree):
    dot = graphviz.Digraph()
    build_graph(dot, tree)
    return dot

# 递归构建图节点
def build_graph(dot, tree, parent_node=None, edge_label=None):
    if isinstance(tree, dict):
        for node, branches in tree.items():
            dot.node(str(id(node)), str(node))
            if parent_node:
                dot.edge(str(id(parent_node)), str(id(node)), label=edge_label)
            for value, subtree in branches.items():
                build_graph(dot, subtree, node, str(value))
    else:
        dot.node(str(id(tree)), str(tree), shape='box')
        if parent_node:
            dot.edge(str(id(parent_node)), str(id(tree)), label=edge_label)

避坑指南

过拟合与剪枝策略

  • 预剪枝:在建树过程中通过验证集准确率决定是否继续分裂
  • 后剪枝:先完全建树,再自底向上替换为叶节点观察准确率变化

缺失值处理三件套

  1. 计算信息增益比时忽略缺失样本
  2. 将缺失样本按不同分支权重分配
  3. 预测时如果遇到缺失特征,走概率最大的分支

类别不平衡解决方案

  • 在计算信息量时采用加权熵
  • 对少数类样本进行过采样
  • 在验证时使用 F1-score 代替准确率

思考进阶方向

  1. 如果把分类改成回归,应该如何修改分裂标准?(提示:可以用方差替代熵)
  2. 对比随机森林中的决策树,单棵 C4.5 树有哪些优缺点?
  3. 当特征维度达到百万级时,哪些步骤会成为性能瓶颈?

完整代码已上传 GitHub 仓库(伪代码位置替换为实际链接),包含测试数据集和更详细的注释说明。建议在头歌平台创建项目时,可以先从较小的数据集开始验证核心算法,再逐步添加预处理和优化模块。

正文完
 0
评论(没有评论)