使用GradientExplainer(shap)分析TensorFlow多输入多输出模型时出现KeyError
解决多输入模型的SHAP KeyError问题
你的KeyError源于多输入模型与SHAP交互时的数据格式不匹配,以下是具体修复方案:
问题根源
模型接收两个输入分支(数值特征、分类特征),但调用explainer()时混合使用了DataFrame和numpy数组,导致SHAP无法正确匹配模型的输入层结构。多输入场景下,SHAP要求背景数据和待解释数据的结构、类型完全一致。
修复步骤
1. 统一输入数据类型
将所有输入数据转为numpy数组,避免DataFrame与数组混合使用,确保训练和解释阶段的数据格式一致:
# 转换训练数据为numpy数组 X_train_num_arr = X_train_num.values X_train_emb_arr = X_train_emb.values y_train_arr = y_train.values # 用数组训练模型(与解释阶段保持格式统一) history = model.fit([X_train_num_arr, X_train_emb_arr], y_train_arr, verbose=1, epochs=10, batch_size=50)
2. 正确初始化并调用GradientExplainer
背景数据和待解释数据都使用numpy数组列表,严格匹配模型的输入结构:
# 用numpy数组初始化Explainer explainer = shap.GradientExplainer(model, [X_train_num_arr, X_train_emb_arr]) # 解释时传入同结构的数组列表 shap_values = explainer([X_train_num_arr, X_train_emb_arr])
3. 处理SHAP输出结果
多输入模型的SHAP结果是一个列表,每个元素对应一个输入分支的SHAP值:
shap_values[0]:数值特征的SHAP值,形状为(样本数, 数值特征数, 目标数)shap_values[1]:分类特征嵌入后的SHAP值,形状为(样本数, 嵌入维度, 目标数)
若需要查看原始分类特征的贡献,可将嵌入层的SHAP值与嵌入层权重做加权和,映射回原分类特征维度。
完整修正代码
import shap import tensorflow as tf from tensorflow.keras import layers, models from tensorflow.keras.optimizers import Adam from tensorflow.keras.initializers import glorot_uniform import pandas as pd import numpy as np # 生成测试数据 X_train_num = pd.DataFrame(np.random.randint(0,100,size=(1000, 33))) X_train_emb = pd.DataFrame(np.random.randint(0,19,size=(1000, 1))) y_train = pd.DataFrame(np.random.randint(0,100,size=(1000, 5))) final_features = X_train_num.columns targets = y_train.columns # 定义优化器 optimizer = Adam( learning_rate=0.0002, beta_1=0.9, beta_2=0.999, epsilon=1e-07, amsgrad=False ) # 构建多输入模型 def get_model(final_features, targets): no_of_unique_cat = 20 embedding_size = 10 layer_numerical = Input(shape=(len(final_features),)) cat_input = Input(shape=(1,)) embed_layer = Embedding(input_dim=no_of_unique_cat, output_dim=embedding_size)(cat_input) embed_layer = Flatten()(embed_layer) merged_layer = concatenate([layer_numerical, embed_layer]) output = Dropout(0.1)(merged_layer) output = Dense(360, kernel_initializer=glorot_uniform(), activation='relu')(output) output = Dropout(0.3)(output) output = Dense(20, kernel_initializer=glorot_uniform(), activation='relu')(output) output = Dense(len(targets))(output) model = models.Model(inputs=[layer_numerical, cat_input], outputs=output) model.compile(loss='mae', optimizer=optimizer) return model model = get_model(final_features, targets) # 统一转换为numpy数组 X_train_num_arr = X_train_num.values X_train_emb_arr = X_train_emb.values y_train_arr = y_train.values # 训练模型 history = model.fit([X_train_num_arr, X_train_emb_arr], y_train_arr, verbose=1, epochs=10, batch_size=50) # SHAP模型解释 explainer = shap.GradientExplainer(model, [X_train_num_arr, X_train_emb_arr]) shap_values = explainer([X_train_num_arr, X_train_emb_arr]) # 打印结果结构验证 print(f"数值特征SHAP形状: {shap_values[0].shape}") print(f"分类特征SHAP形状: {shap_values[1].shape}")
内容的提问来源于stack exchange,提问作者Jaime Hernandez-Sanchez
相关产品推荐
相关产品推荐

