summaryrefslogtreecommitdiff
path: root/experiments/analyze_babyai_shared_b1.py
diff options
context:
space:
mode:
Diffstat (limited to 'experiments/analyze_babyai_shared_b1.py')
-rw-r--r--experiments/analyze_babyai_shared_b1.py127
1 files changed, 127 insertions, 0 deletions
diff --git a/experiments/analyze_babyai_shared_b1.py b/experiments/analyze_babyai_shared_b1.py
new file mode 100644
index 0000000..ebcdb17
--- /dev/null
+++ b/experiments/analyze_babyai_shared_b1.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python3
+"""Summarize the frozen BabyAI GoToObj shared-feedback endpoint."""
+
+import argparse
+import json
+from pathlib import Path
+import statistics
+
+import gymnasium as gym
+import minigrid # noqa: F401 - registers BabyAI environments
+
+
+ROOT = Path(__file__).resolve().parents[1]
+DEFAULT_RESULTS = ROOT / "results" / "babyai_shared" / "b1"
+DEFAULT_OUT = ROOT / "results" / "babyai_shared" / "b1_analysis.json"
+SEEDS = (4101, 4102, 4103)
+CONDITIONS = ("bp", "clean_kp", "raw_shared", "sdil")
+
+
+def summarize(values):
+ return {
+ "values": values,
+ "mean": statistics.mean(values),
+ "sample_std": statistics.stdev(values),
+ }
+
+
+def object_counts(env_id, seeds):
+ counts = []
+ for seed in seeds:
+ env = gym.make(env_id)
+ try:
+ env.reset(seed=seed)
+ counts.append(sum(
+ cell is not None and cell.type in {"ball", "box", "key"}
+ for cell in env.unwrapped.grid.grid))
+ finally:
+ env.close()
+ return counts
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--results", type=Path, default=DEFAULT_RESULTS)
+ parser.add_argument("--out", type=Path, default=DEFAULT_OUT)
+ args = parser.parse_args()
+ records = {}
+ for seed in SEEDS:
+ for condition in CONDITIONS:
+ path = args.results / f"seed{seed}_{condition}.json"
+ with open(path, encoding="utf-8") as handle:
+ records[(seed, condition)] = json.load(handle)
+
+ condition_summary = {}
+ for condition in CONDITIONS:
+ condition_records = [records[(seed, condition)] for seed in SEEDS]
+ condition_summary[condition] = {
+ "rollout_success_percent": summarize([
+ 100.0 * row["rollout"]["success"]
+ for row in condition_records]),
+ "expert_action_accuracy_percent": summarize([
+ 100.0 * row["validation"]["accuracy"]
+ for row in condition_records]),
+ "mission_lesion_rollout_success_percent": summarize([
+ 100.0 * row["mission_lesion_rollout"]["success"]
+ for row in condition_records]),
+ "training_wall_seconds": summarize([
+ row["training"]["training_wall_seconds"]
+ for row in condition_records]),
+ "all_finite": all(row["finite"] for row in condition_records),
+ }
+ rollout_gaps = [100.0 * (
+ records[(seed, "sdil")]["rollout"]["success"]
+ - records[(seed, "raw_shared")]["rollout"]["success"])
+ for seed in SEEDS]
+ action_gaps = [100.0 * (
+ records[(seed, "sdil")]["validation"]["accuracy"]
+ - records[(seed, "raw_shared")]["validation"]["accuracy"])
+ for seed in SEEDS]
+ counts = object_counts("BabyAI-GoToObjS6-v1", range(256))
+ task_required = min(counts) > 1
+ checks = {
+ "all_runs_finite": all(
+ row["finite"] for row in records.values()),
+ "bp_and_clean_kp_mean_rollout_at_least_80": (
+ condition_summary["bp"]["rollout_success_percent"]["mean"] >= 80
+ and condition_summary["clean_kp"]
+ ["rollout_success_percent"]["mean"] >= 80),
+ "sdil_rollout_above_raw_every_seed": all(
+ value > 0 for value in rollout_gaps),
+ "mission_structurally_required": task_required,
+ }
+ report = {
+ "stage": "babyai_shared_b1_analysis",
+ "gate": "pass" if all(checks.values()) else "fail",
+ "checks": checks,
+ "conditions": condition_summary,
+ "paired_sdil_minus_raw": {
+ "rollout_success_points": summarize(rollout_gaps),
+ "expert_action_accuracy_points": summarize(action_gaps),
+ },
+ "task_structure_audit": {
+ "env_id": "BabyAI-GoToObjS6-v1",
+ "audited_episode_seeds": 256,
+ "minimum_task_object_count": min(counts),
+ "maximum_task_object_count": max(counts),
+ "mission_structurally_required_to_identify_the_only_object": (
+ task_required),
+ "interpretation": (
+ "The sole object is the target, so the mission does not select "
+ "among alternatives. Mission lesion is an input ablation, not "
+ "evidence that language is required by the task."),
+ },
+ "test_split_generated_or_read": False,
+ "decision": (
+ "Retain as an implementation diagnostic; do not use as the paper's "
+ "task-required shared-feedback result. Move to PickupLoc."),
+ }
+ args.out.parent.mkdir(parents=True, exist_ok=True)
+ with open(args.out, "w", encoding="utf-8") as handle:
+ json.dump(report, handle, indent=2, sort_keys=True)
+ handle.write("\n")
+ print(json.dumps(report, indent=2, sort_keys=True))
+
+
+if __name__ == "__main__":
+ main()