
一、本节学习目标
完成本节后,你将能够:
-
解释单棵决策树为什么容易波动;
-
理解Bootstrap有放回抽样;
-
掌握Bagging的并行集成思想;
-
说明随机森林与普通Bagging的区别;
-
建立结构最大应力随机森林代理模型;
-
使用袋外分数评价模型;
-
比较单树、Bagging与随机森林;
-
正确解释随机森林特征重要性。
二、为什么需要多棵决策树?
上一节建立的决策树具有较强的可解释性,但也有一个明显问题:对数据变化比较敏感。
假设原始数据中某个样本的J积分从:
变为:
决策树选择的裂纹长度阈值就可能发生变化,后面的分支结构也可能随之改变。
这说明单棵树具有较高方差:
更换少量训练样本,模型结构和预测结果可能明显变化。
解决方法不是只寻找一棵"完美的树",而是训练许多不同的树,再综合它们的判断。
三、Bagging的核心思想
Bagging是Bootstrap Aggregating的缩写,可以理解为"自助采样与聚合"。
其基本过程为:
-
从原始训练集中进行Bootstrap抽样;
-
使用每一份抽样数据训练一棵决策树;
-
重复训练多棵树;
-
对所有树的结果进行平均或投票。

图7-1 Bagging与随机森林工作流程
对于回归问题,最终预测为:
其中为树的数量。
对于分类问题,通常采用多数投票:
四、什么是Bootstrap抽样?
假设训练集中有5个样本:
Bootstrap仍然抽取5次,但每次抽取后都把样本放回。因此某一次抽样可能得到:
另一次可能得到:
可以看到:
-
某些样本会被重复抽到;
-
某些样本不会出现;
-
每棵树看到的训练数据都不完全相同。
对于包含个样本的数据集,一个指定样本一次未被抽中的概率约为:
所以每棵树大约有36.8%的原始样本没有参与训练。这些样本称为袋外样本,可用于评价模型。
五、Bagging与随机森林有什么区别?
Bagging主要引入数据随机性:
随机森林在此基础上又增加了特征随机性:
每个节点分裂时,只从部分随机选出的特征中寻找最佳划分。
因此:
| 方法 | 样本随机抽取 | 特征随机选择 |
|---|---|---|
| 单棵决策树 | 否 | 否 |
| Bagging | 是 | 通常否 |
| 随机森林 | 是 | 是 |
如果所有树总是选择同一个最强特征,它们可能长得十分相似。随机特征选择可以增加树之间的差异,使平均结果更加稳定。
六、建立结构最大应力数据集
假设有限元参数化计算包含以下输入:
-
载荷
;
-
结构长度
;
-
截面宽度
;
-
截面厚度
;
-
弹性模量
;
-
缺口长度
。
输出为结构最大应力:
下面使用公式构造一组模拟仿真数据。
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n_samples = 500
load_kN = rng.uniform(10, 100, n_samples)
length_mm = rng.uniform(500, 1200, n_samples)
width_mm = rng.uniform(20, 80, n_samples)
thickness_mm = rng.uniform(4, 15, n_samples)
elastic_modulus_GPa = rng.uniform(70, 210, n_samples)
notch_length_mm = rng.uniform(0, 8, n_samples)
nominal_stress = (
1000 * load_kN
/ (width_mm * thickness_mm)
)
stress_concentration = (
1 + 0.12 * notch_length_mm
)
noise = rng.normal(0, 5, n_samples)
max_stress_MPa = (
nominal_stress * stress_concentration
+ 0.015 * length_mm
+ noise
)
data = pd.DataFrame({
"载荷_kN": load_kN,
"长度_mm": length_mm,
"宽度_mm": width_mm,
"厚度_mm": thickness_mm,
"弹性模量_GPa": elastic_modulus_GPa,
"缺口长度_mm": notch_length_mm,
"最大应力_MPa": max_stress_MPa
})
print("数据形状:", data.shape)
print("缺失值数量:", data.isna().sum().sum())
预期输出
数据形状: (500, 7)
缺失值数量: 0
这里的公式只用于构建教学数据。在真实项目中,最大应力_MPa应来自Abaqus、ANSYS或FEniCS计算结果。
七、划分训练集和测试集
from sklearn.model_selection import train_test_split
feature_names = [
"载荷_kN",
"长度_mm",
"宽度_mm",
"厚度_mm",
"弹性模量_GPa",
"缺口长度_mm"
]
X = data[feature_names]
y = data["最大应力_MPa"]
X_train, X_test, y_train, y_test = (
train_test_split(
X,
y,
test_size=0.2,
random_state=42
)
)
print("训练集:", X_train.shape)
print("测试集:", X_test.shape)
预期输出
训练集: (400, 6)
测试集: (100, 6)
树模型根据特征大小进行分裂,一般不要求特征标准化。
八、建立单棵决策树基准模型
from sklearn.tree import DecisionTreeRegressor
single_tree = DecisionTreeRegressor(
max_depth=None,
min_samples_leaf=1,
random_state=42
)
single_tree.fit(X_train, y_train)
tree_prediction = single_tree.predict(
X_test
)
print(
"前5个预测值:",
np.round(tree_prediction[:5], 2)
)
预期输出
程序会输出5个最大应力预测值,例如:
前5个预测值: [268.41 113.57 396.82 182.35 521.76]
具体末位数值可能因scikit-learn版本而略有不同。
这棵树没有限制深度,很容易将训练样本划分得非常细,因此可以作为过拟合程度较高的基准模型。
九、建立Bagging回归模型
from sklearn.ensemble import BaggingRegressor
bagging_model = BaggingRegressor(
estimator=DecisionTreeRegressor(
min_samples_leaf=2,
random_state=42
),
n_estimators=100,
max_samples=1.0,
bootstrap=True,
random_state=42,
n_jobs=-1
)
bagging_model.fit(X_train, y_train)
bagging_prediction = bagging_model.predict(
X_test
)
print(
"Bagging包含的树数量:",
len(bagging_model.estimators_)
)
预期输出
Bagging包含的树数量: 100
n_estimators=100表示训练100棵决策树。
如果旧版本scikit-learn提示:
unexpected keyword argument 'estimator'
可以将estimator=改成:
base_estimator=
十、建立随机森林模型
from sklearn.ensemble import (
RandomForestRegressor
)
random_forest = RandomForestRegressor(
n_estimators=300,
max_features="sqrt",
min_samples_leaf=2,
bootstrap=True,
oob_score=True,
random_state=42,
n_jobs=-1
)
random_forest.fit(X_train, y_train)
forest_prediction = random_forest.predict(
X_test
)
print("树的数量:", len(
random_forest.estimators_
))
print(
f"袋外R²:"
f"{random_forest.oob_score_:.4f}"
)
预期输出
树的数量: 300
袋外R²:0.95左右
袋外分数的具体数值可能随软件版本出现小幅变化。
十一、比较三种模型
from sklearn.metrics import (
mean_absolute_error,
mean_squared_error,
r2_score
)
models_and_predictions = {
"单棵树": tree_prediction,
"Bagging": bagging_prediction,
"随机森林": forest_prediction
}
records = []
for name, prediction in (
models_and_predictions.items()
):
mae = mean_absolute_error(
y_test,
prediction
)
rmse = np.sqrt(
mean_squared_error(
y_test,
prediction
)
)
r2 = r2_score(
y_test,
prediction
)
records.append({
"模型": name,
"MAE": mae,
"RMSE": rmse,
"R2": r2
})
evaluation = pd.DataFrame(records)
print(
evaluation.to_string(
index=False,
float_format=lambda value: f"{value:.3f}"
)
)
预期现象
典型结果表现为:
模型 MAE RMSE R2
单棵树 较大 较大 较低
Bagging 较小 较小 较高
随机森林 较小 较小 较高
由于数据随机生成以及软件版本差异,具体数值可能不同,但集成模型通常比未剪枝的单棵树更稳定。

图7-2 单树与集成模型误差示意
图中的数值用于说明典型趋势,实际课程运行时应以程序输出为准。
十二、为什么平均能够降低波动?
假设每棵树的预测可以写成:
其中表示第
棵树的预测波动。
对多棵树取平均:
当不同树的误差不完全相同时,正负误差会部分抵消,从而降低整体方差。
但如果所有树完全相同,它们的误差也会高度相关,平均效果就会减弱。这正是随机森林需要同时引入样本随机性和特征随机性的原因。
十三、查看特征重要性
importance = pd.Series(
random_forest.feature_importances_,
index=feature_names
).sort_values(ascending=False)
print(importance.round(4))
预期现象
通常,载荷、厚度、宽度和缺口长度会获得较高重要性;弹性模量在当前力控制应力模型中的贡献可能较低。
载荷_kN 较高
厚度_mm 较高
宽度_mm 较高
缺口长度_mm 较高
长度_mm 较低
弹性模量_GPa 较低

图7-3 随机森林特征重要性示意
特征重要性表示变量对降低树节点误差的贡献,不等于严格的物理因果关系。
十四、绘制实际运行得到的重要性图
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = [
"Microsoft YaHei",
"SimHei"
]
plt.rcParams["axes.unicode_minus"] = False
importance.sort_values().plot(
kind="barh",
color="#1769aa"
)
plt.xlabel("特征重要性")
plt.ylabel("输入特征")
plt.title("随机森林特征重要性")
plt.grid(axis="x", alpha=0.3)
plt.tight_layout()
plt.show()
预期绘图结果
程序会按照重要性从低到高绘制水平柱状图。柱越长,表示该特征在当前森林的节点划分中贡献越大。
十五、袋外评价有什么用?
每棵树约有36.8%的训练样本未被抽中。可以让这些袋外样本通过对应的树进行预测,再汇总得到袋外分数。
袋外评价的优点是:
-
不需要额外划分验证集;
-
可以充分利用小样本仿真数据;
-
能快速检查森林是否具有基本泛化能力。
但袋外分数不能完全替代独立测试集。最终模型仍应使用未参与训练和调参的数据进行检验。
十六、关键超参数
n_estimators
森林中的树数量。树越多通常越稳定,但计算量也越大。
max_features
每次节点分裂时允许参与竞争的特征数量。数值越小,树之间的差异通常越大。
max_depth
限制每棵树的最大深度。
min_samples_leaf
限制叶节点的最小样本数。适当增大可以让预测曲面更加平滑。
max_samples
控制每棵树使用多少训练样本。
n_jobs=-1
使用可用CPU核心并行训练。
十七、树的数量越多越好吗?
增加树的数量通常会使模型误差逐渐稳定,但不会无限提高精度。
当:
模型方差逐渐趋于稳定,继续增加树数量主要只会增加:
-
训练时间;
-
内存占用;
-
模型文件体积;
-
推理计算量。
工程中可以逐步尝试:
50, 100, 200, 300, 500
并观察测试误差是否已经进入平台区。
十八、随机森林的优势与局限
主要优势
-
能处理明显的非线性关系;
-
不要求特征标准化;
-
对异常值和数据扰动相对稳健;
-
可以估计特征重要性;
-
训练过程容易并行;
-
通常比单棵树具有更好的泛化能力。
主要局限
-
不如单棵树容易直接解释;
-
模型文件可能较大;
-
不能自然地进行远距离外推;
-
普通重要性可能偏向连续或取值较多的特征;
-
预测曲面仍由大量分段区域构成;
-
不能保证满足守恒定律和本构约束。
十九、力学建模中的注意事项
1. 不要随机拆分强相关网格点
相邻节点的应力高度相关。如果同一有限元模型的节点同时进入训练集和测试集,可能造成数据泄漏。
更可靠的方法是按照完整工况或完整模型划分数据。
2. 特征重要性不等于因果关系
载荷重要性高,不代表其他参数没有物理作用。
3. 不要信任训练范围外的预测
随机森林主要依赖已有样本区域进行分段预测,不擅长外推。
4. 检查预测的物理合理性
例如:
-
载荷增大时最大应力是否总体增大;
-
厚度增大时应力是否总体减小;
-
预测值是否出现负应力或异常跳变;
-
高风险工况是否被训练数据覆盖。
二十、本节练习
基础练习
将树数量依次修改为:
10, 50, 100, 300, 500
记录测试集RMSE和训练时间。
进阶练习
分别设置:
max_features = 1.0
max_features = "sqrt"
max_features = 0.5
观察树之间差异和模型精度的变化。
工程挑战
将输出由最大应力改为最大位移,并判断弹性模量的重要性是否会上升。
思考练习
如果随机森林认为"弹性模量"对最大应力几乎没有贡献,这是否一定是错误的?请结合力控制与位移控制边界条件进行解释。
二十一、本节小结
Bagging通过Bootstrap抽样训练多棵不同的树:
随机森林进一步在节点划分时随机选择部分特征:
它的核心价值不是让每一棵树都非常准确,而是让多棵存在差异的树共同形成稳定判断。
下一节将学习梯度提升机GBM,理解另一种集成思路:不再并行训练独立的树,而是让后一棵树专门纠正前面模型尚未解决的预测误差。