如何在tidymodels工作流中集成kernelshap与shapviz?
从Tidymodels到KernelSHAP的实现(回归+分类通用)
1. 核心前提:提取Tidymodels封装的XGBoost模型
Tidymodels会把模型封装为Parsnip对象,需要先取出底层的xgb.Booster实例,回归和分类场景步骤一致:
- 用
extract_fit_parsnip()从调优后的工作流/拟合模型中提取Parsnip模型 - 再用
extract_fit_engine()取出底层XGBoost引擎对象
2. KernelSHAP通用代码实现
回归场景(以mtcars预测mpg为例)
# 加载依赖包 library(tidymodels) library(kernelshap) library(xgboost) library(ggplot2) # 假设你已完成调优,得到工作流tuned_wf # 提取底层XGBoost模型 xgb_model <- tuned_wf %>% extract_fit_parsnip() %>% extract_fit_engine() # 准备特征数据集(剔除响应变量mpg) explain_data <- mtcars %>% select(-mpg) # 定义预测函数:kernelshap要求输入特征矩阵,输出预测值 predict_fun_reg <- function(model, newdata) { predict(model, newdata = xgb.DMatrix(data.matrix(newdata))) } # 计算KernelSHAP值(用50个样本做背景提升计算速度) ks_reg <- kernelshap( model = xgb_model, X = explain_data, predict_function = predict_fun_reg, bg_X = explain_data %>% slice_sample(n = 50) ) # 全局SHAP可视化 plot(ks_reg, type = "beeswarm") # 特征影响分布蜂群图 plot(ks_reg, type = "importance") # 特征重要性排序图
分类场景适配
仅需修改预测函数,返回目标类别的概率(以二分类为例):
# 分类场景预测函数 predict_fun_class <- function(model, newdata) { # 返回正类概率,可根据模型分类逻辑调整索引 predict(model, newdata = xgb.DMatrix(data.matrix(newdata)), type = "prob")[,2] } # 后续计算与可视化同回归场景,替换预测函数即可 ks_class <- kernelshap( model = xgb_model_class, # 分类任务的XGBoost模型 X = explain_data_class, predict_function = predict_fun_class, bg_X = explain_data_class %>% slice_sample(n = 50) ) plot(ks_class, type = "beeswarm")
3. Treeshap vs KernelSHAP:选型建议
- Treeshap更适合树模型:
- 专为树结构设计,计算速度远快于KernelSHAP(大数据/大模型场景优势明显)
- 基于树结构精确计算SHAP值,无近似误差
- 原生支持XGBoost、LightGBM、随机森林等树模型
- KernelSHAP胜在通用性:
- 支持任何模型(包括非树模型如神经网络)
- 适合需要统一不同模型解释逻辑的场景
结论:如果你的模型是XGBoost这类树模型,优先选Treeshap;需要兼容非树模型时用KernelSHAP。
4. Treeshap实现对比
基于相同的tidymodels提取模型:
library(treeshap) library(ggplot2) # 提取底层XGBoost模型(步骤同前) xgb_model <- tuned_wf %>% extract_fit_parsnip() %>% extract_fit_engine() # 转换特征为矩阵格式(treeshap要求输入矩阵) X_matrix <- data.matrix(explain_data) # 计算Treeshap值 ts <- treeshap(xgb_model, X = X_matrix) # 特征重要性可视化 plot_importance(ts) # 手动实现蜂群图(treeshap无原生蜂群图,用ggplot2自定义) shap_df <- ts$shaps %>% as.data.frame() %>% mutate(observation = row_number()) %>% pivot_longer(-observation, names_to = "feature", values_to = "shap_value") %>% left_join( explain_data %>% mutate(observation = row_number()) %>% pivot_longer(-observation, names_to = "feature", values_to = "feature_value"), by = c("observation", "feature") ) ggplot(shap_df, aes(x = feature_value, y = shap_value, color = feature)) + geom_jitter(alpha = 0.6) + facet_wrap(~feature, scales = "free_x") + labs(x = "特征值", y = "SHAP值") + theme_minimal()
内容的提问来源于stack exchange,提问作者Jacek Kotowski
相关产品推荐
相关产品推荐

