EH's Blog

决策树算法(分类 + 回归)#

一、决策树到底在干嘛?#

决策树就是一种”不断问问题”的模型。它长成树的样子,每个分叉点问一个问题,顺着答案往下走,最后走到叶子得到一个结论。

先看一个生活化的例子——周末去不去打球:

           今天天气如何?
          /      |      \
       晴天     阴天      雨天
        /        |          \
   风大吗?    → 去!      湿度高吗?
    /    \                  /      \
 风大→不去  风小→去      湿度低→去  湿度高→不去
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)用来量化一个集合有多”乱”。公式:

H(D)=−∑k=1Kpklog⁡2pkH(D) = -\sum_{k=1}^{K} p_k \log_2 p_k

其中 pkp_k 是第 kk 类样本占的比例,KK 是类别数。

  • 熵 =0= 0:完全纯(全是一种)
  • 熵越大:越乱(各类掺在一起)

用我们的数据算一遍:14 天里 9 个 Yes、5 个 No,所以 pYes=914p_{Yes} = \frac{9}{14},pNo=514p_{No} = \frac{5}{14}:

H(D)=−914log⁡2914−514log⁡2514=−0.643×(−0.637)−0.357×(−1.485)≈0.940H(D) = -\frac{9}{14}\log_2\frac{9}{14} - \frac{5}{14}\log_2\frac{5}{14} = -0.643 \times (-0.637) - 0.357 \times (-1.485) \approx 0.940

这个 0.940 就是整个数据集的”混乱度”,我们记作起点。

3.2 信息增益:每个特征能”降低多少混乱”#

如果按某个特征划分后,每一堆内部都变”纯”了(熵下降),说明这个特征问得好。下降得越多越值得先问。定义信息增益:

Gain(D,a)=H(D)−∑v=1V∣Dv∣∣D∣H(Dv)Gain(D, a) = H(D) - \sum_{v=1}^{V} \frac{|D^v|}{|D|} H(D^v)

就是:原来的熵 − 划分后各堆熵的加权平均。我们要挑增益最大的特征。

下面完整算一遍”按天气 Outlook 划分”的增益:

第一步:按天气分成三堆:

天气包含的天去/不去
晴 SunnyD1,D2,D8,D9,D11(5天)Yes 2,No 3
阴 OvercastD3,D7,D12,D13(4天)Yes 4,No 0
雨 RainD4,D5,D6,D10,D14(5天)Yes 3,No 2

第二步:算每堆的熵:

  • 晴:H=−25log⁡225−35log⁡235≈0.971H = -\frac{2}{5}\log_2\frac{2}{5} - \frac{3}{5}\log_2\frac{3}{5} \approx 0.971
  • 阴:H=−44log⁡244−0=0H = -\frac{4}{4}\log_2\frac{4}{4} - 0 = 0(全是 Yes,完全纯!)
  • 雨:H=−35log⁡235−25log⁡225≈0.971H = -\frac{3}{5}\log_2\frac{3}{5} - \frac{2}{5}\log_2\frac{2}{5} \approx 0.971

第三步:加权平均(每堆按比例加权):

514×0.971+414×0+514×0.971≈0.694\frac{5}{14}\times 0.971 + \frac{4}{14}\times 0 + \frac{5}{14}\times 0.971 \approx 0.694

第四步:信息增益 = 原来的熵 − 加权平均熵:

Gain=0.940−0.694=0.246Gain = 0.940 - 0.694 = 0.246

同理可以算出按”温度""湿度""风”划分的增益,都小于 0.246。所以第一个问题应该问天气!

3.3 基尼指数(CART 分类树用的标准)#

除了信息熵,CART 分类树用基尼指数,同样衡量”纯不纯”,而且不用算 log,更省事:

Gini(D)=1−∑k=1Kpk2Gini(D) = 1 - \sum_{k=1}^{K} p_k^2

还是越小越纯。例:14 天数据集 Gini(D)=1−(914)2−(514)2≈0.459Gini(D) = 1 - (\frac{9}{14})^2 - (\frac{5}{14})^2 \approx 0.459。

