How TRIAD decomposes a score¶
TRIAD's promise is that the decomposition you read is the score the model produced.
Every feature's contribution is split into three signed channels — Information,
Density/epistemic, Missingness — that sum back exactly, and every explain
call re-checks that the channel totals plus the intercept reproduce the model's raw
score before anything is returned.
This article walks the whole pipeline for one batch of instances: how a fitted model becomes bin tensors, how empirical-Bayes shrinkage turns bag disagreement into a support weight, how the three channels are computed, and what the epistemic scores and reason codes summarize. Each stage links to a concept page that covers it in depth; design choices cite the design decisions record.
Running example
A thin-file credit applicant: the portfolio has informative (MNAR) missingness —
30% of x0 is masked, with the masking probability rising with x0 itself, and
masked rows carry a planted +1.0 on their default logit. A lender must separate
derogatory evidence (a genuinely bad value) from absent evidence (nothing on
file). This is the DGP of the
synthetic credit walkthrough
(make_missingness(mechanism="MNAR", missing_effect=1.0)).
The pipeline, end to end — every stage below gets its own section:
flowchart TD
subgraph setup ["Set up (once per TriadExplainer)"]
MODEL["fit_lgbm_gam result /<br/>raw booster / fitted EBM"] -->|"resolve mode"| ADAPT["adapter<br/>exact: TermData bin tensors (f, v, n)<br/>approximate: TreeSHAP phi + replica variance"]
BAGS["bag replicas"] -->|"disagreement"| ADAPT
ADAPT --> TAU["empirical-Bayes tau-squared<br/>per term (mom / mle)"]
end
subgraph solve ["Explain (per batch)"]
X["instances X"] --> BIN["bin lookup: f(x), v(x)<br/>missing bin / unknown slot routing"]
BIN --> W["support weight w = tau2 / (tau2 + v)<br/>x out-of-range decay (exact mode)"]
TAU --> W
W --> CH["I = w·f on observed rows<br/>M = w·f on missing rows<br/>D = f − w·f (residual)"]
CH --> GUARD{"C2 guard:<br/>channel totals + intercept<br/>= raw score?"}
end
GUARD -->|"yes"| OUT["TriadExplanation<br/>channels / ES, KR / reasons / waterfall"]
GUARD -->|"no"| ERR["RuntimeError"]
The identity, stated precisely¶
For each feature \(j\) of instance \(x\), TRIAD produces three signed numbers with
where \(f_j(x_j)\) is the feature's exact contribution to the raw score (C1 — completeness), and across features
(C2 — score reconciliation). The channels are a reallocation of contributions the model already made, not a new attribution method: exact mode reallocates the term contributions of an additive model, approximate mode reallocates the model's own TreeSHAP attributions. Channels and guarantees lists the full invariant suite the test battery asserts over both modes.
One model language: bin tensors¶
Exact mode never touches the model object during decomposition. An adapter first
extracts each additive term into a TermData contract: a tensor f of centered
per-bin scores, a tensor v of per-bin sampling variances, and a tensor n of
per-bin training mass — with the missing bin at index 0 and the unknown bin at
index −1 along each axis.
- For an additive-constrained LightGBM booster, shape functions are reconstructed
from
dump_model()— bins are the intervals between the feature's split thresholds — and reconcile withpredict(raw_score=True)exactly (model adapters). - For an InterpretML EBM,
term_scores_tensors already carry this layout; outer-bag standard deviations becomevandbin_weights_becomen.
Terms are centered: each term's training-mass-weighted mean is folded into the intercept, so a term's value is its deviation from the population average.
At explain time each instance is looked up in this layout: a missing value routes to the missing bin, an unseen categorical level to the unknown slot, and a numeric value to its threshold interval.
The support weight¶
The heart of TRIAD is a per-bin support weight
the posterior weight an empirical-Bayes observer puts on the fitted bin value versus the population prior of 0 (shrinkage and support weights). Here \(v\) is the bin's sampling variance and \(\tau^2\) the between-bin signal variance of the term, estimated once per term by a mass-weighted method of moments (\(\max(0,\ \sum_b \pi_b f_b^2 - \sum_b \pi_b v_b)\)) or, optionally, 1-D marginal maximum likelihood. Two adjustments apply:
- bins with zero training mass get \(w = 0\) regardless of their nominal variance;
- values outside the training range are decayed by
\(\exp(-\lambda \cdot \text{dist}/\text{IQR})\) (exact mode only — see
decision 3), and flagged
ooveither way.
A term whose \(\hat\tau^2\) is exactly 0 is a pure-noise feature: its entire contribution is routed to D, with a warning.
Where does \(v\) come from? No mainstream GBM exposes usable per-feature epistemic
variance natively (decision 2), so TRIAD refits K bootstrap replicas
(default n_bags=8) and reads variance from their disagreement — per-bin shape
variance in exact mode, per-instance attribution variance in approximate mode. EBMs
are the exception: their outer-bag SDs are used directly. See
epistemic variance.
The three channels¶
With \(f\) and \(w\) in hand per (instance, term), the channels are one line of algebra:
D is deliberately computed as the residual \(f - w f\), never as \((1-w)f\), so
completeness holds to float roundoff (decision 11); the test suite
asserts term-level C1 at atol=1e-12. The M channel is the supported part of a
contribution that arrived through missing-value handling — for the thin-file
applicant, the planted +1.0 MNAR effect surfaces as positive M on x0.
Term channels are then aggregated to features. Single-feature terms map 1:1; a pair
term (EBM interactions) splits its channels equally between its two features
(pair_split="half"), which is why feature-level "observed ⇒ M = 0" weakens to
term level for pair-bearing models (decision 14).
Approximate mode: the same routing on TreeSHAP¶
An unconstrained booster has no per-feature term contributions, so exact mode does
not apply. Approximate mode keeps the identical channel algebra but starts from the
booster's native TreeSHAP attributions \(\phi_j(x)\) (pred_contrib=True, no shap
package): the support weight uses the per-instance variance of \(\phi_j\) across bag
replicas, \(\tau^2\) is estimated per feature on a reference population, and D is the
residual \(\phi - I - M\), so C1 holds exactly.
The label "approximate" is earned: \(\phi_j\) is not a function of \(x_j\) alone —
interaction effects leak into per-feature attributions — and there are no per-bin
audit tables (decision 5). No out-of-range decay is applied: in
sparse regions the replicas disagree on their own, so bag disagreement subsumes what
exact mode approximates with the decay heuristic (decision 3). On an
additive booster the two modes coincide — LightGBM's pred_contrib equals the
centered shape value to ~1e-14, an invariant the test suite exploits
(decision 12). See approximate mode.
From channels to decisions¶
Per instance, the channels frame is summarized by three epistemic scores:
ES measures how much of the score rests on weak support, ES_signed its net direction, and the knowledge ratio KR how evidence-based the score is overall — the thin-file applicant shows low KR.
reasons() then maps the channels to ranked adverse-action reasons: Group A
(derogatory information, driven by I) versus Group B (insufficient information,
driven by D + M, with a missing-vs-density subtype), with noise-suppression
thresholds so tiny epistemic residue never becomes a customer-facing reason. The
three-channel waterfall renders the same decomposition per instance — I solid, D
hatched, M gray. See reason codes and waterfalls.
Nothing ships unreconciled¶
Every explain call recomputes the model's raw score and compares it against the
channel totals plus the intercept; a discrepancy beyond 1e-6 raises
RuntimeError("TRIAD reconciliation failed: ...") rather than returning a silently
wrong decomposition. Externally, reconciliation_error verifies the same identity,
and channel_mass reports the portfolio share of mean |I|, |D| and |M|.
Where to go next¶
- Channels and guarantees — the invariants C1–C8 in detail.
- Shrinkage and support weights — τ² estimation and the audit payload.
- Epistemic variance — bagging, and why native GBM variance is not enough.
- Approximate mode — TreeSHAP reallocation and its limits.
- Reason codes and waterfalls — Group A vs Group B.
- Model adapters — LightGBM and EBM specifics.
- Synthetic credit walkthrough — the runnable notebook.
- API reference.