Using approximate estimators

Approximate estimators sample coalitions under a budget fraction and aggregate payoffs into attributions. Monte Carlo delegates to estimand plugins; complementary and Neyman estimators use complementary-contribution (CC) aggregation. Every estimator takes the same estimate_attributions(masks, payoffs, aggregator) call; CC estimators accept the Shapley aggregator only. See Approximate estimation for what each estimator computes from a partial sample.

A toy game with a known answer

The snippets on this page share one five-player game: each player adds its weight, and players p0 and p1 earn a bonus of 2 when both are present. The exact Shapley values are the weights, plus half the bonus for p0 and p1:

from mllm_shap import (
    CoalitionMask,
    ComplementaryEstimator,
    ExactEstimator,
    MonteCarloEstimator,
    NeymanEstimator,
    PlayerSet,
    ShapleyAggregator,
)

player_set = PlayerSet(player_ids=("p0", "p1", "p2", "p3", "p4"))
weights = (1.0, 0.5, 2.0, -1.0, 0.25)

def payoff(mask: CoalitionMask) -> float:
    additive = sum(w for w, present in zip(weights, mask.present) if present)
    bonus = 2.0 if mask.present[0] and mask.present[1] else 0.0
    return additive + bonus

exact = ExactEstimator()
all_masks = list(exact.sample_masks(player_set, 1.0, False, seed=0))
reference = exact.estimate_attributions(
    all_masks, [payoff(mask) for mask in all_masks], ShapleyAggregator()
)
print("coalitions:", len(all_masks))
print("exact:", tuple(round(value, 4) for value in reference))

Output

coalitions: 32
exact: (2.0, 1.5, 2.0, -1.0, 0.25)

Monte Carlo

estimator = MonteCarloEstimator()
masks = list(estimator.sample_masks(
    player_set,
    budget_fraction=0.5,
    include_minimal_masks=True,
    seed=42,
))
values = estimator.estimate_attributions(
    masks,
    [payoff(mask) for mask in masks],
    ShapleyAggregator(),
)
print("samples:", len(masks))
print("mc:", tuple(round(value, 4) for value in values))

Output

samples: 16
mc: (2.2, 1.75, 2.0, -1.0, 0.25)

The same masks aggregated with BanzhafAggregator give the Banzhaf estimate instead; Monte Carlo is the only built-in sampler that supports both estimands.

Complementary pairs

estimator = ComplementaryEstimator()
masks = list(estimator.sample_masks(
    player_set,
    budget_fraction=0.5,
    include_minimal_masks=True,
    seed=7,
))
# The (N, empty) pair comes first, then the singleton pairs ({i}, N\{i}),
# then pairs S, N\S. Order does not matter for aggregation: masks are
# matched to their complements by value.
values = estimator.estimate_attributions(
    masks,
    [payoff(mask) for mask in masks],
    ShapleyAggregator(),
)
print("samples:", len(masks))
print("complementary:", tuple(round(value, 4) for value in values))

Output

samples: 16
complementary: (2.175, 0.525, 1.875, -0.725, 1.3)

Neyman two-phase sampling

Neyman allocation needs phase-one payoffs before drawing phase-two masks:

estimator = NeymanEstimator(initial_num_samples=2, use_standard_method=False)
phase_one = list(estimator.sample_masks(
    player_set,
    budget_fraction=0.8,
    include_minimal_masks=False,
    seed=11,
))
payoffs_one = [payoff(mask) for mask in phase_one]
estimator.begin_allocation(phase_one, payoffs_one)

phase_two = list(estimator.sample_allocation_masks())
payoffs_two = [payoff(mask) for mask in phase_two]

values = estimator.estimate_attributions(
    phase_one + phase_two,
    payoffs_one + payoffs_two,
    ShapleyAggregator(),
)
print("phase one:", len(phase_one), "phase two:", len(phase_two))
print("neyman:", tuple(round(value, 4) for value in values))

Output

phase one: 24 phase two: 0
neyman: (1.5417, 1.4917, 1.9, -0.875, 0.65)

ExplainRunner runs both phases for you when explanation.estimator is neyman_cc; the phase-one size comes from the neyman config section.

Configuration

Set the estimator and budget in ExplainConfig:

explanation:
  estimator: neyman_cc
  budget:
    fraction: 0.8
  include_minimal_masks: false
  neyman:
    initial_num_samples: 2
    use_standard_method: false

Phase one is sized by neyman.initial_num_samples or, when that is unset, neyman.initial_fraction; with neither set a default based on the number of players is used. On small budgets phase one can use the whole allowance; the estimator then aggregates phase one alone; up to \(2 + 2n\) masks it evaluates the same coalitions as complementary sampling, and above that its error is often close to it (examples/estimator_comparison.ipynb shows a game where it is larger). Configuration lists every key.

Plugin resolution

Estimators are registered under the mllm_shap.estimators entry-point group; the names are the values explanation.estimator accepts:

from mllm_shap import PluginGroup, PluginRegistry
from mllm_shap.plugins import register_builtin_plugins

registry = PluginRegistry()
register_builtin_plugins(registry)
print(registry.names(PluginGroup.ESTIMATORS))

Output

('complementary', 'exact', 'loo', 'mc', 'neyman_cc')

Choosing an estimator

Estimator

Best when

Estimand support

Accuracy at low budget

Partial-sample caveat

Monte Carlo (mc)

You need Banzhaf, or Shapley at low and medium budgets

Shapley, Banzhaf

Lowest error from fraction 0.2 up on the benchmark game of Choosing a budget

Not efficient below the full table; sizes without pairs are skipped

Complementary (complementary)

Every coalition should be scored together with its complement

Shapley only

Converges more slowly than Monte Carlo on the benchmark

Not efficient below the full table

Neyman (neyman_cc)

Generous budgets where payoff variance differs by coalition size

Shapley only (two-phase)

Close to complementary while phase one uses the whole budget

Phase-two allocation needs enough phase-one samples per size

Choosing an estimator turns this into a decision table, and examples/estimator_comparison.ipynb (on Examples) measures the error of each estimator against the exact values as the budget grows.