划分时选”划分后基尼加权和最小”的特征。


四、递归建树:一棵树是怎么长出来的#

有了”选特征”的规则,建树就是递归的过程:

  1. 在当前数据里,算每个特征的信息增益
  2. 选增益最大的特征作为判断点(节点)
  3. 按这个特征的每个取值,把数据分成几堆
  4. 对每一堆,重复第 1~3 步(递归!)
  5. 直到:某堆只剩一种类别(纯了),或者没有特征可用,或者达到停止条件 → 把当前堆变成叶子节点

用打球数据演示根节点之后的部分:

                     天气 = ?          ← 信息增益最大,当根
               /        |        \
            晴        阴         雨
         (5天)     (4天)      (5天)
            熵0.971   熵0(纯)    熵0.971
           继续分↓    叶子=去!     继续分↓
        风大吗?                   湿度高吗?
        /      \                 /        \
     大→不去   小→去           高→不去    正常→去
plaintext

这样整棵树就长出来了,以后来新的一天,顺着问题走就能预测”去不去”。


五、回归决策树:预测连续数值#

上面是分类(输出”去/不去”这种类别)。如果输出是连续数值(比如房价、成绩、温度),就用回归树。

5.1 回归树叶子存的是”一个数”#

回归树的叶子节点不再是”类别”,而是一个数值——通常是这个叶子内所有样本的平均值。

5.2 划分标准:均方误差 MSE#

分类树看”纯不纯”,回归树看”预测准不准”。定义均方误差(MSE),衡量”用均值 yˉ\bar{y} 预测所有样本 yiy_i 的误差”:

MSE=1n∑i=1n(yi−yˉ)2MSE = \frac{1}{n} \sum_{i=1}^{n} (y_i - \bar{y})^2

越小说明这一堆里的数越接近,越好。

划分时,试遍所有特征的每个阈值 tt,找让”划分后两堆 MSE 之和最小”的分法:

min⁡a,t[∑xi∈R1(yi−c1)2+∑xi∈R2(yi−c2)2]\min_{a, t} \left[ \sum_{x_i \in R_1} (y_i - c_1)^2 + \sum_{x_i \in R_2} (y_i - c_2)^2 \right]

其中 c1,c2c_1, c_2 是 R1,R2R_1, R_2 两堆的均值。

5.3 直观例子:预测房价#

假设有 6 套房,特征”面积”,目标”价格”:

面积(m²)4050608090100
价格(万)8090110200220250

想找一个分界点 tt,让两边的价格都”尽量聚集”(MSE 小):

  • 试 t=60t = 60:左边 {40,50,60}→价格{80,90,110},均值 93;右边 {80,90,100}→价格{200,220,250},均值 223。两堆都很”整齐”,MSE 很小 ✅
  • 试 t=90t = 90:左边 5 个数价格混杂(80~250),均值很大,MSE 很大 ❌

显然选 t=60t = 60 左右。于是:

        面积 < 60 ?
        /        \
   左边均值93万   右边均值223万
plaintext

以后来一套 70m² 的房子,走右边,预测 223 万。这就是回归树:把空间切块,每块填一个均值。


六、过拟合与剪枝#

6.1 什么是过拟合?#

决策树可以无限往下分,直到每个叶子只有一个样本。这样训练集 100% 正确,但遇到新数据就乱猜——这就是过拟合(“把答案背下来”)。

打个比方:一个学生把练习册答案全背下来了,换一套卷子就不会做。

6.2 两种剪枝#

方式做法优点
预剪枝建树时就限制:最大深度 max_depth、叶子最少样本 min_samples_leaf 等简单高效,最常用
后剪枝树建好后,从下往上,把”对验证集没帮助”的子树砍掉更优,但耗时

实操建议:直接用 sklearn 的 max_depth(比如 35)和 min_samples_leaf(比如 25)就能得到不错的效果。


七、完整可运行的 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
决策树算法
https://ehnotgod.site/zh/blog/%E5%86%B3%E7%AD%96%E6%A0%91%E7%AE%97%E6%B3%95
Author EH
Published at August 9, 2026