1. 引言
在机器学习中,树模型是一类非常直观且强大的监督学习方法。与线性回归、逻辑回归、支持向量机、神经网络等参数化模型不同,决策树不依赖预定义的参数化假设空间,而是直接在树结构函数空间中搜索优质的模型结构。本文基于张伟楠老师在《机器学习》课程第6节的讲义,从泛函空间优化的视角出发,详细讲解决策树(ID3、CART)、集成学习的基本概念,以及Bagging算法的工作原理和有效性分析。
2. 泛函空间优化与树模型
2.1 从参数空间到泛函空间
监督学习的本质是函数逼近:给定特征空间 𝒳 和标签空间 𝒴,存在一个未知的目标函数 f: 𝒳 → 𝒴。我们的任务是根据训练数据 {(x⁽¹⁾, y⁽¹⁾), ..., (x⁽ⁿ⁾, y⁽ⁿ⁾)} 在假设集合 H = {h | h: 𝒳 → 𝒴} 中找到一个最佳逼近假设 h。这个过程就是泛函空间优化,因为我们搜索的是函数本身,而不仅仅是函数中的几个参数。
2.2 树模型作为假设
在泛函空间中,每个假设 h 可以是一棵决策树。树模型由两部分组成:
- 中间节点:用于分割数据(分裂节点);
- 叶子节点:用于给出预测值或类别标签。
无论是连续特征还是离散/类别特征,决策树都能递归地将特征空间划分成与坐标轴平行的(超)矩形区域,每个区域对应一个叶子节点,并用一个标签或标签的概率分布来描述。
决策树的研究可以追溯到20世纪60年代Hunt等人的认知建模,70年代末Quinlan的ID3算法,同时期Breiman等人提出的CART算法,以及后来的C4.5改进版。今天,scikit-learn(Python)和Weka(Java)等工具包中已广泛支持这些算法。
3. 决策树核心概念
3.1 节点分裂的关键问题
构造一棵决策树需要回答三个核心问题:
- 如何选择分裂节点的条件? --- 即选择哪个特征以及在该特征上如何切分数据。
- 如何做出预测? --- 到达叶子节点后输出什么。
- 如何决定树结构? --- 何时停止分裂,如何防止过拟合。
对于分裂条件,我们希望选择"分类能力更强"的特征,即能够使子节点纯度更高、信息增益更大的特征。
3.2 信息论基础
香农熵 衡量随机变量不确定性的平均信息量。对于离散随机变量 X 取值 xᵢ 的概率 P(X=xᵢ) = pᵢ,其熵定义为:
H(X) = -∑ᵢ pᵢ log pᵢ
二项分布的熵曲线呈倒U型,当两类概率相等时熵最大(不确定性最高),当某一类概率为1时熵为0(完全确定)。
交叉熵 用于度量两个概率分布之间的差异:
H(p, q) = -∑ᵢ pᵢ log qᵢ
KL散度(相对熵)也是一种分布差异的非对称度量:
D_KL(p‖q) = H(p, q) - H(p)
这些概念与逻辑回归的交叉熵损失函数紧密相关,这里不再展开。
3.3 条件熵与信息增益
给定特征 Y,我们可以计算目标变量 X 在条件 Y 下的条件熵:
H(X | Y = v) = -∑ᵢ P(X = i | Y = v) log P(X = i | Y = v)
H(X | Y) = ∑ᵥ P(Y = v) H(X | Y = v)
信息增益 表示通过特征 Y 的划分,目标变量 X 不确定性的减少量:
IG(X, Y) = H(X) - H(X | Y)
信息增益越大,说明该特征对减少目标变量的不确定性贡献越大,因此更适合作为分裂节点。
4. ID3决策树算法
4.1 ID3的构建流程
ID3(Iterative Dichotomiser 3)是一种经典的自顶向下贪心决策树算法,专门处理离散类别型数据。其基本框架如下:
- 从包含所有训练数据的根节点开始;
- 对当前节点的剩余数据集,计算所有候选特征的信息增益;
- 选择信息增益最大的特征作为分裂特征;
- 根据该特征的每个取值生成分支,将数据划分到对应子节点;
- 对每个子节点递归执行步骤2-4,直到满足停止条件(如所有样本同属一类、无更多特征可用、或信息增益
下面用流程图直观展示这一递归构建过程:
flowchart TD
A["开始:所有数据在根节点"] --> B["计算当前节点所有特征的信息增益"]
B --> C{"是否存在可分裂特征且\n信息增益 > 0?"}
C -- "是" --> D["选择信息增益最大的特征作为分裂特征"]
D --> E["按该特征的每个取值生成分支,划分数据到子节点"]
E --> F["对每个子节点递归执行步骤 B-E"]
C -- "否" --> G["将该节点标记为叶子节点"]
G --> H["返回类别(多数类)"]
F --> I{"是否满足停止条件?"}
I -- "是" --> G
I -- "否" --> B
上图以 ID3 算法为例,展示了自顶向下贪心构建决策树的完整流程:从根节点开始,不断选择信息增益最大的特征进行分裂,直到满足停止条件(所有样本同属一类、无更多特征可用或信息增益为零),最后给出叶子节点的类别标签。
在实际实现中,除图中所示的自然停止条件外,为了控制树的复杂度和防止过拟合,通常会设置额外的预剪枝条件。常见的停止标准包括:
- 节点样本数阈值 :当当前节点的样本数小于设定值(如
min_samples_split)时,不再分裂,直接标记为叶子节点。这可以防止树在训练数据稀疏的区域过度生长。 - 信息增益阈值:即使存在可分裂特征,当最大信息增益低于某个下限(如 0.001)时,分裂带来的不确定性减少微乎其微,停止分裂有助于避免学习噪声。
- 最大深度限制 :限制树的最大深度(
max_depth),强制在到达指定深度后停止递归,防止树变得过深而记忆训练样本的细节。 - 叶子节点最小样本数 :分裂后任一子节点的样本数若低于阈值(
min_samples_leaf),则放弃分裂。这确保每个叶子节点都有足够的统计支持,避免极端划分。
这些条件协同作用,在树生长过程中主动"刹车",避免模型将训练数据中的随机噪声也编码为分支规则。通过提前终止分裂,降低了决策树的方差,使模型在新数据上的泛化能力更强,这正是防止过拟合的核心机制。
为0)。
ID3保证每个特征在一条路径上最多出现一次,因此不会出现同一特征在一条路径上重复分裂的情况。
4.2 信息增益率的改进
信息增益倾向于选择取值数目较多的特征,因为这类特征天然会将数据分得更细,即便这种划分对真实模式没有帮助。为克服这一缺陷,C4.5算法引入了 信息增益率:
IR(X, Y) = IG(X, Y) / H_Y(X)
其中 H_Y(X) 是特征 Y 将数据分开的分裂信息熵,用于惩罚取值过多的特征:
H_Y(X) = -∑ᵥ (|X_y=v| / |X|) log (|X_y=v| / |X|)
通过除以分裂熵,信息增益率可以更公平地比较不同取值数量的特征。
4.3 过拟合与正则化
由于决策树可以一直生长直至每个叶子节点只包含一个样本,这样能够完美拟合任何有限训练数据,但显然会严重过拟合。树模型的损失函数通常定义如下:
C(T) = ∑ₜ Nₜ Hₜ(T)
其中 Nₜ 是叶子节点 t 的样本数,Hₜ(T) 是该节点的经验熵。为了控制树的复杂度,引入正则化项:
C(T) = ∑ₜ Nₜ Hₜ(T) + λ|T|
|T| 是叶子节点数目,λ 是正则化超参数。当一个节点的分裂所带来的损失减少不足以抵消新增叶子节点带来的惩罚时,就应该停止分裂。
5. CART决策树算法
5.1 CART的特点
分类回归树(CART)由Breiman在1984年提出,与ID3/C4.5相比有几个关键改进:
- 二值分裂:每个非叶子节点只产生两个分支(是/否),而不像ID3那样根据特征取值数产生多个分支。
- 支持连续数值特征:分裂条件形如"xʲ ≤ s",并且同一特征可以在不同分支中重复使用。
- 统一处理分类和回归:回归树输出连续预测值,分类树输出类别或类别概率分布。
5.2 回归树
对于回归任务,目标变量 y 为连续值。CART将输入空间划分为 M 个互斥区域 R₁,...,R_M,每个区域用一个常数 cₘ 作为预测值。模型的预测函数为:
f(x) = ∑ₘ cₘ I(x ∈ Rₘ)
通常使用均方误差作为损失。给定区域 Rₘ,最优预测值 ĉₘ 就是该区域内所有样本 y 的均值。
寻找最优分割 :对于连续特征 j 和阈值 s,分割将其分为两个区域:
R₁(j,s) = {x | xʲ ≤ s}, R₂(j,s) = {x | xʲ > s}
每次选择使得两个子区域均方误差之和最小的 (j, s):
min_{j,s} [ min_{c₁} ∑_{x∈R₁} (y - c₁)² + min_{c₂} ∑_{x∈R₂} (y - c₂)² ]
通过对特征值排序,并在线维护左右子集的平方和,能够在 O(n) 时间内完成一个特征的扫描,从而高效找到最优分割点。
5.3 分类树与基尼不纯度
分类树的目标是最小化分类误差。CART分类树采用 基尼不纯度(Gini impurity) 作为分裂标准。对于K类分类问题,假设节点中第k类的概率为 pₖ,基尼不纯度定义为:
Gini(p) = ∑ₖ pₖ (1 - pₖ) = 1 - ∑ₖ pₖ²
对于二分类,Gini(p) = 2p(1-p),其曲线与分类错误率和熵非常接近,但计算更简单。
给定数据集 D,基尼不纯度为:
Gini(D) = 1 - ∑ₖ (|Dₖ| / |D|)²
对于类别特征 j 的某个取值 a,可以将数据分为 D₁ = {x | xʲ = a} 和 D₂ = {x | xʲ ≠ a},其加权基尼不纯度为:
Gini(D, j=a) = (|D₁|/|D|) Gini(D₁) + (|D₂|/|D|) Gini(D₂)
算法遍历所有特征及所有可能的取值/分割点,选择加权基尼不纯度最小的分裂条件。停止分裂的条件包括:节点样本数过少、基尼不纯度足够低、或没有多余特征。
5.4 树与规则转换
决策树天然可以转换为一系列"IF-THEN"规则,每个叶子节点对应一条规则。这种可解释性是树模型的一大优势,便于可视化、原理解释和错误调试。例如:
IF Age > 20:
IF Gender == Male:
return 4.8
ELSE:
return 4.1
ELSE:
return 2.8
5.5 实战:scikit-learn 代码示例
下面分别演示回归树和分类树在 scikit-learn 中的基本用法,涵盖数据准备、模型训练、预测以及关键参数说明。
回归树示例(DecisionTreeRegressor)
python
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# 1. 生成模拟回归数据
np.random.seed(42)
X = np.sort(5 * np.random.rand(80, 1), axis=0) # 80 个样本, 1 个特征
y = np.sin(X).ravel() + 0.1 * np.random.randn(80) # 正弦曲线 + 噪声
# 2. 划分训练集 / 测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 3. 创建回归树并训练
reg = DecisionTreeRegressor(
max_depth=4, # 树的最大深度(防止过拟合的关键参数)
min_samples_split=5, # 内部节点再分裂所需的最小样本数
min_samples_leaf=2, # 叶子节点最少样本数
random_state=42
)
reg.fit(X_train, y_train)
# 4. 预测与评估
y_pred = reg.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
print(f"回归树 --- 测试集 MSE: {mse:.4f}")
关键参数说明:
max_depth:限制树的最大深度,防止过拟合。None表示不限制。min_samples_split:内部节点至少包含多少样本才允许继续分裂。min_samples_leaf:每个叶子节点至少需要多少样本,配合min_samples_split一起控制树的复杂度。criterion:回归树默认使用"squared_error"(MSE),也支持"friedman_mse"、"absolute_error"等。
分类树示例(DecisionTreeClassifier)
python
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 1. 加载鸢尾花数据集(3 分类)
iris = load_iris()
X, y = iris.data, iris.target
# 2. 划分训练集 / 测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 3. 创建分类树并训练
clf = DecisionTreeClassifier(
criterion='gini', # 分裂标准:'gini'(基尼不纯度)或 'entropy'(信息增益)
max_depth=3, # 树的最大深度
min_samples_split=4, # 内部节点再分裂所需的最小样本数
min_samples_leaf=1, # 叶子节点最少样本数
random_state=42
)
clf.fit(X_train, y_train)
# 4. 预测与评估
y_pred = clf.predict(X_test)
acc = accuracy_score(y_test, y_pred)
print(f"分类树 --- 测试集准确率: {acc:.2%}")
# 5. 查看各类别的预测概率(用于集成中的概率平均)
y_proba = clf.predict_proba(X_test[:3])
print(f"前 3 个测试样本的类别概率分布:\n{y_proba}")
关键参数说明:
criterion:"gini"使用基尼不纯度(默认),"entropy"使用信息增益。max_depth、min_samples_split、min_samples_leaf:同回归树,是防止过拟合的核心参数。class_weight:可为不同类别赋予不同权重,适合处理类别不平衡问题(如"balanced")。predict_proba:返回每个样本属于各类的概率,在Bagging等集成方法中常用概率平均来表决。
6. 集成学习初探
6.1 为什么要集成
单一模型的性能往往受限于数据噪声、过拟合或模式捕捉能力的局限性。集成学习的核心思想是结合多个不同的基学习器 来提升整体预测性能。给定一组预测模型 f₁, ..., f_L,构造一个集成模型 F(x),通过对各模型的输出进行投票 (分类)或平均(回归),或在不同区域使用不同模型,来获得更稳定、更准确的预测。
成功的集成必须保证基学习器之间的多样性 --- 即它们所犯的错误应该是不相关的。多样性的来源可以包括:使用不同类型的模型、采用不同的训练集、选择不同的特征子集等。
6.2 集成学习的实际表现
在诸多机器学习竞赛中,集成模型常常是获胜的关键:
- Netflix Prize:获胜方案集成了超过800个预测模型。
- KDD-Cup 2011 雅虎音乐推荐:冠军团队使用了221个模型的集成,第三名团队也使用了16个模型。
这些案例充分说明了集成的威力:只要基学习器具备一定的准确性和足够的多样性,通过简单的组合就能显著超越单个模型。
6.3 组合模型的形式
常见的集成组合方式包括:
- 简单平均/投票:对所有基学习器的输出取平均(回归)或多数投票(分类)。
- 加权平均 :为每个基学习器分配一个权重,形成加权组合:
F(x) = ∑ wᵢ fᵢ(x),权重可以通过训练学习。 - 门控(Gating) :引入一个可学习的门控函数
gᵢ(x),根据输入x动态决定各模型的贡献:F(x) = ∑ gᵢ(x) fᵢ(x),例如 softmax 形式的门控。 - 树模型作为集成:用决策树本身作为组合器,根据原始特征和基学习器的输出进行节点分裂,实现非线性集成。
7. Bagging算法
7.1 自举法(Bootstrap)
自举法是一种基于重采样的统计推断方法。对于一个包含 N 个样本的训练集 Z,通过有放回 地随机抽取 N 个样本,获得一个新的自举复制集 Z*。由于每次采样都是独立的,一个样本没有被抽中的概率约为:
P(样本 i 不在自举复制集中) ≈ (1 - 1/N)ᴺ → e⁻¹ ≈ 0.368
因此每个自举复制集大约只包含63.2%的原始样本,剩下的约36.8%可以作为一个自然的验证集。
7.2 Bagging流程
Bagging(Bootstrap Aggregating)正是利用自举法来构造多个不同的训练集,从而训练出多样化的基学习器。具体步骤:
- 从原始训练集
Z中有放回地生成B个自举复制集Z*¹, Z*², ..., Z*ᴮ; - 在每个
Z*ᵇ上训练一个基学习器f̂*ᵇ(x); - 对回归任务,取所有基学习器预测值的平均;对分类任务,采用多数表决或平均概率。
最终的Bagging预测为:
f̂_bag(x) = 1/B ∑ᵦ f̂*ᵇ(x)
7.3 Bagging为什么有效?
根据偏差-方差分解,在输入点 x₀ 处的期望预测误差可以分解为:
Err(x₀) = σ²_ε + Bias²(f̂(x₀)) + Var(f̂(x₀))
其中:
σ²_ε为固有噪声;Bias²为系统偏差;Var为预测方差。
Bagging的核心思想是:通过在不同的自举样本上训练多个模型,然后取平均,可以在不增加偏差的情况下显著降低方差。对于高方差、低偏差的模型(如深度决策树),Bagging能够大幅度提升预测稳定性。
从自举复制的角度也可以直观理解:每个自举树都只基于大约63.2%的数据,因此它们的预测会在不同方向上有所偏差,但取平均后这些随机波动被平滑,从而得到一条更光滑、更鲁棒的决策边界。
7.4 实例与效果
讲义中展示了B样条光滑的例子:10次自举实验得到的曲线在不同位置有不同的波动,但平均后的曲线更加平滑,且±1.96倍标准误差带给出了95%的置信区间。类似地,在模拟数据集上训练Bagging树,不同自举样本上的树结构差异较大,但投票或平均概率得到的集成边界比单棵树更加稳健。进一步的研究表明,在分类问题中,取概率平均的Bagging效果往往优于简单的多数表决。
7.5 实战:Bagging 代码示例
下面使用 scikit-learn 的 BaggingClassifier 对决策树进行 Bagging 集成,并对比单棵决策树与 Bagging 集成在分类任务上的性能差异。
python
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
# 1. 加载数据
iris = load_iris()
X, y = iris.data, iris.target
# 2. 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 3. 单棵决策树
tree = DecisionTreeClassifier(criterion='gini', max_depth=None, random_state=42)
tree.fit(X_train, y_train)
tree_acc = accuracy_score(y_test, tree.predict(X_test))
# 4. 使用 Bagging 集成多棵决策树
bagging = BaggingClassifier(
estimator=DecisionTreeClassifier(random_state=42), # 基学习器(默认是决策树)
n_estimators=50, # 集成 50 棵决策树
max_samples=0.8, # 每棵树的训练集为原始训练集的 80% 自举采样
bootstrap=True, # 启用有放回采样
random_state=42
)
bagging.fit(X_train, y_train)
bagging_acc = accuracy_score(y_test, bagging.predict(X_test))
# 5. 对比结果
print(f"单棵决策树 --- 测试集准确率: {tree_acc:.2%}")
print(f"Bagging 集成 (50 棵树) --- 测试集准确率: {bagging_acc:.2%}")
print(f"提升: {(bagging_acc - tree_acc) * 100:.2f} 个百分点")
关键参数说明:
estimator:基学习器对象,传入已配置好的决策树实例,默认为DecisionTreeClassifier()。n_estimators:基学习器的数量(B 的大小)。一般数量越多,方差降低越明显,计算开销也越大。max_samples:每个基学习器训练时使用的自举样本数量或比例。max_samples=0.8表示每棵树只使用 80% 的训练数据,剩余的 20% 可用作袋外估计(OOB)。bootstrap:是否启用自举采样(有放回)。True为经典 Bagging,False则使用全部训练数据(丧失多样性,不推荐)。bootstrap_features:是否对特征也进行自举采样(即 Random Subspace),默认为False。oob_score:是否使用袋外样本评估泛化性能,仅当bootstrap=True时有效。设置为True后,可通过bagging.oob_score_查看 OOB 准确率。
从输出可以看到,Bagging 集成能够显著提升分类准确率,验证了 Bagging 降低方差、增强泛化能力的理论分析。
8.总结
与参数化模型不同,决策树直接在泛函空间中以贪心策略搜索最优树结构。从ID3到C4.5再到CART,节点分裂标准从信息增益发展到信息增益率、基尼不纯度,同时CART引入了二值分裂和对连续特征的有效处理,极大扩展了应用范围。然而,单棵树的决策边界往往过于锐利,方差较大。
集成学习通过结合多个多样化的基学习器来提升性能,而Bagging正是通过自举重采样生成差异化的训练集,对高方差模型(如决策树)取平均以降低方差,在实际应用中屡屡取得拔群效果。进一步,我们还可以思考:既然Bagging通过独立采样构造基学习器,那么是否有方法能够解耦合每棵树的学习过程,从而更加有效地降低方差?这正是随机森林(Random Forest)和后续更多集成方法的设计动机。