深入解析CART决策树的计算步骤:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

基尼系数与熵的本质差异

在构建决策树时,我们需要一个指标来衡量数据的不纯度(Impurity)。最常用的两个指标是基尼系数(Gini Index)和熵(Entropy)。

深入解析 CART 决策树的计算步骤:从数学原理到 Python 实现

  1. 基尼系数公式

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

    其中 p_i 是第 i 类样本的比例。基尼系数计算的是随机抽取两个样本,它们类别不一致的概率。

  2. 熵的公式

    Entropy(p) = -Σp_i * log2(p_i)

    熵衡量的是系统的不确定性,值越大表示混乱程度越高。

  3. 关键区别

  4. 计算效率:基尼系数不涉及对数运算,计算速度更快
  5. 结果差异:两者在大多数情况下选择的分裂点相似,但熵对不纯度变化更敏感
  6. 实际应用:sklearn 默认使用基尼系数,因其计算开销更小

CART 决策树构建全流程

1. 特征选择与分裂阈值计算

决策树的核心是递归地选择最优特征和分裂点。对于每个特征,我们需要:

  1. 对连续特征:
  2. 排序所有可能的分割点
  3. 计算每个分割点的基尼指数增益
  4. 选择增益最大的分割点

  5. 对分类特征:

  6. 考虑所有可能的子集划分
  7. 计算每种划分的基尼指数
  8. 选择最优划分方式

基尼增益计算公式

ΔGini = Gini(D) - (|D_left|/|D|)*Gini(D_left) - (|D_right|/|D|)*Gini(D_right)

2. Python 实现核心逻辑

以下是使用 numpy 实现的 CART 决策树核心代码:

import numpy as np

class Node:
    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 DecisionTree:
    def __init__(self, max_depth=None):
        self.max_depth = max_depth

    def _gini(self, y):
        # 计算基尼系数
        classes = np.unique(y)
        gini = 1.0
        for c in classes:
            p = np.sum(y == c) / len(y)
            gini -= p**2
        return gini

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

        for feature_idx in range(X.shape[1]):
            thresholds = np.unique(X[:, feature_idx])
            for thresh in thresholds:
                left_idx = X[:, feature_idx] <= thresh
                gini = self._gini(y[left_idx]) * np.sum(left_idx)/len(y) + \
                       self._gini(y[~left_idx]) * np.sum(~left_idx)/len(y)
                if gini < best_gini:
                    best_gini = gini
                    best_feature = feature_idx
                    best_thresh = thresh
        return best_feature, best_thresh

    def fit(self, X, y, depth=0):
        # 递归构建决策树
        if depth == self.max_depth or len(np.unique(y)) == 1:
            return Node(value=np.argmax(np.bincount(y)))

        feature, thresh = self._best_split(X, y)
        left_idx = X[:, feature] <= thresh
        node = Node(feature_idx=feature, threshold=thresh,
                   left=self.fit(X[left_idx], y[left_idx], depth+1),
                   right=self.fit(X[~left_idx], y[~left_idx], depth+1))
        return node

3. Iris 数据集实战演示

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# 训练模型
tree = DecisionTree(max_depth=3)
root = tree.fit(X_train, y_train)

# 预测函数(需自行实现)def predict(node, x):
    if node.value is not None:
        return node.value
    if x[node.feature_idx] <= node.threshold:
        return predict(node.left, x)
    else:
        return predict(node.right, x)

性能优化与调参技巧

1. 剪枝策略

  • 预剪枝(Pre-pruning)
  • 限制最大深度(max_depth)
  • 设置叶节点最小样本数(min_samples_leaf)
  • 设定分裂最小增益(min_impurity_decrease)

  • 后剪枝(Post-pruning)

  • 构建完整树后,自底向上合并节点
  • 使用验证集评估合并前后的准确率
  • 采用代价复杂度剪枝(Cost-Complexity Pruning)

2. 连续特征处理技巧

  1. 提前排序特征值,加速分裂点搜索
  2. 使用分位数作为候选分割点,减少计算量
  3. 对大规模数据采用近似算法(如直方图近似)

常见陷阱与解决方案

  • 过拟合表现
  • 训练集准确率 100% 但测试集差
  • 树深度过大,节点过多

  • 解决方法

  • 增加 min_samples_split 参数
  • 使用交叉验证选择最优 max_depth
  • 添加正则化项

  • 类别不平衡处理

  • 使用 class_weight 参数
  • 采用平衡准确率(balanced accuracy)作为评估指标
  • 对少数类样本进行过采样

延伸思考

  1. 如何基于 CART 树构建随机森林?关键在于 bootstrap 采样和特征随机选择
  2. 当遇到类别极度不平衡时,可以考虑调整分裂标准(如使用信息增益比)
  3. CART 与 GBDT 的核心差异:GBDT 是加法模型,通过梯度提升迭代优化;CART 是单棵树,通过递归分裂构建

结语

通过手动实现 CART 算法,我们能更深入理解决策树的工作原理。虽然实际应用中我们会直接使用 sklearn 等库,但理解底层计算逻辑对于调参和问题诊断至关重要。建议读者尝试扩展这个基础实现,比如加入剪枝功能或支持回归任务,这将大大加深对树模型的理解。

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