Local, model-agnostic explanations: LIME surrogates and counterfactuals.
Functions
Explain one prediction by fitting a local linear model around it.
predict is ever called, this works on any model, including ones TreeExplainer cannot touch.Overview
- Draw perturbations by sampling each feature from the background's
- Query the model on every perturbation.
-
Weight each by an exponential kernel on its distance from
x. - Fit a weighted ridge regression; report its coefficients.
Theory
LIME solves
over interpretable models g, with locality supplied by the kernel
Distances are measured in units of the background's standard deviation, so a feature measured in millions does not swamp one measured in units.
Parameters
estimator
predict or predict_proba is used.
x
background
class_index
n_samples
kernel_width
n_features
None keeps all.
feature_names
random_state
Returns
explanation
values holds one coefficient per feature. metadata carries intercept, local_r2 — the surrogate's weighted fit quality — and prediction.
Notes
Complexity. One prediction pass over n_samples rows, plus a least-squares solve. Explaining a whole dataset means repeating that per row, which is why LIME is a tool for inspecting individual decisions rather than summarising a model.
Check local_r2 before believing the coefficients. They describe the surrogate, and if the surrogate fits badly the explanation describes nothing. A low value means the model is not locally linear at this point, and the honest response is to say so rather than to read the coefficients anyway.
LIME is not additive and its explanations are not stable. Unlike TreeExplainer, coefficients carry no guarantee of summing to the prediction, and re-running with a different seed can shift them — markedly so when n_samples is small or the kernel is narrow. If the model is a TuiML tree or forest, prefer TreeExplainer: it is exact, deterministic and cheaper.
Perturbing features independently also fabricates rows the data never contains, the same criticism that applies to partial_dependence.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import lime_explain
>>> from tuiml.algorithms.trees import RandomForestRegressor
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(300, 4))
>>> y = 5.0 * X[:, 2] + rng.normal(0, 0.1, 300)
>>> model = RandomForestRegressor(n_estimators=40, random_state=0).fit(X, y)
>>> result = lime_explain(model, X[0], background=X, random_state=0)
>>> result.top(1)[0][0]
'feature_2'
>>> bool(result.metadata['local_r2'] > 0.5) # the surrogate fits
True
Find the smallest change that would flip the prediction.
Overview
- Find the nearest background sample the model already assigns to the
-
Walk each of its differing features back towards
x, keeping a
- What survives is a sparse, minimal set of changes.
Parameters
estimator
predict.
x
background
target
feature_names
max_features_changed
Returns
explanation
values holds the change required per feature, zero where nothing need change. metadata carries the full counterfactual row, n_changed, distance and found.
Notes
Complexity. One prediction pass over the background, then at most one per differing feature.
A counterfactual is not advice. It says the model would decide differently, not that acting on it would change the real outcome — those coincide only if the model is causal, which it generally is not. It also describes one route among many; a different search returns a different, equally valid answer.
found is False when no background sample is classified as the target, which usually means the class is absent from the background rather than that no counterfactual exists.
References
See Also
>>> import numpy as np
>>> from tuiml.explain import counterfactual
>>> from tuiml.algorithms.trees import DecisionTreeClassifier
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(300, 3))
>>> y = (X[:, 0] > 0).astype(int) # only feature 0 decides
>>> model = DecisionTreeClassifier(max_depth=4).fit(X, y)
>>> negative = X[model.predict(X) == 0][0]
>>> result = counterfactual(model, negative, background=X, target=1)
>>> bool(result.metadata['found'])
True
>>> int(result.metadata['n_changed']) # one feature suffices
1