共计 2830 个字符,预计需要花费 8 分钟才能阅读完成。
基尼系数与熵的本质差异
在构建决策树时,我们需要一个指标来衡量数据的不纯度(Impurity)。最常用的两个指标是基尼系数(Gini Index)和熵(Entropy)。

-
基尼系数公式 :
Gini(p) = 1 - Σ(p_i)^2其中 p_i 是第 i 类样本的比例。基尼系数计算的是随机抽取两个样本,它们类别不一致的概率。
-
熵的公式 :
Entropy(p) = -Σp_i * log2(p_i)熵衡量的是系统的不确定性,值越大表示混乱程度越高。
-
关键区别 :
- 计算效率:基尼系数不涉及对数运算,计算速度更快
- 结果差异:两者在大多数情况下选择的分裂点相似,但熵对不纯度变化更敏感
- 实际应用:sklearn 默认使用基尼系数,因其计算开销更小
CART 决策树构建全流程
1. 特征选择与分裂阈值计算
决策树的核心是递归地选择最优特征和分裂点。对于每个特征,我们需要:
- 对连续特征:
- 排序所有可能的分割点
- 计算每个分割点的基尼指数增益
-
选择增益最大的分割点
-
对分类特征:
- 考虑所有可能的子集划分
- 计算每种划分的基尼指数
- 选择最优划分方式
基尼增益计算公式 :
Δ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. 连续特征处理技巧
- 提前排序特征值,加速分裂点搜索
- 使用分位数作为候选分割点,减少计算量
- 对大规模数据采用近似算法(如直方图近似)
常见陷阱与解决方案
- 过拟合表现 :
- 训练集准确率 100% 但测试集差
-
树深度过大,节点过多
-
解决方法 :
- 增加 min_samples_split 参数
- 使用交叉验证选择最优 max_depth
-
添加正则化项
-
类别不平衡处理 :
- 使用 class_weight 参数
- 采用平衡准确率(balanced accuracy)作为评估指标
- 对少数类样本进行过采样
延伸思考
- 如何基于 CART 树构建随机森林?关键在于 bootstrap 采样和特征随机选择
- 当遇到类别极度不平衡时,可以考虑调整分裂标准(如使用信息增益比)
- CART 与 GBDT 的核心差异:GBDT 是加法模型,通过梯度提升迭代优化;CART 是单棵树,通过递归分裂构建
结语
通过手动实现 CART 算法,我们能更深入理解决策树的工作原理。虽然实际应用中我们会直接使用 sklearn 等库,但理解底层计算逻辑对于调参和问题诊断至关重要。建议读者尝试扩展这个基础实现,比如加入剪枝功能或支持回归任务,这将大大加深对树模型的理解。
正文完
