Leplanner / code /report /recover_profiles.py
nottygian's picture
Push code package
872cf4d verified
Raw History Blame Contribute Delete
3.02 kB
"""Recover the per-q validation distance profile for checkpoints that predate
the profile logging in ``train_controller.py``.
The original controller was trained before ``evaluate()`` recorded
``val_profile``, so its arrival-timing signature — the thing Figure 2 shows —
is missing from its checkpoint while every later run has it. This recomputes
it with the same function, the same held-out split, and the same batch order,
and writes it next to the diagnostics so the figure script can read it.
python report/recover_profiles.py --controllers data/runs/controller/controller.pt
"""
import argparse
import json
import sys
from pathlib import Path
import torch
from torch.utils.data import DataLoader
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from lejepa_control.data import LatentGoalDataset, split_episodes # noqa: E402
from lejepa_control.solver import load_controller # noqa: E402
from lejepa_control.world_model import load_lewm # noqa: E402
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from train_controller import evaluate # noqa: E402
def main():
p = argparse.ArgumentParser()
p.add_argument('--controllers', nargs='+',
default=['data/runs/controller/controller.pt'])
p.add_argument('--latents', default='data/latents')
p.add_argument('--batch-size', type=int, default=128)
p.add_argument('--out', default='data/runs/diagnostics/profiles_posthoc.json')
args = p.parse_args()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
stats = json.loads((Path(args.latents) / 'stats.json').read_text())
model = load_lewm(device=device)
_, val_eps = split_episodes(stats['n_episodes'])
val_set = LatentGoalDataset(
args.latents, max_offset=5, episodes=val_eps, horizon=5
)
out = {}
for path in args.controllers:
name = Path(path).parent.name
controller, ckpt = load_controller(
path, latent_dim=stats['latent_dim'], device=device
)
# same batches for every checkpoint, so profiles are comparable
loader = DataLoader(
val_set, batch_size=args.batch_size, shuffle=True,
generator=torch.Generator().manual_seed(0)
)
val = evaluate(controller, model, loader, device)
out[name] = {
'step': int(ckpt['step']),
'terminal': val['terminal'],
'first': val['first'],
'arrival': val['arrival'],
'profile': val['profile'],
}
print(f'{name}: step {ckpt["step"]} terminal {val["terminal"]:.4f} '
f'arrival {val["arrival"]:.4f}')
for q, prof in val['profile'].items():
print(f' q={q}: {prof}')
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
Path(args.out).write_text(json.dumps(out, indent=2))
print(f'\nwrote {args.out}')
if __name__ == '__main__':
main()