Global model explanations: a surrogate tree and feature-interaction strength.
Functions
Approximate a model with a single readable decision tree.
TreeExplainer, plotting, partial_dependence — then works on the explanation.Overview
-
Collect the model's predictions on
X. - Fit a decision tree to those predictions, keeping the depth small.
- Return the fitted tree; its accuracy on the predictions is the
fidelity.Parameters
estimator
X
max_depth
random_state
Returns
explanation
values holds nothing — the payload is metadata['tree'], the fitted surrogate. metadata['fidelity'] is the surrogate's R² or accuracy on the model's predictions.
Notes
Fidelity is the number that decides whether to trust it. A surrogate that reproduces the model's predictions faithfully is a fair summary of the model; one that does not is a fair summary of nothing. There is no universal threshold — check it against a plain baseline — but a surrogate barely better than guessing says the model has no shallow structure, which is itself a useful thing to learn.
The surrogate describes global behaviour where the data is dense and nothing at all where it is sparse. It will also, by construction, miss detail the shallow tree cannot represent — that is the price of readability, and it is exactly the trade TreeExplainer avoids by being per-sample and exact.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import surrogate_tree
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(400, 4))
>>> y = 3.0 * X[:, 0] - X[:, 1] + rng.normal(0, 0.2, 400)
>>> model = RandomForestRegressor(n_estimators=60, random_state=0).fit(X, y)
>>> result = surrogate_tree(model, X, max_depth=3, random_state=0)
>>> result.metadata['fidelity'] > 0.8
True
>>> result.metadata['tree'].tree_ is not None
True
Quantify how much two features interact, in the Friedman sense.
Overview
- Compute the two single-feature partial-dependence functions, which
- Compute the joint partial-dependence surface, which captures both
- H^2 is the variance in the joint surface left unexplained by the
Theory
With PD_{jk} the joint partial dependence and PD_j, PD_k the individual ones,
where the tilde marks centred partial dependences, each shifted so its mean over the evaluation grid is zero. Centring matters: an uncentred model with a constant level c would report an interaction of H^2 = 1 between two features it does not use, because the flat joint surface c compared against two flat curves summing to 2c leaves a residual of -c.
evaluated over the grid of observed values. A model with no interaction has PD_{jk} = PD_j + PD_k exactly, so H^2 = 0; the more of the surface the two single curves fail to explain, the closer it gets to 1.
Parameters
estimator
X
feature_a, feature_b
n_points
random_state
Returns
h_squared
[0, 1]. Roughly: below 0.01 negligible, above 0.1 worth investigating. Values above 1 are possible when the single curves happen to anti-correlate with the joint surface; treat them as "strong", not as a violation.
Notes
Complexity. O(n^2_{\text{points}}) prediction passes over X, so keep n_points modest — 20 gives 400 passes.
A low value is an assumption confirmed, not a null result. H measures the interaction on the grid of observed values; an interaction confined to a region the data never visits is invisible to it. And like partial_dependence, the marginalisation can evaluate the model on feature combinations the data does not contain.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import friedman_h_statistic
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(400, 3))
>>> additive = 3.0 * X[:, 0] + 2.0 * X[:, 1] + rng.normal(0, 0.2, 400)
>>> interaction = 3.0 * X[:, 0] * X[:, 1] + rng.normal(0, 0.2, 400)
>>> add = RandomForestRegressor(n_estimators=40, random_state=0).fit(X, additive)
>>> inter = RandomForestRegressor(n_estimators=40, random_state=0).fit(X, interaction)
>>> h_add = friedman_h_statistic(add, X, 0, 1, n_points=8, random_state=0)
>>> h_inter = friedman_h_statistic(inter, X, 0, 1, n_points=8, random_state=0)
>>> bool(h_inter > h_add)
True