summaryrefslogtreecommitdiff
path: root/experiments/analyze_babyai_shared_b1.py
blob: ebcdb17233867ef439eb40002817a90ba2cbb832 (plain)
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
#!/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()