SHAP 值计算公式与代码示例
1. 经典 SHAP 计算(Shapley 值)
SHAP 值基于 Shapley 值的定义,用于衡量每个特征对模型预测的贡献。
公式:
ϕ
i
=
∑
S
⊆
{
1
,
.
.
.
,
n
}
∖
{
i
}
∣
S
∣
!
(
n
−
∣
S
∣
−
1
)
!
n
!
[
f
(
S
∪
{
i
}
)
−
f
(
S
)
]
\phi_i = \sum_{S \subseteq \{1, ..., n\} \setminus \{i\}} \frac{|S|! (n - |S| - 1)!}{n!} \Big[ f(S \cup \{i\}) - f(S) \Big]
ϕi=S⊆{1,...,n}∖{i}∑n!∣S∣!(n−∣S∣−1)![f(S∪{i})−f(S)]
其中:
- ϕ i \phi_i ϕi:特征 i i i 的 SHAP 值。
- S S S:包含除 i i i 之外的特征子集。
- f ( S ) f(S) f(S):仅使用子集 S S S 进行预测的模型输出。
- f ( S ∪ { i } ) f(S \cup \{i\}) f(S∪{i}):加入 i i i 之后的预测值。
- 组合系数 ∣ S ∣ ! ( n − ∣ S ∣ − 1 ) ! n ! \frac{|S|! (n - |S| - 1)!}{n!} n!∣S∣!(n−∣S∣−1)! 确保所有子集的贡献被公平分配。
代码示例
import itertools
import numpy as np
def shap_value(model, X, i):
""" 计算特征 i 的 SHAP 值 """
n = X.shape[1] # 特征总数
phi_i = 0 # 初始化 SHAP 值
for S in itertools.combinations([j for j in range(n) if j != i], r=None):
S = list(S)
weight = np.math.factorial(len(S)) * np.math.factorial(n - len(S) - 1) / np.math.factorial(n)
f_S = model.predict(X[:, S].mean(axis=0, keepdims=True)) # 仅用 S 预测
f_Si = model.predict(X[:, S + [i]].mean(axis=0, keepdims=True)) # S + i 预测
phi_i += weight * (f_Si - f_S)
return phi_i
shap_values = [shap_value(model, X, i) for i in range(X.shape[1])]
注意:此方法计算复杂度为 O(2^n),高维数据不适用。
- Deep SHAP(深度学习 SHAP)
Deep SHAP 结合 DeepLIFT,通过基线输入(baseline)计算输入特征相对于基线的贡献。
公式:
ϕ
i
=
(
x
i
−
x
’
i
)
⋅
∂
f
(
x
)
∂
x
i
∣
x
’
\phi_i = (x_i - x’i) \cdot \frac{\partial f(x)}{\partial x_i} \Big|{x’}
ϕi=(xi−x’i)⋅∂xi∂f(x)
x’
期望 SHAP 计算:
ϕ
i
=
E
x
’
∼
P
(
x
’
)
[
(
x
i
−
x
’
i
)
⋅
∂
f
(
x
)
∂
x
i
∣
x
’
]
\phi_i = \mathbb{E}_{x’ \sim P(x’)} \Big[ (x_i - x’i) \cdot \frac{\partial f(x)}{\partial x_i} \Big|{x’} \Big]
ϕi=Ex’∼P(x’)[(xi−x’i)⋅∂xi∂f(x)
x’]
其中:
•
x
’
x’
x’ 是基线输入(如零输入、均值输入)。
•
P
(
x
’
)
P(x’)
P(x’) 是基线输入的分布,用多个样本求期望。
• 计算输入
x
i
x_i
xi 相对于基线
x
’
i
x’_i
x’i 的贡献,结合梯度信息衡量 SHAP 值。
代码示例
import shap
import torch
X_baseline = X_train.mean(axis=0, keepdims=True) # 均值作为基线输入
explainer = shap.DeepExplainer(model, torch.tensor(X_baseline, dtype=torch.float32))
shap_values = explainer.shap_values(torch.tensor(X_test, dtype=torch.float32))
适用场景:深度学习(如 CNN、RNN、Transformer)。
- Kernel SHAP(通用模型)
Kernel SHAP 采用加权线性回归近似 SHAP 值。
公式:
ϕ
=
arg
min
ϕ
∑
S
⊆
N
w
(
S
)
(
f
(
S
)
−
∑
i
∈
S
ϕ
i
)
2
\phi = \arg\min_{\phi} \sum_{S \subseteq N} w(S) \Big( f(S) - \sum_{i \in S} \phi_i \Big)^2
ϕ=argϕminS⊆N∑w(S)(f(S)−i∈S∑ϕi)2
其中:
•
w
(
S
)
w(S)
w(S) 是加权因子,近似 Shapley 值。
代码示例
import shap
explainer = shap.KernelExplainer(model.predict, X_train)
shap_values = explainer.shap_values(X_test)
适用场景:适用于任何黑盒模型,但计算较慢。
- Tree SHAP(决策树)
Tree SHAP 针对 决策树(XGBoost, LightGBM) 进行了优化,计算复杂度降低:
复杂度:
O
(
T
L
)
O(TL)
O(TL)
其中:
•
T
T
T 是树的数量。
•
L
L
L 是树的最大深度。
代码示例
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
适用场景:梯度提升树(GBDT、XGBoost、LightGBM)。
- 处理 Embedding 特征的 SHAP 计算
当特征经过 Embedding 层 变成 多维向量,每个 SHAP 计算都会返回多个 SHAP 值,例如:
X_category → Embedding → (d_embedding × SHAP values)
如何衡量整个特征的影响?
- 绝对值均值: 1 d ∑ i = 1 d ϕ i \frac{1}{d} \sum_{i=1}^{d} \phi_i d1∑i=1dϕi
- L2 范数: ∑ i = 1 d ϕ i 2 \sqrt{\sum_{i=1}^{d} \phi_i^2} ∑i=1dϕi2
- 最大 SHAP 值: max ( ϕ 1 ) \max( \phi_1) max(ϕ1)
- 计算 SHAP 重要性示例
计算 L2 范数作为整体特征贡献
shap_feature_importance = np.linalg.norm(shap_values, axis=1)
计算特征的绝对 SHAP 均值
shap_feature_importance = np.mean(np.abs(shap_values), axis=1)
找出贡献最大的维度
top_k_dims = np.argsort(np.abs(shap_values), axis=1)[:, -3:]
- 总结
7. 总结
SHAP 方法 | 适用模型 | 计算复杂度 | 优点 | 缺点 |
---|---|---|---|---|
经典 SHAP 计算 | 任何模型 | ( O(2^n) ) | 精确计算 Shapley 值,理论最优 | 计算量指数级增长,无法用于高维数据 |
Deep SHAP | 深度学习(CNN, RNN, Transformer) | ( O(n) ) | 适用于神经网络,支持梯度计算 | 依赖于选定的基线数据,解释可能受基线选择影响 |
Kernel SHAP | 任何模型(黑盒) | ( O(n^2) ) | 适用于任何模型,无需访问模型内部 | 计算较慢,高维数据开销大 |
Tree SHAP | 决策树(XGBoost, LightGBM) | ( O(TL) ) | 计算高效,支持任意树模型 | 仅适用于树模型,无法用于 DNN |
L2 范数聚合 | 处理嵌入特征 | ( O(d) ) | 适用于高维嵌入特征,衡量整体贡献 | 不能解释具体维度贡献,仅提供整体影响 |
最大 SHAP 绝对值 | 处理嵌入特征 | ( O(d) ) | 捕捉最重要维度贡献 | 可能忽略次要维度的影响 |
这篇 Markdown 代码 适用于 CSDN、Jupyter Notebook、GitHub Markdown 编辑器,确保公式和代码格式正确展示!🚀