Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python | |
| """Evaluate BrainRL baselines with optional condition + subject splits. | |
| Examples | |
| -------- | |
| Evaluate every condition / every subject: | |
| python evaluate.py --episodes 32 | |
| Evaluate the test subjects on the single-male-narrator condition only: | |
| python evaluate.py \\ | |
| --episodes 32 \\ | |
| --condition single_m \\ | |
| --participant-info configs/participant_run_info.json \\ | |
| --train-subjects sub-01:sub-20 \\ | |
| --test-subjects sub-21:sub-26 \\ | |
| --split test \\ | |
| --output-csv outputs/eval/single_m_test.csv \\ | |
| --plot-dir outputs/eval/single_m_test | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import json | |
| from pathlib import Path | |
| from baselines import ( | |
| EpisodeResult, | |
| PromptPolicy, | |
| default_baselines, | |
| run_policy_episode, | |
| r2_curves_by_policy, | |
| summarize_results, | |
| ) | |
| from data_split import DEFAULT_PARTICIPANT_INFO, build_condition_split | |
| def build_parser() -> argparse.ArgumentParser: | |
| parser = argparse.ArgumentParser(description="Evaluate BrainRL policies") | |
| parser.add_argument("--episodes", type=int, default=8, help="Episodes per (subject, run) pair per policy") | |
| parser.add_argument("--seed", type=int, default=42, help="Base random seed") | |
| parser.add_argument( | |
| "--condition", | |
| choices=("single_m", "single_f", "mixed_m", "mixed_f"), | |
| default=None, | |
| help="Optional condition filter (uses participant_run_info.json).", | |
| ) | |
| parser.add_argument( | |
| "--participant-info", | |
| type=str, | |
| default=str(DEFAULT_PARTICIPANT_INFO), | |
| help="Path to participant_run_info.json.", | |
| ) | |
| parser.add_argument( | |
| "--train-subjects", | |
| type=str, | |
| default=None, | |
| help="Subject spec for the train split (e.g. sub-01:sub-20).", | |
| ) | |
| parser.add_argument( | |
| "--test-subjects", | |
| type=str, | |
| default=None, | |
| help="Subject spec for the test split (e.g. sub-21:sub-26).", | |
| ) | |
| parser.add_argument( | |
| "--split", | |
| choices=("train", "test", "all"), | |
| default="all", | |
| help="Which subject split to evaluate.", | |
| ) | |
| parser.add_argument( | |
| "--exclude-subjects", | |
| type=str, | |
| default=None, | |
| help=( | |
| "Comma list / range of subjects to drop from both train and test " | |
| "splits, e.g. 'sub-03,sub-18' for corrupted recordings." | |
| ), | |
| ) | |
| parser.add_argument( | |
| "--output-csv", | |
| type=str, | |
| default=None, | |
| help="Optional CSV path for summary rows.", | |
| ) | |
| parser.add_argument( | |
| "--plot-dir", | |
| type=str, | |
| default=None, | |
| help="Optional directory for baseline_comparison.png and r2_curves.png.", | |
| ) | |
| parser.add_argument( | |
| "--use-llm", | |
| action="store_true", | |
| help="Add prompt-based LLM policy to the OpenEnv comparison.", | |
| ) | |
| return parser | |
| def print_summary(rows: list[dict[str, object]], header: str) -> None: | |
| bar = "=" * 92 | |
| print(bar) | |
| print(header) | |
| print(bar) | |
| for row in rows: | |
| print( | |
| f"{row['policy']:>14} | " | |
| f"episodes={row['episodes']} | " | |
| f"mean_final_r2={float(row['mean_final_r2']):.4f} | " | |
| f"corr={float(row['mean_priority_correlation']):.4f} | " | |
| f"2v2={float(row['mean_2v2_accuracy']):.4f} | " | |
| f"mean_total_reward={float(row['mean_total_reward']):.4f} | " | |
| f"order={row['example_order']}" | |
| ) | |
| print(bar) | |
| def write_summary(path: Path, rows: list[dict[str, object]]) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| with path.open("w", encoding="utf-8", newline="") as handle: | |
| writer = csv.DictWriter(handle, fieldnames=list(rows[0].keys())) | |
| writer.writeheader() | |
| writer.writerows(rows) | |
| def _episode_pairs(args: argparse.Namespace) -> tuple[list[dict[str, str | None]], str]: | |
| """Resolve which (subject, run, condition) pairs to roll out.""" | |
| if args.condition is None: | |
| return [{"subject_id": None, "run_id": None, "condition": None}], "all-conditions" | |
| split = build_condition_split( | |
| condition=args.condition, | |
| participant_info_path=args.participant_info, | |
| train_subjects_spec=args.train_subjects, | |
| test_subjects_spec=args.test_subjects, | |
| exclude_subjects=args.exclude_subjects, | |
| ) | |
| pairs = split.pairs_for(args.split) | |
| if not pairs: | |
| raise SystemExit( | |
| f"No (subject, run) pairs available for condition={args.condition} split={args.split}." | |
| ) | |
| summary = split.summary() | |
| excluded = summary.get("excluded_subjects") or [] | |
| print( | |
| f"[split] condition={args.condition} split={args.split} " | |
| f"train_subjects={summary['n_train_subjects']} test_subjects={summary['n_test_subjects']} " | |
| f"selected_pairs={len(pairs)}" | |
| + (f" excluded={excluded}" if excluded else "") | |
| ) | |
| return [pair.as_dict() for pair in pairs], f"{args.condition}/{args.split}" | |
| def main() -> None: | |
| args = build_parser().parse_args() | |
| pairs, label = _episode_pairs(args) | |
| policies = default_baselines(seed=int(args.seed)) | |
| if args.use_llm: | |
| policies.append(PromptPolicy(use_llm=True)) | |
| results: list[EpisodeResult] = [] | |
| for policy in policies: | |
| for pair_idx, pair in enumerate(pairs): | |
| for episode_idx in range(int(args.episodes)): | |
| seed = int(args.seed) + pair_idx * 1009 + episode_idx | |
| results.append( | |
| run_policy_episode( | |
| policy=policy, | |
| seed=seed, | |
| subject_id=pair.get("subject_id"), | |
| run_id=pair.get("run_id"), | |
| condition=pair.get("condition"), | |
| ) | |
| ) | |
| rows = summarize_results(results, split_label=label) | |
| print_summary(rows, header=f"BrainRL policy comparison ({label})") | |
| if args.output_csv: | |
| out_csv = Path(args.output_csv).expanduser() | |
| write_summary(out_csv, rows) | |
| print(f"Wrote summary CSV: {out_csv}") | |
| if args.plot_dir: | |
| from plotting import plot_baseline_comparison, plot_r2_curves | |
| plot_dir = Path(args.plot_dir).expanduser() | |
| plot_dir.mkdir(parents=True, exist_ok=True) | |
| bar_path = plot_baseline_comparison(rows, plot_dir / "baseline_comparison.png") | |
| curves_path = plot_r2_curves( | |
| r2_curves_by_policy(results), | |
| plot_dir / "r2_curves.png", | |
| title=f"BrainRL R² curves ({label})", | |
| ) | |
| meta_path = plot_dir / "split_summary.json" | |
| with meta_path.open("w", encoding="utf-8") as handle: | |
| json.dump({"label": label, "n_pairs": len(pairs), "pairs": pairs}, handle, indent=2) | |
| print( | |
| f"Wrote plots to {plot_dir} " | |
| "(baseline_comparison.png, r2_curves.png)" | |
| ) | |
| if __name__ == "__main__": | |
| main() | |