共计 1711 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点:为什么需要决策树?
传统线性回归模型假设特征和目标变量之间存在线性关系,但在实际应用中,很多数据呈现非线性特征。比如房价预测中,面积和价格的关系可能在某个临界点突然变化(如学区房阈值)。这时候,决策树的优势就显现出来了:

- 天然适合处理非线性关系
- 通过树形结构自动学习分段规则
- 结果具有更好的可解释性
CART 回归树的数学原理
1. 基尼系数计算
回归树使用方差最小化作为分裂标准(分类树用基尼系数)。对于节点 m,其方差计算公式为:
$$\text{Variance}(m) = \frac{1}{N_m} \sum_{i \in m} (y_i – \bar{y}_m)^2$$
其中:
– $N_m$ 是节点 m 的样本数
– $\bar{y}_m$ 是节点 m 的样本均值
2. 特征选择与切分
对于每个特征 j 和切分点 s,计算分裂后的方差减少量:
$$\Delta(j,s) = \text{Variance}(m) – \left(\frac{N_l}{N_m}\text{Variance}(l) + \frac{N_r}{N_m}\text{Variance}(r) \right)$$
选择使 $\Delta(j,s)$ 最大的 (j,s) 组合进行分裂。
Python 代码实现
import numpy as np
class TreeNode:
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 # 叶节点预测值
def compute_variance(y):
"""计算节点的方差"""
if len(y) == 0:
return 0
return np.var(y)
def find_best_split(X, y):
"""寻找最优特征和切分点"""
best_gain = -1
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
right_idx = X[:, feature_idx] > thresh
# 计算方差减少量
gain = compute_variance(y) - \
(left_idx.mean()*compute_variance(y[left_idx]) + \
right_idx.mean()*compute_variance(y[right_idx]))
if gain > best_gain:
best_gain = gain
best_feature = feature_idx
best_thresh = thresh
return best_feature, best_thresh
避坑指南
连续值处理
- 需要对连续特征进行排序后扫描所有可能切分点
- 数据标准化不是必须的,但能提升计算效率
防止过拟合
- 预剪枝:
- 设置最大树深度
-
设置叶节点最小样本数
-
后剪枝:
- 构建完整树后自底向上剪枝
- 使用验证集评估剪枝效果
性能优化
- 时间复杂度:$O(n_{\text{features}} \times n_{\text{samples}} \log n_{\text{samples}})$
- 实际工程中会使用近似算法加速(如 sklearn 的直方图近似)
延伸思考
- 随机森林扩展:
- 通过 bootstrap 采样构建多棵树
-
特征采样增加多样性
-
缺失值处理:
- 通过代理分裂 (surrogate splits) 处理
- 将缺失样本分配到增益最大的分支
实践心得
在真实项目中,CART 回归树的参数调优需要特别注意 max_depth 和 min_samples_leaf 的设置。过深的树容易捕捉噪声,而过浅的树可能欠拟合。建议通过交叉验证找到最佳参数组合,同时结合业务理解解释树的分裂规则。
正文完
