✨ 欢迎关注 ✨
本节介绍:用SHAP+PDP解读Stacking集成模型:从基学习器到元学习器的可解释性与模型调参实现。数据采用模拟数据,作者根据个人对机器学习的理解进行代码实现与图表输出,仅供参考。 为单独付费项目 。需要的朋友可关注公众号文末提供的购买方式 。 购买前请咨询,避免不必要的问题。
✨ Stacking集成模型 ✨
Stacking(堆叠集成)是一种集成学习方法,它通过组合多个基础模型(一级学习器)的预测结果,再用一个元模型(二级学习器)来进一步学习这些预测结果,最终得到一个更强的预测模型,使用多个不同的基础模型(如随机森林、XGBoost、LightGBM等)对训练数据进行预测,将这些基础模型的预测值作为新的特征,输入到元模型中,元模型通过学习基础模型的输出特征,综合各模型的优点,给出最终预测
实际应用中这里集成模型采用Stacking回归架构。第一层(基学习器)集成了调参后的多种强模型,包括K最近邻、随机森林、XGBoost、LightGBM、CatBoost 和MLP多层感知机。每个基学习器独立学习并输出预测结果。第二层使用线性回归作为元学习器(meta-learner),对所有基学习器的预测结果进行再学习与加权组合,从而提升整体模型的泛化能力与稳定性
对于第一层(基学习器)集成了六种经过调参优化的强学习器:
- KNN:基于样本之间的距离度量进行预测,适合局部模式捕捉
- RF:通过构建多棵决策树并进行投票,具有较强的抗过拟合能力
- XGBoost:一种高效的梯度提升算法,兼具速度和精度,在结构性数据上表现优异
- LightGBM:采用基于直方图的快速决策树算法,计算效率高,适用于大规模数据集
- CatBoost:对类别特征处理优化良好的梯度提升模型,默认参数下即可获得良好性能
- MLP(多层感知机):一种基础神经网络模型,擅长建模非线性关系
每个模型在单独建模阶段均进行了超参数调优,确保各基模型在自身结构下达到最优性能。第二层使用线性回归作为元学习器(meta-learner),对第一层所有基学习器的预测结果进行再学习与加权融合,从而进一步提升模型的泛化能力与稳定性
需要注意的是,SHAP是一种对单一模型进行解释的工具,它通过分配特征对模型预测的贡献值来衡量特征的重要性,所以针对Stacking需要逐层拆解进行分析,可以通过以下两种方式来解释Stacking模型:
- 逐步拆解Stacking结构,分别解释基学习器和元学习器的行为
- 将Stacking模型视为整体的“黑箱”进行解释(仅关注输入特征与最终预测输出的关系)
✨ 基础代码 ✨
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['axes.unicode_minus'] = False
df = pd.read_excel('2025-6-12公众号Python机器学习AI.xlsx')
from sklearn.model_selection import train_test_split
# 划分特征和目标变量
X = df.drop(['Y'], axis=1)
y = df['Y']
# 划分训练集和测试集
X_train, X_test , y_train, y_test = train_test_split(
X,
y,
test_size=0.3,
random_state=42
)
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import GridSearchCV, KFold
from sklearn.metrics import mean_squared_error, r2_score
# 创建 KNN Regressor
knn_model = KNeighborsRegressor()
# 定义参数网格
knn_param_grid = {
'n_neighbors': list(range(1, 31)),
'weights': ['uniform', 'distance'],
'p': [1, 2]
}
# 定义 KFold 交叉验证
knn_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
knn_grid_search = GridSearchCV(
estimator=knn_model,
param_grid=knn_param_grid, # 改这里
cv=knn_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting KNN GridSearchCV...")
knn_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("KNN Best parameters found: ", knn_grid_search.best_params_)
from sklearn.ensemble import RandomForestRegressor
# 创建 RandomForest Regressor
rf_model = RandomForestRegressor(random_state=42)
# 定义参数网格(与KNN模型不同,随机森林有其他参数)
rf_param_grid = {
'n_estimators': [100, 200], # 树的数量
'max_depth': [None, 10, 20], # 树的最大深度
'min_samples_split': [2, 5], # 内部节点再划分所需的最小样本数
'min_samples_leaf': [1, 2], # 叶子节点最小样本数
'bootstrap': [True, False] # 是否采用 bootstrap 样本
}
# 定义 KFold 交叉验证
rf_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
rf_grid_search = GridSearchCV(
estimator=rf_model,
param_grid=rf_param_grid,
cv=rf_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting Random Forest GridSearchCV...")
rf_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("RF Best parameters found: ", rf_grid_search.best_params_)
from xgboost import XGBRegressor
# 创建 XGB Regressor
xgb_model = XGBRegressor(random_state=42)
# 定义参数网格
xgb_param_grid = {
'n_estimators': [100, 200], # 树的数量
'max_depth': [3, 6, 10], # 树的最大深度
'learning_rate': [0.01, 0.1], # 学习率
'subsample': [0.8, 1.0], # 样本子采样比率
'colsample_bytree': [0.8, 1.0], # 每棵树的列采样比率
'gamma': [0, 0.1], # 正则化参数
'min_child_weight': [1, 5] # 最小叶子节点样本权重
}
# 定义 KFold 交叉验证
xgb_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
xgb_grid_search = GridSearchCV(
estimator=xgb_model,
param_grid=xgb_param_grid,
cv=xgb_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting XGBoost GridSearchCV...")
xgb_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("XGBoost Best parameters found: ", xgb_grid_search.best_params_)
from lightgbm import LGBMRegressor
# 创建 LGBM Regressor
lgbm_model = LGBMRegressor(random_state=42, verbose=-1)
# 定义参数网格
lgbm_param_grid = {
'n_estimators': [100, 200], # 树的数量
'max_depth': [3, 10], # 树的最大深度
'learning_rate': [0.01, 0.1], # 学习率
'subsample': [0.8, 1.0], # 样本子采样比率
'colsample_bytree': [0.8, 1.0], # 每棵树的列采样比率
'min_child_samples': [10, 20], # 每棵树的最小样本数
'reg_alpha': [0, 0.1], # L1正则化
'reg_lambda': [0, 0.1] # L2正则化
}
# 定义 KFold 交叉验证
lgbm_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
lgbm_grid_search = GridSearchCV(
estimator=lgbm_model,
param_grid=lgbm_param_grid,
cv=lgbm_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting LGBM GridSearchCV...")
lgbm_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("LGBM Best parameters found: ", lgbm_grid_search.best_params_)
from catboost import CatBoostRegressor
# 创建 CatBoost Regressor
catboost_model = CatBoostRegressor(random_state=42, verbose=0)
# 定义参数网格
catboost_param_grid = {
'iterations': [100, 200], # 迭代次数,即树的数量
'depth': [3, 10], # 树的最大深度
'learning_rate': [0.01, 0.3], # 学习率
'l2_leaf_reg': [1, 5], # L2正则化参数
'border_count': [32, 128], # 特征值划分的边界数量
'subsample': [0.8, 1.0], # 样本子采样比率
'colsample_bylevel': [0.8, 1.0] # 每层树的特征采样比率
}
# 定义 KFold 交叉验证
catboost_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
catboost_grid_search = GridSearchCV(
estimator=catboost_model,
param_grid=catboost_param_grid,
cv=catboost_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting CatBoost GridSearchCV...")
catboost_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("CatBoost Best parameters found: ", catboost_grid_search.best_params_)
from sklearn.neural_network import MLPRegressor
# 创建 MLP Regressor
mlp_model = MLPRegressor(random_state=42, max_iter=1000)
# 定义参数网格(MLP特有的参数)
mlp_param_grid = {
'hidden_layer_sizes': [(50,), (100,), (50, 50)], # 隐藏层的神经元个数
'activation': ['relu', 'tanh'], # 激活函数
'solver': ['adam', 'lbfgs'], # 求解器
'alpha': [0.0001, 0.001, 0.01], # 正则化参数
'learning_rate': ['constant', 'adaptive'], # 学习率调整方式
'learning_rate_init': [0.001, 0.01, 0.1] # 初始学习率
}
# 定义 KFold 交叉验证
mlp_cv = KFold(n_splits=5, shuffle=True, random_state=42)
# 定义 GridSearchCV
mlp_grid_search = GridSearchCV(
estimator=mlp_model,
param_grid=mlp_param_grid,
cv=mlp_cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
# 执行网格搜索
print("Fitting MLP GridSearchCV...")
mlp_grid_search.fit(X_train, y_train)
# 输出最佳参数
print("MLP Best parameters found: ", mlp_grid_search.best_params_)
对KNN、RF、XGBoost、LightGBM、CatBoost 和 MLP 六种回归模型分别使用 GridSearchCV 结合5折交叉验证进行系统性调参,以获得各模型在训练集上的最优超参数配置
Fitting KNN GridSearchCV...
Fitting 5 folds for each of 120 candidates, totalling 600 fits
KNN Best parameters found: {'n_neighbors': 14, 'p': 1, 'weights': 'distance'}
KNN Best CV MSE: 0.8730655469256151
KNN Test RMSE: 0.8455191410269014
KNN Test R²: 0.16877170179372736
Fitting Random Forest GridSearchCV...
Fitting 5 folds for each of 48 candidates, totalling 240 fits
RF Best parameters found: {'bootstrap': True, 'max_depth': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'n_estimators': 100}
RF Best CV MSE: 0.19414730643884512
RF Test RMSE: 0.40841073195909633
RF Test R²: 0.8060598514883957
Fitting XGBoost GridSearchCV...
Fitting 5 folds for each of 192 candidates, totalling 960 fits
XGBoost Best parameters found: {'colsample_bytree': 0.8, 'gamma': 0, 'learning_rate': 0.1, 'max_depth': 6, 'min_child_weight': 1, 'n_estimators': 100, 'subsample': 1.0}
XGBoost Best CV MSE: 0.17946133992185848
XGBoost Test RMSE: 0.4068730401085547
XGBoost Test R²: 0.807517495736095
Fitting LGBM GridSearchCV...
Fitting 5 folds for each of 256 candidates, totalling 1280 fits
LGBM Best parameters found: {'colsample_bytree': 0.8, 'learning_rate': 0.1, 'max_depth': 10, 'min_child_samples': 10, 'n_estimators': 100, 'reg_alpha': 0, 'reg_lambda': 0.1, 'subsample': 0.8}
LGBM Best CV MSE: 0.17135990391080808
LGBM Test RMSE: 0.3914894510592229
LGBM Test R²: 0.8217975950206939
Fitting CatBoost GridSearchCV...
Fitting 5 folds for each of 128 candidates, totalling 640 fits
CatBoost Best parameters found: {'border_count': 32, 'colsample_bylevel': 1.0, 'depth': 3, 'iterations': 200, 'l2_leaf_reg': 5, 'learning_rate': 0.3, 'subsample': 1.0}
CatBoost Best CV MSE: 0.19169850630441304
CatBoost Test RMSE: 0.39877286268964307
CatBoost Test R²: 0.8151052309421698
Fitting MLP GridSearchCV...
Fitting 5 folds for each of 216 candidates, totalling 1080 fits
MLP Best parameters found: {'activation': 'tanh', 'alpha': 0.01, 'hidden_layer_sizes': (50,), 'learning_rate': 'constant', 'learning_rate_init': 0.001, 'solver': 'lbfgs'}
MLP Best CV MSE: 0.26611679055853327
MLP Test RMSE: 0.5127538682741835
MLP Test R²: 0.6943028968225878
输出展示了六个回归模型在经过超参数调优后的最佳配置及其在训练集交叉验证和测试集上的性能,其中在测试集上表现最差的是 KNN模型(R²=0.169) ,最佳的是 LightGBM模型(R²=0.822) ,为后续与Stacking集成模型的性能对比提供明确的单模型参考基线
from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import LinearRegression
# 定义一级学习器,使用调参后的模型
base_learners = [
("KNN", knn_best_model),
("RF", rf_best_model),
("XGB", xgb_best_model),
("LGBM", lgbm_best_model),
("CatBoost", catboost_best_model),
("MLP", mlp_best_model)
]
# 二级学习器(可以换成 XGB 或 Ridge 等)
meta_model = LinearRegression()
# 构建 Stacking 回归器
stacking_regressor = StackingRegressor(
estimators=base_learners,
final_estimator=meta_model,
cv=5,
n_jobs=-1,
passthrough=False # 可设为 True 以传入原始特征
)
# 拟合堆叠模型
print("Training StackingRegressor...")
stacking_regressor.fit(X_train, y_train)
构建并训练一个包含六个调参优化后基学习器的Stacking回归模型,使用线性回归作为元学习器对各基模型的预测结果进行加权融合;其中一级学习器的模型参数均通过
GridSearchCV
结合5折交叉验证自动搜索得到最优配置,接下来对集成模型进行模型评价
输出结果显示, Stacking集成模型在测试集上的R²达到0.832,整体性能优于所有单个基模型 ,其中此前最优的单模型为LightGBM(R²=0.822),其次是XGBoost(R²=0.808),而KNN仅为0.169。Stacking通过整合多个调参优化后的模型,有效利用它们在不同数据子空间的优势,进一步提升模型的泛化能力与稳定性。同时,训练集上 R² 达到 0.957,表明模型在学习过程中拟合效果良好且不过拟合迹象较轻(当然这个不同研究方向的容忍度会不同)
需要特别说明的是: 本例更侧重于演示如何构建、调参并融合多模型形成Stacking回归器的完整流程 。读者可根据自己的任务场景替换基学习器或元学习器(如用XGBoost替代线性回归),方法同样适用,具有较强的迁移性和实用价值,接下来进行模型解释即可
图中展示的是Stacking模型第一层各基学习器的SHAP特征重要性点图,用于揭示不同模型对输入特征的依赖模式与预测机制。每幅图反映一个基模型在测试集上的SHAP分布情况,横轴表示特征对预测结果的正负贡献,点的颜色代表特征值的高低。可以看出,X_1在大部分模型中(KNN例外)均为关键特征,而各模型对其他特征的响应差异体现了模型结构的多样性与互补性,这也正是Stacking能有效提升预测性能的核心基础
这里展示的是Stacking模型第一层各基学习器的 SHAP 平均特征重要性排序柱状图,横轴表示每个特征的平均绝对 SHAP 值(即对模型输出的平均影响程度),清晰地反映出不同模型在整体预测中最依赖的核心特征
接下来针对Stacking模型第二层元学习器(Linear Regression)进行SHAP特征贡献分析,用于解释各基学习器的预测输出在最终融合中的影响程度,其中LGBM和CatBoost的预测结果对最终输出影响最大,说明它们在整个集成结构中贡献最显著
展示的是Stacking模型第二层元学习器对各基学习器输出的平均SHAP贡献排序柱状图,表明在最终预测中LGBM和CatBoost的输出对元模型影响最大,是整体集成效果的主要驱动因素,根据这个读者也可以进一步对模型进行优化,以提高模型精确度
最后展示对整个Stacking集成模型的SHAP特征重要性点图分析,反映各输入变量在最终预测结果中的实际贡献程度。图中可以看出,X_1是对模型输出影响最大的关键特征,其高值显著推高预测结果;X_2和X_5的贡献相对较小。通过这种全局可解释性分析,可以直观理解集成模型是如何综合各特征信息做出预测判断的
模型整体基于SHAP的特征平均贡献柱状图,表明在Stacking模型中,X_1和X_7是对预测结果影响最大的两个特征
整体模型上每个特征对Stacking模型输出的单独影响趋势,可以看出特征呈现非线性甚至拐点式影响,揭示了模型对不同输入特征的复杂响应关系,如特征X_1shap值先为负代表降低模型的预测值但是shap绝对值逐渐减小也表示 降低模型的预测值的程度也在减小,直到shap值为0,该特征对应的shap值为正代表增加模型的预测值随着 特征X_1的增加shap值也在增加表示增加 模型的预测值的程度也在变大
最后绘制集成模型的部分依赖图(PDP),用于补充解释各特征对预测结果的单独影响趋势
| 特征 | PDP(部分依赖图) | SHAP(SHapley值) | | --- | --- | --- | | 解释目标 | 显示某个特征对预测结果的平均影响 | 显示某个特征对单个样本预测的精确贡献 | | 是否局部解释 | ❌ 全局方法(平均效应) | ✅ 局部和全局均可(每个样本的解释) | | 考虑特征交互性 | ❌ 不考虑(默认特征独立) | ✅ 考虑特征之间的交互 | | 计算复杂度 | ✅ 较低(对模型调用次数较少) | ❌ 较高(需要多个采样和模型调用) | | 解释能力 | 平滑趋势,适合解释线性或单调关系 | 更精确、可用于非线性和复杂模型 | | 输出结果 | 每个特征值 vs 平均预测 | 每个特征值 vs 预测的归因值(Shapley值) |
✨ 该文章案例 ✨
在完整项目压缩包中,案例包含完整代码注释等,确保读者能够达到最佳的学习效果。内容都经过详细解读,帮助读者深入理解模型的实现过程和数据分析步骤,从而最大化学习成果。
✨ 淘宝店铺 ✨
请大家打开淘宝扫描上方的二维码,进入店铺,获取更多Python机器学习和AI相关的内容,或者添加作者微信deep_ML联系 避免淘宝客服漏掉信息 ,希望能为您的学习之路提供帮助!
往期推荐
GeoShapley算法:基于地理数据的Shapley值在空间效应测量中的应用——位置重要性与特征交互作用分析
期刊配图:基于‘UpSet图’展示不同数据预处理对模型性能的影响
J Clean Prod:结合K-means聚类确定样本分组方式再结合shap初步解释模型的模拟实现
nature communications:结合LightGBM特征选择与RF模型的机器学习方法及SHAP解释
期刊配图:结合lightgbm回归模型与K折交叉验证的特征筛选可视化
Nature新算法:准确的小数据预测与表格基础模型TabPFN分类实现及其模型解释
Nature新算法:准确的小数据预测与表格基础模型TabPFN回归实现及其模型解释
如果你对类似于这样的文章感兴趣。
欢迎关注、点赞、转发~
个人观点,仅供参考