决策树实战:从Gini系数理解到Cart算法实现

1次阅读
没有评论

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

image.webp

理解 Gini 系数:决策树的核心指标

Gini 系数是衡量数据集不纯度的关键指标,数值范围在 0 到 1 之间。当 Gini 系数为 0 时,表示数据集完全纯净,所有样本都属于同一类别;当 Gini 系数接近 1 时,表示数据集非常不纯,样本类别分布均匀。

决策树实战:从 Gini 系数理解到 Cart 算法实现

  1. 数学定义 :对于一个包含 K 个类别的数据集 D,Gini 系数计算公式为:

    Gini(D) = 1 - Σ(p_i)^2

    其中 p_i 是第 i 类样本在数据集 D 中的比例。

  2. 物理意义 :Gini 系数实际上表示从数据集中随机抽取两个样本,它们属于不同类别的概率。这个值越小,说明数据集纯度越高。

  3. 计算示例 :假设一个二分类数据集中有 80 个正样本和 20 个负样本,那么它的 Gini 系数为:

    1 - (0.8)^2 - (0.2)^2 = 0.32

Gini 系数 vs 信息增益

决策树常用的分裂标准除了 Gini 系数,还有信息增益(基于信息熵)。两者各有特点:

  • Gini 系数优点 :
  • 计算更简单,不需要对数运算
  • 对类别分布不平衡的数据集表现更好
  • 在实际应用中通常与信息增益效果相当

  • 信息增益优点 :

  • 理论更完备,基于信息论
  • 对具有更多类别的特征更敏感
  • 可以通过信息增益比解决偏向多值特征的问题

Python 实现 Cart 决策树

下面是一个完整的 Cart 决策树实现,包含 Gini 系数计算和特征选择:

import numpy as np

class DecisionTree:
    def __init__(self, max_depth=None):
        self.max_depth = max_depth

    def _gini(self, y):
        """计算 Gini 系数"""
        classes = np.unique(y)
        gini = 1.0
        for cls in classes:
            p = len(y[y == cls]) / len(y)
            gini -= p ** 2
        return gini

    def _best_split(self, X, y):
        """寻找最佳分裂特征和阈值"""
        best_gini = float('inf')
        best_feature = None
        best_threshold = None

        for feature in range(X.shape[1]):
            thresholds = np.unique(X[:, feature])
            for threshold in thresholds:
                left_indices = X[:, feature] <= threshold
                right_indices = X[:, feature] > threshold

                if len(y[left_indices]) == 0 or len(y[right_indices]) == 0:
                    continue

                gini_left = self._gini(y[left_indices])
                gini_right = self._gini(y[right_indices])

                weighted_gini = (len(y[left_indices]) * gini_left + 
                                len(y[right_indices]) * gini_right) / len(y)

                if weighted_gini < best_gini:
                    best_gini = weighted_gini
                    best_feature = feature
                    best_threshold = threshold

        return best_feature, best_threshold

    def fit(self, X, y, depth=0):
        """递归构建决策树"""
        if len(np.unique(y)) == 1 or (self.max_depth is not None and depth >= self.max_depth):
            self.label = np.argmax(np.bincount(y))
            return

        self.feature, self.threshold = self._best_split(X, y)
        if self.feature is None:
            self.label = np.argmax(np.bincount(y))
            return

        left_indices = X[:, self.feature] <= self.threshold
        right_indices = X[:, self.feature] > self.threshold

        self.left = DecisionTree(self.max_depth)
        self.right = DecisionTree(self.max_depth)

        self.left.fit(X[left_indices], y[left_indices], depth + 1)
        self.right.fit(X[right_indices], y[right_indices], depth + 1)

    def predict(self, X):
        """预测新样本"""
        if hasattr(self, 'label'):
            return self.label

        if X[self.feature] <= self.threshold:
            return self.left.predict(X)
        else:
            return self.right.predict(X)

解决过拟合问题

决策树容易过拟合训练数据,常见的解决方法包括:

  1. 预剪枝 :在树完全生长前停止
  2. 限制最大深度
  3. 设置叶子节点最小样本数
  4. 设置分裂的最小 Gini 系数改善值

  5. 后剪枝 :先让树完全生长,然后修剪

  6. 计算剪枝前后的验证集准确率
  7. 如果剪枝后准确率不降,则剪枝
  8. 可以使用成本复杂度剪枝 (CCP)

决策树最佳实践

在实际项目中使用 Cart 决策树时,建议:

  1. 特征工程 :
  2. 对连续特征进行分箱处理
  3. 对类别特征进行编码(如 one-hot)
  4. 处理缺失值(如用中位数 / 众数填充)

  5. 模型调优 :

  6. 使用网格搜索调整 max_depth、min_samples_split 等参数
  7. 尝试不同的分裂标准(Gini/ 信息增益)
  8. 使用交叉验证评估模型性能

  9. 模型解释 :

  10. 可视化决策树理解决策路径
  11. 计算特征重要性
  12. 使用 SHAP 值等方法解释预测

思考题

  1. Gini 系数为 0 意味着什么?在什么情况下会出现?
    Gini 系数为 0 表示节点完全纯净,所有样本都属于同一类别。这种情况会在样本类别完全一致,或者决策树已经完美分类时出现。

  2. 如何处理连续型特征?
    对于连续型特征,Cart 算法会尝试所有可能的划分点(通常是特征值的中间点),选择使 Gini 系数降低最多的划分点。这可以通过排序特征值后检查相邻值的中点来实现。

总结

通过本文的学习,我们深入理解了 Gini 系数的计算原理和在 Cart 决策树中的应用。实现了一个完整的决策树分类器,并讨论了避免过拟合的方法和实际应用中的最佳实践。决策树虽然简单,但在很多场景下仍然是非常有效的模型,特别是需要模型可解释性的场合。

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