Source code for shapiq.explainer.agnostic

"""Agnostic Explainer for shapiq."""

from __future__ import annotations

from typing import TYPE_CHECKING

from shapiq.game import Game

from .base import Explainer
from .configuration import setup_approximator
from .custom_types import ExplainerIndices

if TYPE_CHECKING:
    from collections.abc import Callable
    from typing import Any, Literal

    import numpy as np

    from shapiq.approximator.base import Approximator
    from shapiq.interaction_values import InteractionValues

    from .tabular import TabularExplainerApproximators

AgnosticExplainerIndices = ExplainerIndices


[docs] class AgnosticExplainer(Explainer): """Agnostic Explainer for shapiq. This explainer is used to explain models that do not have a specific implementation in shapiq. It uses the game-based approach to explain the model's predictions. """ game: Game | Callable[[np.ndarray], np.ndarray] """The cooperative game to be explained, either as a Game instance or a callable value function.""" def __init__( self, game: Game | Callable[[np.ndarray], np.ndarray], *, n_players: int | None = None, index: AgnosticExplainerIndices = "SV", max_order: int = 1, approximator: ( Approximator[AgnosticExplainerIndices] | Literal["auto"] | TabularExplainerApproximators ) = "auto", random_state: int | None = None, ) -> None: """Initialize the AgnosticExplainer. Args: game: The cooperative game to be explained. This can be an instance of :class:`shapiq.games.base.Game` or a callable value function that expects a one-hot matrix of coalitions as input and returns the value of the coalition. n_players: The number of players in the game. If not provided, it will be inferred from the :class:`shapiq.games.base.Game`. index: The type of game-theoretic index to be used for the explanation. Defaults to ``"SV"``. max_order: The maximum order of interactions to be computed. Set to ``1`` for no interactions (i.e, for Shapley values ``"SV"`` or Banzhaf values ``"BV"``). approximator: The approximator to use for the game-based approach. Defaults to "auto", which will automatically select the appropriate approximator based on the index and max_order. Other options include "regression", "spex", "svarm", "montecarlo", and "permutation". Can also be an instance of :class:`shapiq.approximator._base.Approximator`. random_state: The random state to use for reproducibility. Defaults to None. Raises: ValueError: If the `game` is not an instance of `shapiq.games.base.Game` and `n_players` is not specified. """ if n_players is None: if not isinstance(game, Game): msg = ( f"The number of players must be specified with the `n_players` argument if no " f"`shapiq.games.base.Game` instance is provided. Got {type(game)}." ) raise ValueError(msg) n_players = game.n_players super().__init__(model=game, class_index=None) self.game = game self._approximator = setup_approximator( approximator=approximator, max_order=max_order, index=index, n_players=n_players, random_state=random_state, )
[docs] def explain_function( # type: ignore[override] self, budget: int, *, x: np.ndarray | None = None, random_state: int | None = None, **kwargs: Any, # noqa: ARG002 ) -> InteractionValues: """Explain the function using the game-based approach. Args: budget: The budget used for the approximation / computation of interaction values. x: An optional data point to explain. This is only usable if the game is a :class:`~shapiq.games.imputer.base.Imputer`. If provided, the imputer will be fitted to this data point before computing the interaction values. Defaults to ``None``. random_state: An optional random state for reproducibility. Defaults to ``None``. If ``None``, no random state is set for the game or approximator. **kwargs: Additional keyword arguments (not used, only for compatibility). Returns: InteractionValues: The computed interaction values. """ from shapiq.imputer.base import Imputer if x is not None and isinstance(self.game, Imputer): self.game.fit(x=x) if random_state is not None: self.game.set_random_state(random_state=random_state) if random_state is not None: self.approximator.set_random_state(random_state=random_state) return self.approximator.approximate(game=self.game, budget=budget)