Using the exact estimator

ExactEstimator lazily enumerates every coalition mask (except the grand coalition) and delegates attribution to a registered estimand plugin.

Quick start

from itertools import product

from nlp_shap.domain.coalition import CoalitionMask
from nlp_shap.domain.players import PlayerSet
from nlp_shap.estimation.estimands import BanzhafAggregator, ShapleyAggregator
from nlp_shap.estimation.exact import ExactEstimator

player_set = PlayerSet(player_ids=("p0", "p1", "p2"))
estimator = ExactEstimator()

masks = list(estimator.sample_masks(
    player_set,
    budget_fraction=1.0,
    include_minimal_masks=False,
    seed=0,
))
print("coalitions:", len(masks))  # 7 for three players

all_masks = [
    tuple(bits) for bits in product([False, True], repeat=player_set.num_players)
]

def majority_payoff(mask: tuple[bool, ...]) -> float:
    return 1.0 if sum(mask) >= 2 else 0.0

payoffs = [majority_payoff(mask) for mask in all_masks]
coalition_masks = tuple(CoalitionMask.from_sequence(mask) for mask in all_masks)

shapley = estimator.estimate_attributions(
    coalition_masks,
    payoffs,
    ShapleyAggregator(),
)
banzhaf = estimator.estimate_attributions(
    coalition_masks,
    payoffs,
    BanzhafAggregator(),
)

print("Shapley:", shapley)
print("Banzhaf:", banzhaf)

Bitmask iteration

For large player sets, iterate packed coalition integers and decode only when needed:

from nlp_shap.estimation.exact import ExactEstimator

for mask_int in ExactEstimator.iter_mask_ints(player_set.num_players):
    present = ExactEstimator.mask_int_to_present(mask_int, player_set.num_players)

Budget requirement

Exact enumeration requires a full budget fraction:

from nlp_shap.domain.players import PlayerSet
from nlp_shap.estimation.exact import ExactEstimator

player_set = PlayerSet(player_ids=("p0",))
try:
    ExactEstimator().sample_masks(
        player_set,
        budget_fraction=0.5,
        include_minimal_masks=False,
        seed=0,
    )
except ValueError as exc:
    print(exc)  # exact estimator requires budget_fraction == 1.0

Plugin resolution

Resolve the estimator from packaging entry points:

from nlp_shap.plugins import PluginGroup, PluginRegistry, register_builtin_plugins

registry = PluginRegistry()
register_builtin_plugins(registry)
registry.load_entry_points(PluginGroup.ESTIMATORS)

estimator = registry.resolve(PluginGroup.ESTIMATORS, "exact")
print(estimator.name)  # exact

Notebook

See the runnable walkthrough in Examplesexamples/exact_estimation.ipynb.

Further reading