决策树实战:从基尼系数到CART算法实现

1次阅读
没有评论

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

image.webp

核心概念

决策树是机器学习中最直观的分类工具之一,它通过一系列简单的 if-else 规则模拟人类决策过程,特别适合处理结构化数据和非线性关系。对于初学者来说,理解其核心分裂指标和构建流程是掌握该算法的关键。

决策树实战:从基尼系数到 CART 算法实现

基尼系数 vs 信息增益

  • 基尼系数 (Gini Index):衡量数据集不纯度的指标,值越小表示纯度越高。计算公式为:

    def gini_index(groups, classes):
        n_instances = sum(len(group) for group in groups)
        gini = 0.0
        for group in groups:
            size = len(group)
            if size == 0:
                continue
            score = 0.0
            for class_val in classes:
                p = [row[-1] for row in group].count(class_val) / size
                score += p * p
            gini += (1.0 - score) * (size / n_instances)
        return gini

  • 信息增益 (Information Gain):基于信息熵的指标,计算复杂度略高于基尼系数

实际应用中,基尼系数通常作为默认选择,因为:
1. 计算速度更快(无需对数运算)
2. 对类别分布差异更敏感
3. 在 sklearn 等库中默认使用

过拟合的典型症状

  • 训练集准确率接近 100% 但测试集表现骤降
  • 决策路径出现明显不合理的长链条
  • 对微小数据变化极度敏感

算法实现

CART 二叉树构建流程

  1. 从根节点开始,计算当前节点的基尼不纯度
  2. 遍历所有特征及其可能的分裂点
  3. 选择使子节点加权不纯度下降最多的分裂方案
  4. 递归处理子节点直到满足停止条件

图示说明:

        [根节点]
         /    \
    [特征 A≤0.5] [特征 A >0.5]
      /   \       /   \
   [类 1] [类 2] [类 3] [继续分裂]

完整 Python 实现

class DecisionNode:
    def __init__(self, feature_idx=None, threshold=None,
                 left=None, right=None, value=None):
        self.feature_idx = feature_idx  # 分裂特征索引
        self.threshold = threshold      # 分裂阈值
        self.left = left                # 左子树
        self.right = right              # 右子树
        self.value = value              # 叶节点预测值

class CARTClassifier:
    def __init__(self, max_depth=5):
        self.max_depth = max_depth

    def _gini(self, y):
        m = y.size
        return 1.0 - sum((np.sum(y == c) / m) ** 2 for c in np.unique(y))

    def _best_split(self, X, y):
        best_gini = float('inf')
        best_idx, best_thr = None, None

        for idx in range(X.shape[1]):
            thresholds = np.unique(X[:, idx])
            for thr in thresholds:
                left_mask = X[:, idx] <= thr
                gini = (left_mask.sum() * self._gini(y[left_mask]) + 
                       (~left_mask).sum() * self._gini(y[~left_mask])) / y.size
                if gini < best_gini:
                    best_gini = gini
                    best_idx = idx
                    best_thr = thr
        return best_idx, best_thr

    def fit(self, X, y, node=None, depth=0):
        if node is None:
            self.tree = DecisionNode()
            node = self.tree

        if depth >= self.max_depth or len(np.unique(y)) == 1:
            node.value = np.argmax(np.bincount(y))
            return

        idx, thr = self._best_split(X, y)
        if idx is None:
            node.value = np.argmax(np.bincount(y))
            return

        left_mask = X[:, idx] <= thr
        node.feature_idx = idx
        node.threshold = thr
        node.left = DecisionNode()
        node.right = DecisionNode()

        self.fit(X[left_mask], y[left_mask], node.left, depth+1)
        self.fit(X[~left_mask], y[~left_mask], node.right, depth+1)

优化策略

剪枝技术对比

预剪枝 (Pre-pruning):

# sklearn 实现示例
from sklearn.tree import DecisionTreeClassifier
clf = DecisionTreeClassifier(
    max_depth=3, 
    min_samples_leaf=5,
    min_impurity_decrease=0.01
)

后剪枝 (Post-pruning):

def prune(node, X_val, y_val):
    if node.value is not None:
        return node.value == y_val.mean()

    left_mask = X_val[:, node.feature_idx] <= node.threshold

    # 剪枝判断条件
    if (prune(node.left, X_val[left_mask], y_val[left_mask]) and
        prune(node.right, X_val[~left_mask], y_val[~left_mask])):
        node.value = np.argmax(np.bincount(y_val))
        return True
    return False

连续特征处理

  • 等宽分箱:pd.cut(feature, bins=10)
  • 等频分箱:pd.qcut(feature, q=10)
  • 基于分裂点动态离散化(CART 原生支持)

类别不平衡处理

# sklearn 中的类别权重设置
clf = DecisionTreeClassifier(class_weight={0:1, 1:5}  # 少数类样本权重放大
)

生产实践

特征重要性计算

基尼重要性公式:

 特征 j 的重要性 = ∑(节点 k 的样本占比 × ΔGini_k)

sklearn 实现:

importance = clf.feature_importances_

部署注意事项

  1. 对连续特征进行标准化处理(0- 1 归一化)
  2. 限制树深度防止 API 响应超时
  3. 添加特征缺失值的默认处理分支

延伸思考

  1. 特征重要性解释 :基尼系数下降越多的特征,对分类决策贡献越大。但需要注意高基数类别特征可能带来虚假重要性

  2. 决策树劣势场景 :

  3. 特征间存在强线性关系
  4. 数据维度极高且样本稀疏
  5. 需要概率输出时(相比逻辑回归)

  6. 进阶方向 :

  7. 尝试用 Graphviz 可视化决策路径
  8. 研究 GBDT/XGBoost 等集成方法如何提升单棵树的表现

决策树就像数据科学的瑞士军刀——简单但功能强大。理解其核心机制后,你可以更自信地调整参数和应对各种数据挑战。试着用本文代码在 Kaggle 数据集上实践,观察不同参数如何影响模型表现吧!

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