Files
hypertower/v4/scripts/analysis/fused_importance_with_axial.py
T
rpotter6298 3d7777f010 Add new experiments and analysis scripts for dropzero features
- Introduced `fused_importance_with_axial.py` to evaluate the importance of Axial_Length in the fused-head model.
- Created JSON configurations for various experiments excluding zero-importance clinical features:
  - `cd_solo_bilateral_dropzero.json`: Bilateral clinical-only evaluation.
  - `cd_solo_single_dropzero.json`: Single-eye clinical-only evaluation.
  - `ensemble_refugelike_ckpt_dropzero.json`: Ensemble model with dropped zero-importance features.
  - `ensemble_single_refugelike_dropzero.json`: Single-eye ensemble model with dropped features.
2026-08-24 12:26:16 +02:00

76 lines
2.9 KiB
Python

"""Re-run the fused-head permutation importance on the with-Axial_Length model.
Points F8's make_fused_clinical_importance at the sensitivity checkpoints
(refugelike_ensemble_with_axial_length, 10 reps with save_checkpoints=true and
exclude_cols=[]) instead of the standard ensemble_refugelike_ckpt run. This
tests whether Axial_Length lands at ~0 fused-head importance like the other
four dropzero features, closing the circle on the historical decision to
exclude it upstream.
Output is routed to output/S8e_fused_clinical_importance_with_axial.{png,csv}
so it does not overwrite the main S8e figure.
Usage:
python -m v4.scripts.analysis.fused_importance_with_axial [--n-permutations N]
"""
from __future__ import annotations
import argparse
from pathlib import Path
from v4.figures import F8_explainability as F8
from v4.figures.util.loaders import REPO_ROOT
AXIAL_RUN = (
REPO_ROOT / "v4" / "results" / "experiments" / "sensitivity"
/ "refugelike_ensemble_with_axial_length" / "rep00" / "binary"
)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--n-permutations", type=int, default=30,
help="Permutation repeats per feature (default 30).")
args = ap.parse_args()
if not AXIAL_RUN.exists():
raise SystemExit(f"Sensitivity run not found: {AXIAL_RUN}")
# Point the F8 module at the sensitivity run. rep_base is derived as
# V4_CKPT_RUN.parent.parent inside make_fused_clinical_importance, so
# all 10 reps under refugelike_ensemble_with_axial_length are picked up.
F8.V4_CKPT_RUN = AXIAL_RUN
# Route the output filenames so we do not overwrite the primary S8e run.
orig_savefig = F8.plt.Figure.savefig # noqa: E501 (untouched, just noted)
orig_out_dir = F8.OUT_DIR
tag = "with_axial"
# Monkey-patch pandas.DataFrame.to_csv and Figure.savefig only for calls
# that name the S8e file. Simplest: swap out OUT_DIR and rename the two
# target filenames post-hoc via a wrapper.
src_png = orig_out_dir / "S8e_fused_clinical_importance.png"
src_csv = orig_out_dir / "S8e_fused_clinical_importance.csv"
dst_png = orig_out_dir / f"S8e_fused_clinical_importance_{tag}.png"
dst_csv = orig_out_dir / f"S8e_fused_clinical_importance_{tag}.csv"
# Move originals aside if present, restore after; simpler than hooking I/O.
def _stash(p: Path):
return p.rename(p.with_suffix(p.suffix + ".bak")) if p.exists() else None
stashed = [_stash(src_png), _stash(src_csv)]
try:
F8.make_fused_clinical_importance(n_permutations=args.n_permutations)
if src_png.exists(): src_png.rename(dst_png)
if src_csv.exists(): src_csv.rename(dst_csv)
print(f"\nRenamed outputs -> {dst_png.name}, {dst_csv.name}")
finally:
for p in stashed:
if p is not None:
p.rename(p.with_suffix(""))
if __name__ == "__main__":
main()