Skip to content

v-code01/shapcast

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

shapcast

captum's ShapleyValueSampling and ShapleyValues build the running attribution accumulator total_attrib with a hardcoded dtype=torch.float, that is float32, regardless of the model's output dtype. Its sibling FeatureAblation, in the same PerturbationAttribution family and driven by the same forward function, reads the forward output dtype into attrib_type and builds its accumulator with dtype=attrib_type. On a float64 model the Shapley accumulator is silently float32 while the FeatureAblation one is float64. The Shapley values are therefore accumulated in float32: always a silent precision downgrade, and under a large activation dynamic range a 100 percent violation of the Shapley completeness axiom sum(attributions) = f(x) - f(baseline) that does not converge with more samples.

The code

captum, captum/attr/_core/shapley_value.py, 0.9.0 and current main (unfixed), the accumulator in both the main path (0.9.0 line 367, main line 373) and the futures path (0.9.0 line 639, main line 653):

total_attrib = [
    torch.zeros(
        tuple(output_shape) + tuple(input.shape[1:]),
        dtype=torch.float,                       # hardcoded float32, ignores the forward dtype
        device=inputs_tuple[0].device,
    )
    for input in inputs_tuple
]

The sibling captum/attr/_core/feature_ablation.py (lines 71, 79) does it correctly:

attrib_type = flattened_initial_eval.dtype       # read from the forward output
...
total_attrib = [
    torch.zeros((n_outputs,) + input.shape[1:], dtype=attrib_type, device=input.device)
    for input in inputs
]

Each Shapley marginal f(prefix + i) - f(prefix) is formed in the model's dtype, then stored into the float32 accumulator. The fix is one substitution, dtype=attrib_type, exactly as the sibling does.

What it does

Driving the real captum methods on a float64 model:

always-on downgrade (float64 linear model):
  ShapleyValues   dtype torch.float32, abs err 5.96e-08
  FeatureAblation dtype torch.float64, abs err 0.00e+00

completeness violation (A=1e8 model):
  f(x) - f(base) = 2.0
  exact Shapley  [1.0, 1.0] sum 2.0
  captum Shapley [1.0, 0.0] sum 1.0  gap 1.0

sampling does not converge toward [1, 1]:
  n_samples=   25: ['-4.000e+06', '4.000e+06']
  n_samples= 1000: ['-2.000e+05', '2.000e+05']
  n_samples=20000: ['-4.100e+05', '4.100e+05']

Always on, a float64 model returns float32 Shapley values carrying float32 rounding, here 5.96e-08 (float32 machine epsilon), while FeatureAblation on the same model returns exact float64. Under a large dynamic range, f(x0, x1) = A*x0 + A*x1 - (2A-2)*x0*x1 with A = 1e8, the true attribution of each feature is 1.0 and their sum equals f(x) - f(baseline) = 2.0. The marginals are of magnitude A, so once they are stored in the float32 accumulator the O(1) attribution is lost: ShapleyValues, a full permutation enumeration with no sampling, returns [1.0, 0.0] summing to 1.0, and ShapleyValueSampling wanders at magnitude 1e5 to 1e7 and does not shrink toward [1, 1] as the sample count grows.

The base case isolates the fault: FeatureAblation uses the same base class and the same forward function and returns the float64 answer exactly, so the loss is the Shapley accumulator dtype, not the perturbation approach. The accumulator.py model reproduces the permutation accumulation with the accumulator dtype as the only free variable: a float64 accumulator gives [1, 1] with completeness gap 0, a float32 accumulator collapses it, everything else held fixed.

Scope

The downgrade is on the default path with no special parameters: any float64 model attributed with ShapleyValues or ShapleyValueSampling gets float32 accumulation, whereas FeatureAblation, Occlusion, and the other perturbation methods honor float64. For float32 models the accumulator dtype coincidentally matches, so this is invisible; it is float64 models specifically, common in scientific and calibration settings, that silently lose precision, and lose the attribution entirely when the marginals are large relative to their telescoping sum. The error is an induced float64 to float32 downcast, not unavoidable round-off: the sibling avoids it and is exact.

Layout

  • excerpt.py: the Shapley accumulator line and the FeatureAblation accumulator line quoted with the flags that name the fault.
  • accumulator.py: the Shapley permutation accumulation with the accumulator dtype as a parameter, so a float64 total is exact and a float32 total collapses, with everything else identical.
  • consequence.py: the real captum ShapleyValues, ShapleyValueSampling, and FeatureAblation, the always-on dtype downgrade, the completeness violation, and the non-convergence of the sampler.
  • test_shapcast.py: the model float64 accumulator is exact and the float32 one is not, the real Shapley is downgraded while FeatureAblation stays exact, completeness is violated, and the sampler does not converge.

Reproduce

python accumulator.py
python consequence.py
python test_shapcast.py

The accumulator lines are quoted from current main; the attributions are produced by the real captum methods. The fix is dtype=attrib_type in both accumulator constructions, matching FeatureAblation.

About

captum's `ShapleyValueSampling` and `ShapleyValues` build the running attribution accumulator `total_attrib` with a hardcoded `dtype=torch.float`, that is float32, regardless of the model's output dtype.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages