Generic bootstrap bagging — the default epistemic-variance source.
No mainstream GBM exposes usable per-feature epistemic variance natively
(see docs/DECISIONS.md), so TRIAD refits K bootstrap replicas and reads
variance from their disagreement: per-bin shape variance in exact mode,
per-instance attribution variance in approximate mode.
fit_bagged
fit_bagged(
fit_fn: Callable[[DataFrame, ndarray, int], ModelT],
X: DataFrame,
y: ndarray,
*,
n_bags: int = 8,
seed: int = 0
) -> list[ModelT]
Fit n_bags replicas of a model on bootstrap resamples of (X, y).
fit_fn(X_boot, y_boot, bag_seed) receives a distinct seed per bag.
Bootstraps that collapse to a single class (binary targets with rare
positives) are redrawn.
Source code in src/triadxai/bagging.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 | def fit_bagged(
fit_fn: Callable[[pd.DataFrame, np.ndarray, int], ModelT],
X: pd.DataFrame,
y: np.ndarray,
*,
n_bags: int = 8,
seed: int = 0,
) -> list[ModelT]:
"""Fit ``n_bags`` replicas of a model on bootstrap resamples of (X, y).
``fit_fn(X_boot, y_boot, bag_seed)`` receives a distinct seed per bag.
Bootstraps that collapse to a single class (binary targets with rare
positives) are redrawn.
"""
y = np.asarray(y)
n = len(X)
if len(y) != n:
raise ValueError(f"X and y length mismatch: {n} vs {len(y)}")
classes = np.unique(y)
check_classes = len(classes) <= 20 # classification-like target
if check_classes and len(classes) < 2:
raise ValueError("y contains a single class; cannot fit bagged models")
rng = np.random.default_rng(seed)
models: list[ModelT] = []
for bag in range(n_bags):
for _ in range(_MAX_RESAMPLE_ATTEMPTS):
idx = rng.integers(0, n, size=n)
if not check_classes or len(np.unique(y[idx])) == len(classes):
break
else:
raise ValueError(
f"could not draw a bootstrap containing all classes in "
f"{_MAX_RESAMPLE_ATTEMPTS} attempts (bag {bag})"
)
X_boot = X.iloc[idx].reset_index(drop=True) if hasattr(X, "iloc") else X[idx]
bag_seed = seed * 10_000 + bag + 1
models.append(fit_fn(X_boot, y[idx], bag_seed))
return models
|
phi_variance
phi_variance(
predict_contrib_fns: Sequence[
Callable[[ndarray], ndarray]
],
X: ndarray,
) -> np.ndarray
Per-instance, per-feature variance of attributions across replicas.
Each callable maps X to an (n_samples, n_features) attribution matrix;
the result is the elementwise sample variance (ddof=1) across replicas.
Source code in src/triadxai/bagging.py
65
66
67
68
69
70
71
72
73
74
75
76
77 | def phi_variance(
predict_contrib_fns: Sequence[Callable[[np.ndarray], np.ndarray]],
X: np.ndarray,
) -> np.ndarray:
"""Per-instance, per-feature variance of attributions across replicas.
Each callable maps X to an (n_samples, n_features) attribution matrix;
the result is the elementwise sample variance (ddof=1) across replicas.
"""
if len(predict_contrib_fns) < 2:
raise ValueError("need at least 2 replicas to estimate variance")
stack = np.stack([fn(X) for fn in predict_contrib_fns], axis=0)
return stack.var(axis=0, ddof=1)
|