Source code for mllm_shap.estimation.loo
"""Leave-one-out attribution: each player's drop from the full coalition.
@domain estimation
@see [[knowledge/architecture/estimation]]
@tested-by [[tests/estimation/test_loo.py]]
"""
from collections.abc import Iterator, Sequence
from ..domain.coalition import CoalitionMask
from ..domain.players import PlayerSet
from ..protocols.estimand import EstimandAggregator
from ._shared import _SnapshotEstimator
[docs]
class LeaveOneOutEstimator(_SnapshotEstimator):
"""Attribute ``v(N) - v(N - {i})`` to player ``i`` from ``n + 1`` coalitions.
The baseline every attribution method should beat. It ignores the budget and
the estimand: values are not Shapley or Banzhaf values and need not add up
to ``v(N) - v(empty)``.
"""
@property
def name(self) -> str:
"""Return the registered estimator identifier."""
return "loo"
[docs]
def sample_masks(
self,
player_set: PlayerSet,
budget_fraction: float,
include_minimal_masks: bool,
seed: int,
) -> Iterator[CoalitionMask]:
"""Yield the grand coalition, then the grand coalition without each player."""
_ = budget_fraction, include_minimal_masks, seed
n = player_set.num_players
yield CoalitionMask.from_sequence((True,) * n)
for i in range(n):
yield CoalitionMask.from_sequence(tuple(j != i for j in range(n)))
[docs]
def estimate_attributions(
self,
masks: Sequence[CoalitionMask],
payoffs: Sequence[float],
aggregator: EstimandAggregator | None,
) -> list[float]:
"""Return ``v(N) - v(N - {i})`` for every player ``i``."""
_ = aggregator
payoff = {
mask.present: value for mask, value in zip(masks, payoffs, strict=True)
}
n = len(masks[0].present)
grand = payoff[(True,) * n]
return [grand - payoff[tuple(j != i for j in range(n))] for i in range(n)]