06 Comparison with SHAP
We will compare Shapley value computation between shapley-numba and SHAP.
Remember to pip install shapley-numba[shap].
import numpy as np
import shap
from shapley_numba.examples import GloveGame
from shapley_numba.shapley import shapley
We will define our game.
Recall that in shapley_numba, number of players is often an external parameter that needs to be supplied to computations.
The following defines a glove market game of 12 players with 3 left gloves sellers (and 9 right gloves sellers).
num_players = 12
game = GloveGame(3)
explainer = shap.explainers.Exact(
lambda x: [game.value(c) for c in x], np.zeros((1, num_players), dtype=np.int32)
)
explainer(np.ones((1, num_players), dtype=np.int32))
.values =
array([[0.85909091, 0.85909091, 0.85909091, 0.0469697 , 0.0469697 ,
0.0469697 , 0.0469697 , 0.0469697 , 0.0469697 , 0.0469697 ,
0.0469697 , 0.0469697 ]])
.base_values =
array([0.])
.data =
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=int32)
shapley(game, num_players)
array([0.85909091, 0.85909091, 0.85909091, 0.0469697 , 0.0469697 ,
0.0469697 , 0.0469697 , 0.0469697 , 0.0469697 , 0.0469697 ,
0.0469697 , 0.0469697 ])
Performance comparison
Let’s increase the number of players to compare the performance of two packages.
high_number_of_players = 20
explainer = shap.explainers.Exact(
lambda x: [game.value(c) for c in x],
np.zeros((1, high_number_of_players), dtype=np.int32),
)
%%timeit
explainer(np.ones((1, high_number_of_players), dtype=np.int32), max_evals=None)
2.16 s ± 30.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit
shapley(game, high_number_of_players)
67.6 ms ± 729 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
While this is faster, there is a slight of hand - the adapter of our game for SHAP is a pure python loop. Let’s try to write it in numba.
import numba
def shap_game_adapter(game):
jitted_game = game.jitted_game
@numba.njit
def _value_of_coalitions(jg, coalitions):
ln = len(coalitions)
result = np.zeros(ln)
for i, c in enumerate(coalitions):
result[i] = jg.value(c)
return result
def value_of_coalitions(coalitions):
return _value_of_coalitions(jitted_game, coalitions)
return value_of_coalitions
shap_glove = shap_game_adapter(game)
explainer = shap.explainers.Exact(
shap_glove,
np.zeros(
(
1,
high_number_of_players,
),
dtype=np.int32,
),
)
%%timeit
explainer(np.ones((1, high_number_of_players), dtype=np.int32), max_evals=None)
916 ms ± 8.56 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)