Skip to content

Add explainability support for cross-validation - #834

Merged
cristian-tamblay merged 5 commits into
developfrom
fix/explainers-cross-validation
Aug 24, 2026
Merged

Add explainability support for cross-validation#834
cristian-tamblay merged 5 commits into
developfrom
fix/explainers-cross-validation

Conversation

@Irozuku

@Irozuku Irozuku commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Cross-validation runs can now be explained. Until now they could not: the explainer job crashed on the fold shaped split_indexes payload with KeyError: 'train_indexes', and even past that there was nothing honest to explain, because the model that gets saved is refit on every row the folds used.

Cross-validation sessions now reserve a slice of the dataset (default 10%, configurable, 0 to disable) that the folds never touch. The final model never sees those rows, so they are what explanations are measured on. A run that reserved nothing is refused with a reason rather than explained on data the model already learned from.

Which partitions a run exposes is decided by the splitter that produced it, not by code that inspects the payload's shape, so a splitter with a different partitioning scheme, including one from a plugin, participates without changes to the explainability code or the frontend.


Type of Change

  • Backend change
  • Frontend change
  • CI / Workflow change
  • Build / Packaging change
  • Bug fix
  • Documentation

Changes (by file)

Backend

  • splitters/base_splitter.py: new contract, explainable_partitions() plus EVALUATION_PARTITION, naming the partition that holds rows the model has not seen. explainable_splits() is generic on top of it, so a splitter usually implements one method.
  • splitters/fold_splitter.py: carves the reserved rows before folding, then maps fold positions back to original row numbers so no fold ever sees a reserved row. The carve respects the splitter through one HOLDOUT_STRATEGY attribute: ShuffleSplit, StratifiedShuffleSplit, or GroupShuffleSplit so a group is never split across the carve. Declares its partitions as train and holdout.
  • splitters/holdout.py: declares its train / test / val partitions.
  • splitters/*.py (all seven fold splitters): new holdout schema field, float 0 to 0.5, default 0.1, so the wizard renders it automatically.
  • splitters/splits_payload.py: splitter_class_for() resolves a run's splitter from the registry; explainable_indexes() asks that class for the rows an explainer may use and refuses when the partition it evaluates on is empty. The previous shape sniffing is gone.
  • job/explainer_job.py: resolves the run's splitter and asks it which partitions exist, instead of indexing fixed keys. Accepts whichever partition name the splitter offers and maps it onto the dataset dictionary. Refuses deriving a split of a dataset the session never saw.
  • api/api_v1/endpoints/explainers.py: new GET /explainer/explainable-splits/{run_id}, returning the partitions and row counts a run can be explained on.

Frontend

  • explainers/SelectDatasetStep.jsx: renders the partitions the backend returns, with real row counts, replacing the hardcoded ["train","test","val","all"] and the fraction arithmetic that produced NaN / NaN for cross-validated sessions. The split choice only appears when the selected dataset is the session's own. Unknown partition names render via t(..., { defaultValue: name }), so a future backend-added partition needs no frontend change.
  • explainers/SplitSelector.jsx: deleted, unused and emitting a split name the backend never accepted.
  • api/explainer.ts: client for the new endpoint.
  • models/runResults/ResultsTabsHeader.jsx: disables the Explainability tab for a cross-validation run that reserved nothing, with the reason in the tooltip.

Testing

Inside the app: set a cross-validation session with the default 10% reserved, confirming that an explainer runs on the reserved set; one with the reserved field set to 0, confirming that the tab is disabled and displays its tooltip; and a group-based splitter, confirming that it moves whole groups so the reserved count is approximate rather than exact.

@Irozuku Irozuku added bug Something isn't working front Frontend work back Backend work labels Aug 21, 2026
@Irozuku Irozuku changed the title Fix/explainers cross validation Add Explainability Support for Cross-Validation Aug 21, 2026
@Irozuku Irozuku changed the title Add Explainability Support for Cross-Validation Add explainability support for cross-validation Aug 21, 2026
@Irozuku
Irozuku force-pushed the fix/explainers-cross-validation branch from d0b2e3c to b560dd4 Compare August 24, 2026 16:32
Base automatically changed from feat/splitter-schema-forms to develop August 24, 2026 20:32
@cristian-tamblay
cristian-tamblay force-pushed the fix/explainers-cross-validation branch from b560dd4 to d22fb91 Compare August 24, 2026 20:32
@cristian-tamblay
cristian-tamblay merged commit 1880c90 into develop Aug 24, 2026
20 checks passed
@cristian-tamblay
cristian-tamblay deleted the fix/explainers-cross-validation branch August 24, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

back Backend work bug Something isn't working front Frontend work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants