共计 2423 个字符,预计需要花费 7 分钟才能阅读完成。
从基尼系数与熵说起
决策树的核心是选择最优分裂特征,CART 使用基尼系数(Gini Index),而 ID3/C4.5 使用信息增益(熵)。两者的本质都是衡量数据不纯度(impurity),但计算方式不同:

- 基尼系数:$Gini(p) = 1 – \sum_{k=1}^K p_k^2$,计算更简单(无需对数运算)
- 信息熵:$H(p) = -\sum_{k=1}^K p_k \log p_k$,对不均衡数据更敏感
以二分类为例,当类别概率为 [0.5, 0.5] 时:
– 基尼系数 = 1 – (0.25 + 0.25) = 0.5
– 信息熵 = -(0.5log0.5 + 0.5log0.5) ≈ 0.69
特征选择实战:基尼指数计算
CART 采用 二分法 处理特征,对于每个特征的所有可能分割点计算基尼指数:
$Gini_index(D, a) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2)$
示例数据集:
| 年龄 | 收入 | 是否购买 |
|——|——-|———-|
| 青年 | 低 | 否 |
| 青年 | 高 | 是 |
| 中年 | 高 | 是 |
| 老年 | 中 | 否 |
计算 ” 收入 = 低 ” 作为分割点的基尼指数:
1. 左子集 D1(收入 = 低):[否] → Gini(D1)=0
2. 右子集 D2(收入≠低):[是, 是, 否] → Gini(D2)=1-(2/3)²-(1/3)²≈0.444
3. 基尼指数 = (1/4)0 + (3/4)0.444 ≈ 0.333
Python 手写实现
1. 定义节点结构
class Node:
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 # 叶节点预测值
2. 递归构建树
def build_tree(X, y, max_depth=3):
# 终止条件:纯度 100% 或达到最大深度
if len(set(y)) == 1 or max_depth == 0:
return Node(value=max(set(y), key=list(y).count))
best_gini = float('inf')
best_feature, best_threshold = None, None
# 遍历所有特征和可能的分割点
for feature in range(X.shape[1]):
thresholds = np.unique(X[:, feature])
for threshold in thresholds:
left_idx = X[:, feature] <= threshold
gini = calculate_gini(y[left_idx], y[~left_idx])
if gini < best_gini:
best_gini = gini
best_feature = feature
best_threshold = threshold
# 递归分裂
left_idx = X[:, best_feature] <= best_threshold
left = build_tree(X[left_idx], y[left_idx], max_depth-1)
right = build_tree(X[~left_idx], y[~left_idx], max_depth-1)
return Node(best_feature, best_threshold, left, right)
3. 可视化决策边界(需 matplotlib)
def plot_decision_boundary(model, X, y):
x_min, x_max = X[:, 0].min()-1, X[:, 0].max()+1
y_min, y_max = X[:, 1].min()-1, X[:, 1].max()+1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
np.arange(y_min, y_max, 0.01))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
plt.contourf(xx, yy, Z.reshape(xx.shape), alpha=0.3)
plt.scatter(X[:, 0], X[:, 1], c=y, edgecolor='k')
sklearn 的优化实现
官方实现采用以下加速策略:
1. 预排序技术:对连续特征提前排序,加速最优分割点搜索
2. 并行计算 :n_jobs 参数支持多线程处理特征
3. 内存优化 :使用presort=False 时改为即时排序
关键参数示例:
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(
criterion='gini', # 使用基尼系数
max_depth=5, # 控制过拟合
min_samples_split=10, # 节点最小样本数
min_impurity_decrease=0.01 # 分裂最小增益
)
生产环境注意事项
过拟合问题
- 预剪枝:设置
max_depth/min_samples_leaf - 后剪枝 :通过
ccp_alpha参数进行代价复杂度剪枝
类别不平衡
- 使用
class_weight='balanced' - 过采样少数类 (SMOTE) 或欠采样多数类
高基数特征
- 对类别型特征采用目标编码(Target Encoding)
- 使用
max_categories限制 one-hot 编码维度
思考题
- 连续值特征处理:可以采用 分箱离散化 或动态分割点 策略
- 随机森林通过 Bootstrap 采样 和特征随机子集 降低方差
总结
手动实现 CART 决策树有助于深入理解其分裂逻辑,但生产环境建议使用优化库。关键要掌握基尼系数的计算过程、递归终止条件以及剪枝策略的应用场景。
