Files
2026-07-13 13:22:52 +08:00

165 lines
7.9 KiB
Python

from __future__ import annotations
from ..._explanation import Explanation
from .._explainer import Explainer
class DeepExplainer(Explainer):
"""Meant to approximate SHAP values for deep learning models.
This is an enhanced version of the DeepLIFT algorithm (Deep SHAP) where, similar to Kernel SHAP, we
approximate the conditional expectations of SHAP values using a selection of background samples.
Lundberg and Lee, NIPS 2017 showed that the per node attribution rules in DeepLIFT (Shrikumar,
Greenside, and Kundaje, arXiv 2017) can be chosen to approximate Shapley values. By integrating
over many background samples, Deep estimates approximate SHAP values such that they sum
up to the difference between the expected model output on the passed background samples and the
current model output (f(x) - E[f(x)]).
Examples
--------
See :ref:`Deep Explainer Examples <deep_explainer_examples>`
"""
def __init__(self, model, data, session=None, learning_phase_flags=None):
"""An explainer object for a differentiable model using a given background dataset.
Note that the complexity of the method scales linearly with the number of background data
samples. Passing the entire training dataset as `data` will give very accurate expected
values, but will be unreasonably expensive. The variance of the expectation estimates scales by
roughly 1/sqrt(N) for N background data samples. So 100 samples will give a good estimate,
and 1000 samples a very good estimate of the expected values.
Parameters
----------
model :
if framework == 'tensorflow', (input : [tf.Tensor], output : tf.Tensor)
A pair of TensorFlow tensors (or a list and a tensor) that specifies the input and
output of the model to be explained. Note that SHAP values are specific to a single
output value, so the output tf.Tensor should be a single dimensional output (,1).
if framework == 'pytorch', an nn.Module object (model), or a tuple (model, layer),
where both are nn.Module objects.
The model is an nn.Module object which takes as input a tensor (or list of tensors) of
shape data, and returns a single dimensional output.
If the input is a tuple, the returned shap values will be for the input of the
layer argument. layer must be a layer in the model, i.e. model.conv2
data :
if framework == 'tensorflow': [np.array] or [pandas.DataFrame]
if framework == 'pytorch': [torch.tensor]
The background dataset to use for integrating out features. Deep integrates
over these samples. The data passed here must match the input tensors given in the
first argument. Note that, since these samples are integrated over for each sample, you
should only use something like 100 or 1000 random background samples, not the whole
training dataset.
session : None or tensorflow.Session
The TensorFlow session that has the model we are explaining. If None is passed then
we do our best to find the right session, first looking for a keras session, then
falling back to the default TensorFlow session.
learning_phase_flags : None or list of tensors
If you have your own custom learning phase flags, pass them here. When explaining a prediction
we need to ensure we are not in training mode, since this changes the behavior of ops like
batch norm or dropout. If None is passed then we look for tensors in the graph that look like
learning phase flags (this works for Keras models). Note that we assume all the flags should
have a value of False during predictions (and hence explanations).
"""
# first, we need to find the framework
if isinstance(model, tuple):
a, b = model
try:
a.named_parameters()
framework = "pytorch"
except Exception:
framework = "tensorflow"
else:
try:
model.named_parameters()
framework = "pytorch"
except Exception:
framework = "tensorflow"
masker = data
super().__init__(model, masker)
self.explainer: TFDeep | PyTorchDeep
if framework == "tensorflow":
from .deep_tf import TFDeep
self.explainer = TFDeep(model, data, session, learning_phase_flags)
elif framework == "pytorch":
from .deep_pytorch import PyTorchDeep
self.explainer = PyTorchDeep(model, data)
self.expected_value = self.explainer.expected_value
self.explainer.framework = framework # type: ignore
def __call__(self, X: list | np.ndarray | pd.DataFrame | torch.tensor) -> Explanation: # type: ignore # noqa: F821
"""Return an explanation object for the model applied to X.
Parameters
----------
X : list,
if framework == 'tensorflow': numpy.array, or pandas.DataFrame
if framework == 'pytorch': torch.tensor
A tensor (or list of tensors) of samples (where X.shape[0] == # samples) on which to
explain the model's output.
Returns
-------
shap.Explanation:
"""
shap_values = self.shap_values(X)
return Explanation(values=shap_values, data=X)
def shap_values(self, X, ranked_outputs=None, output_rank_order="max", check_additivity=True):
"""Return approximate SHAP values for the model applied to the data given by X.
Parameters
----------
X : list,
if framework == 'tensorflow': np.array, or pandas.DataFrame
if framework == 'pytorch': torch.tensor
A tensor (or list of tensors) of samples (where X.shape[0] == # samples) on which to
explain the model's output.
ranked_outputs : None or int
If ranked_outputs is None then we explain all the outputs in a multi-output model. If
ranked_outputs is a positive integer then we only explain that many of the top model
outputs (where "top" is determined by output_rank_order). Note that this causes a pair
of values to be returned (shap_values, indexes), where shap_values is a list of numpy
arrays for each of the output ranks, and indexes is a matrix that indicates for each sample
which output indexes were choses as "top".
output_rank_order : "max", "min", or "max_abs"
How to order the model outputs when using ranked_outputs, either by maximum, minimum, or
maximum absolute value.
Returns
-------
np.array or list
Estimated SHAP values, usually of shape ``(# samples x # features)``.
The shape of the returned array depends on the number of model outputs:
* one input, one output: matrix of shape ``(#num_samples, *X.shape[1:])``.
* one input, multiple outputs: matrix of shape ``(#num_samples, *X.shape[1:], #num_outputs)``
* multiple inputs, one or more outputs: list of matrices, with shapes of one of the above.
If ranked_outputs is ``None`` then this list of tensors matches
the number of model outputs. If ranked_outputs is a positive integer a pair is returned
(shap_values, indexes), where shap_values is a list of tensors with a length of
ranked_outputs, and indexes is a matrix that indicates for each sample which output indexes
were chosen as "top".
.. versionchanged:: 0.45.0
Return type for models with multiple outputs and one input changed from list to np.ndarray.
"""
return self.explainer.shap_values(X, ranked_outputs, output_rank_order, check_additivity=check_additivity)