决策树算法(分类 + 回归)#
一、决策树到底在干嘛?#
决策树就是一种”不断问问题”的模型。它长成树的样子,每个分叉点问一个问题,顺着答案往下走,最后走到叶子得到一个结论。
先看一个生活化的例子——周末去不去打球:
今天天气如何?
/ | \
晴天 阴天 雨天
/ | \
风大吗? → 去! 湿度高吗?
/ \ / \
风大→不去 风小→去 湿度低→去 湿度高→不去plaintext核心问题:这些”问题”是怎么选出来的?凭什么先问”天气”而不是先问”风大不大”? 这就是决策树算法的精髓——它从数据里”学”出最该先问哪个问题。
二、拿真实数据,手动建棵树#
决策树不是凭空设计的,它要从带标签的历史数据里学。下面是我们”打球数据集”,一共 14 天记录(4 个特征 + 1 个结果):
| 天数 | 天气 Outlook | 温度 Temperature | 湿度 Humidity | 风 Wind | 是否打球 Play |
|---|---|---|---|---|---|
| D1 | 晴 Sunny | 热 Hot | 高 High | 小 Weak | 否 No |
| D2 | 晴 Sunny | 热 Hot | 高 High | 大 Strong | 否 No |
| D3 | 阴 Overcast | 热 Hot | 高 High | 小 Weak | 是 Yes |
| D4 | 雨 Rain | 温和 Mild | 高 High | 小 Weak | 是 Yes |
| D5 | 雨 Rain | 凉 Cool | 正常 Normal | 小 Weak | 是 Yes |
| D6 | 雨 Rain | 凉 Cool | 正常 Normal | 大 Strong | 否 No |
| D7 | 阴 Overcast | 凉 Cool | 正常 Normal | 大 Strong | 是 Yes |
| D8 | 晴 Sunny | 温和 Mild | 高 High | 小 Weak | 否 No |
| D9 | 晴 Sunny | 凉 Cool | 正常 Normal | 小 Weak | 是 Yes |
| D10 | 雨 Rain | 温和 Mild | 正常 Normal | 小 Weak | 是 Yes |
| D11 | 晴 Sunny | 温和 Mild | 正常 Normal | 大 Strong | 是 Yes |
| D12 | 阴 Overcast | 热 Hot | 高 High | 大 Strong | 是 Yes |
| D13 | 阴 Overcast | 热 Hot | 正常 Normal | 小 Weak | 是 Yes |
| D14 | 雨 Rain | 温和 Mild | 高 High | 大 Strong | 否 No |
统计一下结果:14 天里,9 天去打球(Yes),5 天不去(No)。
我们要做的:选一个特征当”根问题”。直觉上,我们希望问完之后,每一堆里尽可能只含一种答案(全是”去”或全是”不去”),这样判断就”干净”。
那怎么量化”干净不干净”?请看下面的信息熵。
三、分类决策树:怎么选特征(手把手计算)#
3.1 信息熵:衡量”乱不乱”#
信息熵(Entropy)用来量化一个集合有多”乱”。公式:
其中 是第 类样本占的比例, 是类别数。
- 熵 :完全纯(全是一种)
- 熵越大:越乱(各类掺在一起)
用我们的数据算一遍:14 天里 9 个 Yes、5 个 No,所以 ,:
这个 0.940 就是整个数据集的”混乱度”,我们记作起点。
3.2 信息增益:每个特征能”降低多少混乱”#
如果按某个特征划分后,每一堆内部都变”纯”了(熵下降),说明这个特征问得好。下降得越多越值得先问。定义信息增益:
就是:原来的熵 − 划分后各堆熵的加权平均。我们要挑增益最大的特征。
下面完整算一遍”按天气 Outlook 划分”的增益:
第一步:按天气分成三堆:
| 天气 | 包含的天 | 去/不去 |
|---|---|---|
| 晴 Sunny | D1,D2,D8,D9,D11(5天) | Yes 2,No 3 |
| 阴 Overcast | D3,D7,D12,D13(4天) | Yes 4,No 0 |
| 雨 Rain | D4,D5,D6,D10,D14(5天) | Yes 3,No 2 |
第二步:算每堆的熵:
- 晴:
- 阴:(全是 Yes,完全纯!)
- 雨:
第三步:加权平均(每堆按比例加权):
第四步:信息增益 = 原来的熵 − 加权平均熵:
同理可以算出按”温度""湿度""风”划分的增益,都小于 0.246。所以第一个问题应该问天气!
3.3 基尼指数(CART 分类树用的标准)#
除了信息熵,CART 分类树用基尼指数,同样衡量”纯不纯”,而且不用算 log,更省事:
还是越小越纯。例:14 天数据集 。
划分时选”划分后基尼加权和最小”的特征。
四、递归建树:一棵树是怎么长出来的#
有了”选特征”的规则,建树就是递归的过程:
- 在当前数据里,算每个特征的信息增益
- 选增益最大的特征作为判断点(节点)
- 按这个特征的每个取值,把数据分成几堆
- 对每一堆,重复第 1~3 步(递归!)
- 直到:某堆只剩一种类别(纯了),或者没有特征可用,或者达到停止条件 → 把当前堆变成叶子节点
用打球数据演示根节点之后的部分:
天气 = ? ← 信息增益最大,当根
/ | \
晴 阴 雨
(5天) (4天) (5天)
熵0.971 熵0(纯) 熵0.971
继续分↓ 叶子=去! 继续分↓
风大吗? 湿度高吗?
/ \ / \
大→不去 小→去 高→不去 正常→去plaintext这样整棵树就长出来了,以后来新的一天,顺着问题走就能预测”去不去”。
五、回归决策树:预测连续数值#
上面是分类(输出”去/不去”这种类别)。如果输出是连续数值(比如房价、成绩、温度),就用回归树。
5.1 回归树叶子存的是”一个数”#
回归树的叶子节点不再是”类别”,而是一个数值——通常是这个叶子内所有样本的平均值。
5.2 划分标准:均方误差 MSE#
分类树看”纯不纯”,回归树看”预测准不准”。定义均方误差(MSE),衡量”用均值 预测所有样本 的误差”:
越小说明这一堆里的数越接近,越好。
划分时,试遍所有特征的每个阈值 ,找让”划分后两堆 MSE 之和最小”的分法:
其中 是 两堆的均值。
5.3 直观例子:预测房价#
假设有 6 套房,特征”面积”,目标”价格”:
| 面积(m²) | 40 | 50 | 60 | 80 | 90 | 100 |
|---|---|---|---|---|---|---|
| 价格(万) | 80 | 90 | 110 | 200 | 220 | 250 |
想找一个分界点 ,让两边的价格都”尽量聚集”(MSE 小):
- 试 :左边 {40,50,60}→价格{80,90,110},均值 93;右边 {80,90,100}→价格{200,220,250},均值 223。两堆都很”整齐”,MSE 很小 ✅
- 试 :左边 5 个数价格混杂(80~250),均值很大,MSE 很大 ❌
显然选 左右。于是:
面积 < 60 ?
/ \
左边均值93万 右边均值223万plaintext以后来一套 70m² 的房子,走右边,预测 223 万。这就是回归树:把空间切块,每块填一个均值。
六、过拟合与剪枝#
6.1 什么是过拟合?#
决策树可以无限往下分,直到每个叶子只有一个样本。这样训练集 100% 正确,但遇到新数据就乱猜——这就是过拟合(“把答案背下来”)。
打个比方:一个学生把练习册答案全背下来了,换一套卷子就不会做。
6.2 两种剪枝#
| 方式 | 做法 | 优点 |
|---|---|---|
| 预剪枝 | 建树时就限制:最大深度 max_depth、叶子最少样本 min_samples_leaf 等 | 简单高效,最常用 |
| 后剪枝 | 树建好后,从下往上,把”对验证集没帮助”的子树砍掉 | 更优,但耗时 |
实操建议:直接用 sklearn 的
max_depth(比如 35)和5)就能得到不错的效果。min_samples_leaf(比如 2
七、完整可运行的 Python 代码#
下面用 sklearn 直接跑上面的”打球数据集”(分类),再演示回归:
import numpy as np
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor, plot_tree
import matplotlib.pyplot as plt
# ---------- 分类:打球数据集 ----------
# 特征:天气、温度、湿度、风(先转成数字)
X = np.array([
[0, 0, 0, 0], [0, 0, 0, 1], [1, 0, 0, 0], [2, 1, 0, 0],
[2, 2, 1, 0], [2, 2, 1, 1], [1, 2, 1, 1], [0, 1, 0, 0],
[0, 2, 1, 0], [2, 1, 1, 0], [0, 1, 1, 1], [1, 0, 0, 1],
[1, 0, 1, 0], [2, 1, 0, 1],
])
# 0=晴/热/高/小, 1=阴/温和/正常/大, 2=雨/凉
y = np.array([0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0]) # 0=不去 1=去
clf = DecisionTreeClassifier(max_depth=3, random_state=0)
clf.fit(X, y)
print("训练集准确率:", clf.score(X, y))
# 可视化这棵树
plt.figure(figsize=(10, 6))
plot_tree(clf, filled=True, feature_names=["天气", "温度", "湿度", "风"],
class_names=["不去", "去"])
plt.show()
# ---------- 回归:房价 ----------
Xh = np.array([[40], [50], [60], [80], [90], [100]])
yh = np.array([80, 90, 110, 200, 220, 250])
reg = DecisionTreeRegressor(max_depth=2, random_state=0)
reg.fit(Xh, yh)
print("预测 70m² 房价:", reg.predict([[70]])) # 会落在均值 223 万那个叶子python