モデル非依存(model-agnostic)、つまり任意の機械学習アルゴリズムで作った予測モデルに対して適用できるSHAP values推定アルゴリズム。
SHAPの提案論文(Lundberg & Lee, 2017)のTheorem2あたりで出てくる話
数式の概要¶
Notation¶
時点の特徴量ベクトルがあるとし、特徴量が存在するかどうかを表すようにsimplifiedした二値変数のベクトルをとする。
をに戻す関数をとおく
予測値を特徴の寄与度 に分解する関数をとおく:
Shapley kernel¶
重み関数
をShapley kernelと呼ぶ。なお、ここで はの非ゼロ要素の数()である。
重み付き二乗損失¶
モデルの予測値(実測値) と 説明モデルの出力を近づけるようにすると損失が小さくなる関数を定義し、Shapley kernelで重みをつける。
Theorem2¶
上記の を最小化する解はShapeley values(モデルの説明について望ましい性質1~3を満たす値)である
(そしてこの重み付き回帰がKernel SHAPと呼ばれる)
実装例で確かめる¶
全体像¶
Simple Kernel SHAP — SHAP latest documentation
を参考にしつつ改変したものが次
(referenceの値の重要性はいまいちわかっておらずひとまず0にしている)
import itertools
import numpy as np
import scipy.special
def shapley_kernel(M, s):
"""重みカーネル"""
if s == 0 or s == M:
return 10000 # infが返るのを防ぐ
return (M - 1) / (scipy.special.binom(M, s) * s * (M - s))
def powerset(M: int):
"""特徴量のすべての組み合わせを列挙する"""
s = list(range(M))
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1))
def create_data(x):
M = x.shape[0] # 特徴ベクトルの次元数
# 特徴量の出現の有無(0,1)を表す
S = np.zeros((2**M, M + 1))
S[:, -1] = 1 # 切片項 φ_0 のためのカラムを追加
weights = np.zeros(2**M)
# 特徴量の値を入れる行列
reference = 0
X = np.zeros((2**M, M))
for i in range(2**M):
X[i, :] = reference
ws = {}
for i, s in enumerate(powerset(M)):
s = list(s)
X[i, s] = x[s]
S[i, s] = 1
w = shapley_kernel(M, len(s))
ws[len(s)] = ws.get(len(s), 0) + w
weights[i] = w
return X, S, weights
def solve_wls(X, W, y):
"""重み付き最小二乗法(WLS)を解く"""
A = X.T @ W @ X
b = X.T @ W @ y
return np.linalg.solve(A, b)
def kernel_shap(x, f):
"""Kernel SHAPの簡単な実装例"""
X, S, weights = create_data(x)
y = f(X) # 予測モデルの出力値
W = np.diag(weights) # 重み行列
phis = solve_wls(S, W, y) # NOTE: 入力の行列はX(特徴量の値)ではなくS(特徴量の出現の有無)
return phis
# ---------------------------------
# 例:線形回帰モデルを説明する
# ---------------------------------
x = np.array([10, 15]) # 特徴量ベクトル
def f(x):
"""線形回帰モデル"""
alpha = 10 # 切片
beta = np.array([5, 10]) # 回帰係数
return alpha + x @ beta
phi = kernel_shap(x, f)
base_value = phi[-1]
shap_values = phi[:-1]
print(" x =", x)
print("shap_values =", shap_values)
print(" base_value =", base_value)
print(" sum(phi) =", np.sum(phi))
print(" f(x) =", f(x)) x = [10 15]
shap_values = [ 50. 150.]
base_value = 10.000000000000002
sum(phi) = 210.0
f(x) = 210
x = np.array([10, 15]) # 特徴量ベクトル
def f(x):
"""線形回帰モデル"""
alpha = 10 # 切片
beta = np.array([5, 10]) # 回帰係数
return alpha + x @ beta
f(x)210import scipy.special
def shapley_kernel(M: int, s: int) -> float:
# sはここではlen(s)
if s == 0 or s == M:
return 10000.0 # infが返るのを防ぐ
return (M - 1) / (scipy.special.binom(M, s) * s * (M - s))# 例
shapley_kernel(M=2, s=1)0.5データ行列を作る¶
def powerset(M: int):
"""特徴量のすべての組み合わせを列挙する"""
s = list(range(M))
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1))
list(powerset(M=2))[(), (0,), (1,), (0, 1)]def create_data(x):
M = x.shape[0] # 特徴ベクトルの次元数
# 特徴量の出現の有無(0,1)を表す
S = np.zeros((2**M, M + 1))
S[:, -1] = 1 # 切片項 φ_0 のためのカラムを追加
weights = np.zeros(2**M)
# 特徴量の値を入れる行列
reference = 0
X = np.zeros((2**M, M))
for i in range(2**M):
X[i, :] = reference
ws = {}
for i, s in enumerate(powerset(M)):
s = list(s)
X[i, s] = x[s]
S[i, s] = 1
w = shapley_kernel(M, len(s))
ws[len(s)] = ws.get(len(s), 0) + w
weights[i] = w
return X, S, weightsdef solve_wls(X, W, y):
A = X.T @ W @ X
b = X.T @ W @ y
return np.linalg.solve(A, b)def kernel_shap(x, f):
"""Kernel SHAPの簡単な実装例"""
X, S, weights = create_data(x)
y = f(X) # 予測モデルの出力値
W = np.diag(weights) # 重み行列
phis = solve_wls(S, W, y) # NOTE: 入力の行列はX(特徴量の値)ではなくS(特徴量の出現の有無)
return phis
phi = kernel_shap(x, f)
base_value = phi[-1]
shap_values = phi[:-1]
print(" x =", x)
print("shap_values =", shap_values)
print(" base_value =", base_value)
print(" sum(phi) =", np.sum(phi))
print(" f(x) =", f(x)) x = [10 15]
shap_values = [ 50. 150.]
base_value = 10.000000000000002
sum(phi) = 210.0
f(x) = 210
パッケージとの比較¶
import shap
reference = np.zeros(len(x))
explainer = shap.KernelExplainer(f, np.reshape(reference, (1, len(reference))))
shap_values = explainer.shap_values(x)
print("shap_values =", shap_values)
print("base value =", explainer.expected_value)shap_values = [ 50. 150.]
base value = 10.0
参考文献¶
Simple Kernel SHAP — SHAP latest documentation
公式ドキュメントでの実装つき説明