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)

Output

coalitions: 7
Shapley: [0.3333333333333333, 0.3333333333333333, 0.3333333333333333]
Banzhaf: [0.5, 0.5, 0.5]

Bitmask iteration

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

from nlp_shap.estimation.exact import ExactEstimator

for index, mask_int in enumerate(ExactEstimator.iter_mask_ints(3)):
    present = ExactEstimator.mask_int_to_present(mask_int, 3)
    print(present)
    if index >= 2:
        break

Output

(False, False, False)
(True, False, False)
(False, True, False)

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)

Output

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)

Output

exact

Notebook

See the runnable walkthrough in Examplesexamples/exact_estimation.ipynb.

Further reading