共计 3285 个字符,预计需要花费 9 分钟才能阅读完成。
CART 决策树算法深度解析
基础概念与数学原理
决策树是一种常见的机器学习方法,它通过树状结构对数据进行分类或回归。CART(Classification and Regression Trees)是其中最著名的算法之一,由 Breiman 等人于 1984 年提出。

基尼系数与信息增益
CART 算法使用基尼系数 (Gini Index) 作为分裂标准,这与 ID3 和 C4.5 使用的信息增益 (Information Gain) 有所不同。
-
基尼系数:衡量数据集的不纯度,值越小表示纯度越高
Gini(D) = 1 - Σ(p_i)^2其中 p_i 是第 i 类样本在数据集 D 中的比例
-
信息增益:基于信息熵的概念,表示特征对信息不确定性的减少程度
Gain(D,A) = Entropy(D) - Σ(|D_v|/|D|)*Entropy(D_v)
CART 与 ID3、C4.5 的对比
- 分裂标准
- CART:基尼系数(分类)或最小二乘误差(回归)
- ID3:信息增益
-
C4.5:信息增益比
-
输出类型
- CART:可处理分类和回归问题
-
ID3/C4.5:仅处理分类问题
-
特征类型
- CART:支持连续和离散特征
- ID3:仅支持离散特征
-
C4.5:支持离散特征,可处理缺失值
-
树结构
- CART:二叉树结构
- ID3/C4.5:多叉树结构
Python 实现示例
以下是 CART 决策树的简化实现,包含特征选择、树构建和预测过程。
import numpy as np
class TreeNode:
def __init__(self, feature=None, threshold=None, left=None, right=None, value=None):
self.feature = feature # 分裂特征
self.threshold = threshold # 分裂阈值
self.left = left # 左子树
self.right = right # 右子树
self.value = value # 叶节点值
class CARTDecisionTree:
def __init__(self, max_depth=None, min_samples_split=2):
self.max_depth = max_depth
self.min_samples_split = min_samples_split
self.tree = None
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 = None
best_threshold = None
for feature in range(X.shape[1]):
thresholds = np.unique(X[:, feature])
for threshold in thresholds:
left_idx = X[:, feature] <= threshold
right_idx = ~left_idx
gini_left = self._gini(y[left_idx])
gini_right = self._gini(y[right_idx])
weighted_gini = (len(y[left_idx]) * gini_left +
len(y[right_idx]) * gini_right) / len(y)
if weighted_gini < best_gini:
best_gini = weighted_gini
best_feature = feature
best_threshold = threshold
return best_feature, best_threshold, best_gini
def _build_tree(self, X, y, depth=0):
if (self.max_depth is not None and depth >= self.max_depth) or
len(y) < self.min_samples_split or len(np.unique(y)) == 1:
return TreeNode(value=np.argmax(np.bincount(y)))
feature, threshold, gini = self._best_split(X, y)
if gini == float('inf'):
return TreeNode(value=np.argmax(np.bincount(y)))
left_idx = X[:, feature] <= threshold
right_idx = ~left_idx
left_subtree = self._build_tree(X[left_idx], y[left_idx], depth+1)
right_subtree = self._build_tree(X[right_idx], y[right_idx], depth+1)
return TreeNode(feature=feature, threshold=threshold,
left=left_subtree, right=right_subtree)
def fit(self, X, y):
self.tree = self._build_tree(X, y)
return self
def _predict_one(self, x, node):
if node.value is not None:
return node.value
if x[node.feature] <= node.threshold:
return self._predict_one(x, node.left)
else:
return self._predict_one(x, node.right)
def predict(self, X):
return np.array([self._predict_one(x, self.tree) for x in X])
算法复杂度与优化建议
时间复杂度分析
- 训练阶段
- 每次分裂需要遍历所有特征和可能的阈值
-
对于 m 个特征,n 个样本,构建完整树的时间复杂度为 O(mn log n)
-
预测阶段
- 单样本预测时间复杂度为 O(树深度)
- 通常树深度为 O(log n)
大规模数据优化建议
- 特征采样
- 随机选取部分特征进行分裂(类似随机森林)
-
减少每次分裂的计算量
-
数据采样
- 使用子样本构建树
-
结合 bagging 或 boosting 方法
-
并行化
- 特征级别的并行化
-
树构建的并行化
-
增量学习
- 对新增数据增量更新树结构
- 适用于流式数据
过拟合问题与解决方案
决策树容易过拟合训练数据,常见解决方法包括:
预剪枝(Pre-pruning)
在树构建过程中提前停止生长:
- 最大深度限制
-
设置树的最大深度
-
最小样本分裂
-
节点样本数小于阈值时停止分裂
-
最小纯度提升
- 分裂带来的纯度提升小于阈值时停止
后剪枝(Post-pruning)
先构建完整树,再剪去不必要的分支:
- 代价复杂度剪枝(CCP)
- 引入复杂度惩罚项
-
最小化损失函数:Loss = 误差 + α * 叶节点数
-
错误率降低剪枝(REP)
- 使用验证集评估剪枝效果
- 剪去对验证集准确率无贡献的分支
实际应用场景
金融风控
- 信用评分模型
- 评估借款人违约风险
-
可解释性强,符合监管要求
-
反欺诈检测
- 识别异常交易模式
- 处理类别不平衡问题
推荐系统
- 用户分群
-
基于用户特征和行为进行分群
-
冷启动问题
- 处理新用户 / 新物品的推荐
生产环境避坑指南
- 类别不平衡处理
- 使用类别权重
- 过采样 / 欠采样
-
使用 AUC 等不敏感指标
-
连续值离散化
- 等宽 / 等频分箱
-
基于信息增益的最优分箱
-
缺失值处理
- 代理分裂(Surrogate Splits)
-
默认方向处理
-
特征重要性评估
- 基于分裂次数或纯度提升
- 避免特征数量偏差
决策树在深度学习时代的竞争力
尽管深度学习在很多领域表现出色,决策树类算法仍然有其独特优势:
- 可解释性
-
金融、医疗等领域需要模型可解释
-
小数据表现
-
在小样本情况下表现良好
-
计算效率
-
训练和预测速度快
-
集成方法
- 随机森林、GBDT 等集成方法依然强大
未来发展方向可能包括:
- 与深度学习的结合(深度森林)
- 自适应学习率调整
- 在线学习能力增强
决策树类算法仍然是机器学习工具箱中不可或缺的一部分,特别是在需要模型可解释性和处理结构化数据的场景中。
