共计 2882 个字符,预计需要花费 8 分钟才能阅读完成。
1. 算法背景
决策树是机器学习中最基础且直观的算法之一,而 CART(Classification and Regression Trees)算法则是其中最经典的实现方式。它不仅能处理分类问题(通过基尼系数),还能解决回归问题(通过方差最小化)。与 ID3、C4.5 等算法相比,CART 的特点是:

- 严格生成二叉树结构
- 同时支持分类和回归任务
- 默认使用基尼不纯度作为分类标准
2. 核心原理
2.1 基尼系数计算
基尼系数衡量数据的不纯度,值越小表示纯度越高。计算公式为:
Gini = 1 - Σ(p_i)^2
其中 p_i 是第 i 类样本在节点中的比例。计算示例:
- 假设一个节点有 10 个样本,类别 A 有 6 个,类别 B 有 4 个
- Gini = 1 – (6/10)^2 – (4/10)^2 = 0.48
2.2 特征选择
CART 采用贪婪算法选择最佳分割特征和分割点:
- 遍历所有特征
- 对每个特征,遍历所有可能的分割点
- 计算分割后的加权基尼系数
- 选择使基尼系数下降最多的特征和分割点
2.3 节点分裂
分裂过程递归进行,直到满足停止条件:
- 当前节点样本数小于阈值
- 基尼系数低于阈值
- 达到最大树深度
3. Python 实现
以下是完整的 CART 决策树实现:
import numpy as np
class TreeNode:
def __init__(self, feature_idx=None, threshold=None, value=None, left=None, right=None):
self.feature_idx = feature_idx # 分裂特征索引
self.threshold = threshold # 分裂阈值
self.value = value # 叶节点值
self.left = left # 左子树
self.right = right # 右子树
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.root = None
def _gini(self, y):
classes = np.unique(y)
gini = 1.0
for cls in classes:
p = np.sum(y == cls) / 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_indices = X[:, feature_idx] <= thresh
right_indices = ~left_indices
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_idx
best_thresh = thresh
return best_feature, best_thresh
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_idx, threshold = self._best_split(X, y)
if feature_idx is None:
return TreeNode(value=np.argmax(np.bincount(y)))
left_indices = X[:, feature_idx] <= threshold
right_indices = ~left_indices
left = self._build_tree(X[left_indices], y[left_indices], depth+1)
right = self._build_tree(X[right_indices], y[right_indices], depth+1)
return TreeNode(feature_idx, threshold, left=left, right=right)
def fit(self, X, y):
self.root = self._build_tree(X, y)
def predict(self, X):
return np.array([self._predict_tree(x, self.root) for x in X])
def _predict_tree(self, x, node):
if node.value is not None:
return node.value
if x[node.feature_idx] <= node.threshold:
return self._predict_tree(x, node.left)
else:
return self._predict_tree(x, node.right)
4. 优化技巧
4.1 预剪枝
在树构建过程中提前停止生长:
- 限制最大深度
- 设置最小样本分裂数
- 设置信息增益阈值
4.2 后剪枝
先构建完整树,再自底向上剪枝:
- 计算剪枝前后的验证集准确率
- 如果准确率不下降则剪枝
- 重复直到无法继续剪枝
5. 避坑指南
- 过拟合问题 :
-
解决方案:合理设置 max_depth 和 min_samples_split
-
类别不平衡 :
-
解决方案:使用 class_weight 参数或重采样
-
连续值处理 :
-
解决方案:离散化或直接使用原始值
-
计算效率低 :
- 解决方案:对特征值排序后计算分割点
6. 实战建议
- 小型数据集 :
- 可尝试不限制树深度
-
重点关注可解释性
-
大型数据集 :
- 建议使用预剪枝
-
考虑使用并行化实现
-
高维数据 :
- 先进行特征选择
- 考虑集成学习方法
思考题
- 如何修改算法使其支持回归任务?
- 当特征大部分是类别型时,应该如何优化分割策略?
- 如何实现决策树的可视化展示?
实现一个完整的 CART 决策树需要考虑很多细节,但核心思想并不复杂。希望这篇文章能帮助你理解算法原理并实现自己的决策树模型。在实际项目中,建议先使用 scikit-learn 中的 DecisionTreeClassifier 验证思路,再尝试自己的实现。
正文完
