决策树:像做选择题一样理解分类与回归
决策树(Decision Tree)是一种非常符合人类直觉的机器学习算法。它的预测过程就像做选择题:先问一个问题,根据答案走到下一步,再继续问问题,直到得到最终结论。
例如判断一个学生考试是否通过,可以问:
- 学习时间是否大于 3 小时?
- 睡眠时间是否大于 7 小时?
- 是否完成复习?
最后走到某个叶子节点,得到“通过”或“不通过”的预测结果。
1. 决策树的基本思想
决策树的核心思想是:不断选择一个最合适的特征,把数据集切分得越来越“纯”。
这里的“纯”可以理解为:一个节点里的样本类别越统一,就越纯。
例如一个节点里有 10 个样本:
- 如果 10 个都是“通过”,这个节点非常纯。
- 如果 5 个“通过”、5 个“不通过”,这个节点就很混乱。
训练决策树时,算法会不断寻找最佳划分条件,例如:
学习时间 >= 3 小时?
如果这个问题能把“通过”和“不通过”分得更开,就说明它是一个不错的划分。
2. 决策树由哪些部分组成
一棵决策树通常包含三类节点:
| 结构 | 含义 |
|---|---|
| 根节点 | 整棵树的起点,包含全部训练数据 |
| 内部节点 | 一个判断条件,例如“年龄是否大于 18” |
| 叶子节点 | 最终预测结果,例如“通过”或“不通过” |
从根节点走到叶子节点,就是模型预测一个样本的过程。
例如:
学习时间 >= 3 小时?
是 -> 完成复习?
是 -> 预测:通过
否 -> 预测:不通过
否 -> 睡眠时间 >= 7 小时?
是 -> 预测:通过
否 -> 预测:不通过
这种结构的好处是非常容易解释。相比逻辑回归、支持向量机等模型,决策树更像是一套可读的判断规则。
3. 如何判断哪个特征更适合划分
决策树训练时最关键的问题是:
当前节点应该用哪个特征来划分?
常见的划分标准包括:
- 信息增益
- 信息增益率
- 基尼指数
- 均方误差
其中分类树常用信息熵、信息增益和基尼指数;回归树常用均方误差。
4. 信息熵:衡量混乱程度
信息熵(Entropy)可以用来衡量一个数据集的混乱程度。
对于分类问题,信息熵定义为:
$$
H(D) = -\sum_{k=1}^{K} p_k \log_2 p_k
$$
其中:
- $D$ 表示当前数据集。
- $K$ 表示类别数量。
- $p_k$ 表示第 $k$ 类样本所占比例。
如果一个节点里所有样本都属于同一类,那么熵为 0,说明完全不混乱。
如果不同类别各占一半,熵会比较大,说明节点比较混乱。
以二分类为例:
| 通过 | 不通过 | 熵 |
|---|---|---|
| 10 | 0 | 0 |
| 8 | 2 | 较小 |
| 5 | 5 | 较大 |
决策树希望每次划分后,子节点的熵尽可能小。
5. 信息增益:划分后混乱减少了多少
信息增益(Information Gain)表示划分前后信息熵减少了多少。
假设用特征 $A$ 对数据集 $D$ 进行划分,信息增益为:
$$
Gain(D, A) = H(D) - \sum_{v=1}^{V}\frac{|D_v|}{|D|}H(D_v)
$$
其中:
- $H(D)$ 是划分前的熵。
- $D_v$ 是按特征 $A$ 划分后的第 $v$ 个子集。
- $\frac{|D_v|}{|D|}$ 表示子集占原数据集的比例。
信息增益越大,说明这个特征让数据变得越清晰。
ID3 决策树算法就是优先选择信息增益最大的特征进行划分。
6. 基尼指数:衡量随机错分概率
CART 决策树常用基尼指数(Gini Index)作为分类划分标准。
基尼指数定义为:
$$
Gini(D) = 1 - \sum_{k=1}^{K}p_k^2
$$
它可以理解为:从当前节点随机抽一个样本,再随机按类别比例猜它的类别,猜错的概率大不大。
如果节点越纯,基尼指数越小。
二分类下:
- 如果一个节点全是正类,$Gini=0$。
- 如果正负类各占一半,$Gini=0.5$。
使用特征 $A$ 划分后的基尼指数为:
$$
Gini(D, A) = \sum_{v=1}^{V}\frac{|D_v|}{|D|}Gini(D_v)
$$
训练时会选择让划分后基尼指数最小的特征。
7. 回归树怎么划分
决策树不仅能做分类,也能做回归。
分类树的叶子节点输出类别;回归树的叶子节点输出数值。
例如预测房价时,一个叶子节点中可能有多个训练样本:
90 万、95 万、100 万
那么这个叶子节点的预测值通常取平均值:
$$
\hat{y} = \frac{1}{m}\sum_{i=1}^{m}y^{(i)}
$$
回归树在选择划分时,通常希望划分后的平方误差最小:
$$
MSE = \frac{1}{m}\sum_{i=1}^{m}(y^{(i)}-\hat{y})^2
$$
所以分类树更关心“类别是否更纯”,回归树更关心“数值是否更接近”。
8. 决策树为什么容易过拟合
决策树很容易把训练数据记得太细。
如果不加限制,它可以一直划分,直到每个叶子节点几乎只剩一个样本。这样训练集准确率可能非常高,但对新数据的泛化能力会下降。
这就是过拟合。
常见控制方法包括:
- 限制树的最大深度
max_depth。 - 限制叶子节点最少样本数
min_samples_leaf。 - 限制内部节点继续划分所需的最少样本数
min_samples_split。 - 剪枝,去掉贡献不大的分支。
可以把这些参数理解为“不要让树长得太复杂”。
9. Python 手写:计算基尼指数并选择划分
下面不急着手写完整决策树,而是先手写决策树最核心的一步:计算某个划分是否让数据更纯。
from collections import Counter
def gini(labels):
# 对应模块:计算当前节点的基尼指数,值越小表示类别越纯
total = len(labels)
counts = Counter(labels)
impurity = 1.0
for count in counts.values():
prob = count / total
impurity -= prob ** 2
return impurity
def split_dataset(X, y, feature_index, threshold):
# 对应模块:按照某个特征阈值把数据切成左右两个子节点
left_y = []
right_y = []
for sample, label in zip(X, y):
if sample[feature_index] <= threshold:
left_y.append(label)
else:
right_y.append(label)
return left_y, right_y
def weighted_gini(left_y, right_y):
# 对应模块:计算划分后的加权基尼指数
total = len(left_y) + len(right_y)
left_weight = len(left_y) / total
right_weight = len(right_y) / total
return left_weight * gini(left_y) + right_weight * gini(right_y)
X = [
[2, 6], # 学习 2 小时,睡眠 6 小时
[3, 7],
[4, 6],
[5, 8],
[6, 7],
]
y = ["不通过", "不通过", "通过", "通过", "通过"]
# 尝试用第 0 个特征,也就是学习时间,以 3.5 为阈值划分
left_y, right_y = split_dataset(X, y, feature_index=0, threshold=3.5)
print("左节点标签:", left_y)
print("右节点标签:", right_y)
print("划分前基尼指数:", gini(y))
print("划分后加权基尼指数:", weighted_gini(left_y, right_y))
这段代码展示了决策树选择划分的核心逻辑:
- 先选一个特征和阈值。
- 按条件把样本分成左右两组。
- 分别计算左右节点的纯度。
- 如果划分后的加权基尼指数更小,说明这个划分更好。
完整决策树就是不断重复这个过程,直到满足停止条件。
10. 使用 sklearn 训练决策树分类器
实际项目中可以直接使用 sklearn.tree.DecisionTreeClassifier。
import numpy as np
from sklearn.tree import DecisionTreeClassifier, export_text
# 特征:学习时间、睡眠时间
X = np.array([
[2, 6],
[3, 7],
[4, 6],
[5, 8],
[6, 7],
[1, 5],
[7, 8],
])
y = np.array(["不通过", "不通过", "通过", "通过", "通过", "不通过", "通过"])
# max_depth 限制树的深度,避免树过于复杂
model = DecisionTreeClassifier(
criterion="gini",
max_depth=2,
random_state=42,
)
model.fit(X, y)
new_student = np.array([[4, 7]])
prediction = model.predict(new_student)
print("预测结果:", prediction[0])
# 打印树的规则,方便理解模型是怎么做判断的
rules = export_text(
model,
feature_names=["学习时间", "睡眠时间"],
)
print(rules)
可能输出类似:
|--- 学习时间 <= 3.50
| |--- class: 不通过
|--- 学习时间 > 3.50
| |--- class: 通过
这也是决策树的一个重要优点:模型结果相对容易解释。
11. 决策树的优缺点
优点
- 可解释性强,预测过程像一组规则。
- 可以处理非线性关系。
- 对特征缩放不敏感,通常不需要标准化。
- 既能做分类,也能做回归。
- 可以处理数值特征和类别特征。
缺点
- 容易过拟合。
- 对数据扰动比较敏感,数据稍微变化,树结构可能明显改变。
- 单棵树的泛化能力有限。
- 贪心划分不一定能找到全局最优树。
这些缺点也解释了为什么后续会出现随机森林、梯度提升树、XGBoost、LightGBM 等集成模型。它们本质上都是在决策树基础上进行改进。
12. 实践建议
使用决策树时,可以重点关注以下几点:
- 先限制
max_depth,不要让树长得太深。 - 使用交叉验证选择
max_depth、min_samples_leaf等参数。 - 如果数据噪声较多,适当增大
min_samples_leaf。 - 如果单棵树效果不稳定,可以尝试随机森林。
- 如果追求更强的预测性能,可以学习梯度提升树相关算法。
13. 总结
决策树的核心思想很简单:不断提出问题,把数据切分得越来越纯。
对于分类任务,它常用信息熵、信息增益或基尼指数来判断划分好坏;对于回归任务,它通常使用均方误差来选择划分。
理解决策树时,可以抓住三句话:
- 节点是在问问题。
- 分支是问题的不同答案。
- 叶子节点是最终预测结果。
决策树本身简单直观,但它也是很多强大模型的基础。理解决策树之后,再学习随机森林、GBDT、XGBoost 等算法时,会更容易抓住它们的核心思想。
