Spaces:
Runtime error
Runtime error
| """ | |
| LIME-style local surrogate explanation. | |
| Unlike shap_exact.py (which uses global knowledge of the model -- its | |
| coefficients), this implements the actual LIME idea from scratch: perturb | |
| the applicant's features randomly in a neighborhood around their real | |
| values, get the real model's prediction for each perturbed point, then fit | |
| a simple local linear regression to those (perturbation, prediction) pairs. | |
| The regression's coefficients are the "LIME weights" -- an explanation that | |
| never looked at the model's internals, only its input/output behavior. | |
| Running both this and the exact SHAP values and checking they agree (see | |
| app.py's "cross-check" panel) is a legitimate audit technique: two | |
| independently-derived explanations of the same decision converging is much | |
| stronger evidence than either alone -- and if they diverge, that's a signal | |
| worth investigating before trusting the explanation. | |
| """ | |
| from __future__ import annotations | |
| import random | |
| import numpy as np | |
| from sklearn.linear_model import LinearRegression | |
| from model import TrainedModel, FEATURES, FEATURE_LABELS, predict_proba | |
| def lime_explain(trained: TrainedModel, applicant: dict, n_samples: int = 400, seed: int = 0) -> dict: | |
| rng = np.random.default_rng(seed) | |
| x0 = np.array([applicant[f] for f in FEATURES], dtype=float) | |
| # feature-wise perturbation scale: 15% of the training population's std, | |
| # so the local neighborhood is meaningful relative to each feature's range | |
| stds = np.array([max(trained.feature_means[f] * 0.15, 0.5) for f in FEATURES]) | |
| samples = x0 + rng.normal(0, 1, size=(n_samples, len(FEATURES))) * stds | |
| samples = np.clip(samples, 0, None) | |
| preds = [] | |
| for row in samples: | |
| applicant_perturbed = dict(zip(FEATURES, row)) | |
| preds.append(predict_proba(trained, applicant_perturbed)) | |
| preds = np.array(preds) | |
| # weight samples by proximity to x0 (closer perturbations matter more -- | |
| # the defining idea of LIME's "local" fidelity) | |
| dists = np.linalg.norm((samples - x0) / stds, axis=1) | |
| weights = np.exp(-(dists ** 2) / 2) | |
| local_model = LinearRegression() | |
| local_model.fit(samples, preds, sample_weight=weights) | |
| lime_weights = dict(zip(FEATURES, local_model.coef_)) | |
| local_r2 = local_model.score(samples, preds, sample_weight=weights) | |
| return { | |
| "weights": lime_weights, | |
| "local_fidelity_r2": float(local_r2), | |
| "n_samples": n_samples, | |
| } | |