从零实现CHAD决策树:Python代码详解与避坑指南

1次阅读
没有评论

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

image.webp

技术背景:CHAID 与传统决策树的差异

CHAID(Chi-squared Automatic Interaction Detection)与传统决策树(如 CART)有三大核心区别:

从零实现 CHAD 决策树:Python 代码详解与避坑指南

  1. 分裂标准 :使用卡方检验(分类问题)或 F 检验(连续目标)代替信息增益 / 基尼系数。通过统计显著性判断分裂合理性,公式表示为:
    $$\chi^2 = \sum\frac{(O-E)^2}{E}$$
    其中 $O$ 为观测值,$E$ 为期望值

  2. 多路分裂 :支持非二叉树结构,例如一个分类特征有 3 个取值时可直接生成 3 个子节点

  3. 预剪枝机制 :通过合并相似类别(如卡方检验 p 值 >0.05 的类别)和设置最小样本量阈值防止过拟合

核心代码实现

1. 卡方检验计算

from scipy.stats import chi2_contingency
import numpy as np

def calculate_chi2(feature, target):
    """
    计算特征与目标的卡方统计量和 p 值
    :param feature: 待检验特征数组
    :param target: 目标变量数组
    :return: (chi2_statistic, p_value)
    """
    contingency_table = pd.crosstab(feature, target)
    chi2, p, _, _ = chi2_contingency(contingency_table)
    return chi2, p

2. 最佳分裂点选择

def find_best_split(df, feature, target, min_samples=5):
    """
    寻找特征的最佳分裂方式
    :param df: 包含特征和目标的数据框
    :param feature: 待分析特征名
    :param target: 目标变量名
    :param min_samples: 节点最小样本量
    :return: (最佳分裂值, 分裂后卡方值) 或 None
    """
    unique_values = df[feature].unique()
    if len(unique_values) == 1:
        return None

    # 对连续变量进行离散化处理
    if df[feature].dtype.kind in 'biufc':
        split_points = sorted(unique_values)[1:-1]
        best_chi2 = -np.inf
        best_split = None

        for val in split_points:
            temp_feature = (df[feature] <= val).astype(int)
            chi2, _ = calculate_chi2(temp_feature, df[target])
            if chi2 > best_chi2:
                best_chi2 = chi2
                best_split = val
        return best_split, best_chi2

    # 分类变量直接计算
    else:
        chi2, p = calculate_chi2(df[feature], df[target])
        if p < 0.05 and df[feature].nunique() >= 2:
            return 'multiway', chi2
    return None

3. 剪枝条件判断

def should_prune(node, min_samples_leaf=10, max_depth=5):
    """
    判断是否需要剪枝
    :param node: 当前节点
    :param min_samples_leaf: 叶节点最小样本量
    :param max_depth: 最大树深度
    :return: bool
    """
    conditions = [
        node.sample_size < min_samples_leaf,
        node.depth >= max_depth,
        len(node.children) == 0,
        node.chi2 < 3.841  # 卡方临界值 (p=0.05, df=1)
    ]
    return any(conditions)

对比实验

我们使用 UCI 的 Adult 数据集测试不同参数效果:

参数组合 准确率 叶节点数
min_samples_leaf=5 0.824 143
min_samples_leaf=20 0.817 87
max_depth=3 0.802 42
不剪枝 0.826 210

实验显示:适当增大 min_samples_leaf 能在轻微损失精度的情况下显著简化模型结构

生产环境注意事项

  1. 类别不平衡处理
  2. 对卡方检验进行 Yates 校正:
    from scipy.stats import chi2_contingency
    chi2, p, _, _ = chi2_contingency(contingency_table, correction=True)
  3. 对少数类样本加权:

    sample_weight = compute_class_weight('balanced', classes=y.unique(), y=y)

  4. 大数据优化

  5. 对连续特征采用等频分箱代替等宽分箱
  6. 使用稀疏矩阵存储高基数分类特征

延伸思考

  1. 如何改进当前算法使其支持混合类型特征(数值 + 分类)的自动处理?
  2. 当特征间存在强相关性时,CHAID 会如何表现?可以增加哪些校验机制?

实现过程中发现,CHAID 虽然解释性强,但在处理连续特征时效率较低。下次我们可以尝试结合信息增益比来优化数值特征的分裂点选择。

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