Spaces:
Sleeping
Sleeping
File size: 6,987 Bytes
32d14f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 | #!/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()
|