Download code/scripts/test_data.py from nottygian/Leplanner: direct link, hf CLI and curl.
- Browser
- Download file 5.64 kB
-
https://huggingface.co/nottygian/Leplanner/resolve/main/code/scripts/test_data.py
- Command line
-
hf download hf://nottygian/Leplanner/code/scripts/test_data.py
-
curl -L -o test_data.py https://huggingface.co/nottygian/Leplanner/resolve/main/code/scripts/test_data.py
5.64 kB
| """Validate the latent dataset against the raw HDF5 and the world model. | |
| The indexing here is easy to get subtly wrong: pixels are strided by frameskip | |
| while actions are not, and the block leaving frame k must be the actions that | |
| actually produced frame k+1. This checks that alignment against ground truth, | |
| then confirms the frozen predictor can reach the sampled goals — if teacher | |
| forcing on real actions doesn't reduce the goal distance, the goals aren't | |
| reachable and the controller has no learnable signal. | |
| """ | |
| import sys | |
| from pathlib import Path | |
| import h5py | |
| import hdf5plugin # noqa: F401 -- blosc filter | |
| import numpy as np | |
| import torch | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from lejepa_control.data import ( # noqa: E402 | |
| FRAMESKIP, | |
| NUM_CONTEXT, | |
| LatentGoalDataset, | |
| split_episodes, | |
| ) | |
| from lejepa_control.rollout import goal_distance, rollout_plan # noqa: E402 | |
| from lejepa_control.world_model import load_lewm # noqa: E402 | |
| H5 = 'data/swm_home/datasets/pusht_expert_train.h5' | |
| IMAGENET_MEAN = torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1) | |
| IMAGENET_STD = torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1) | |
| def main(): | |
| device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| torch.manual_seed(0) | |
| ds = LatentGoalDataset('data/latents', max_offset=5) | |
| print(f'{len(ds)} clips over {len(ds.episodes)} episodes') | |
| f = h5py.File(H5, 'r') | |
| model = load_lewm(device=device) | |
| # --- cached latents match a fresh encode of the same raw frames ------- | |
| sample_idx = [0, 12345, 999999] | |
| for idx in sample_idx: | |
| ep = int(ds.clip_episode[idx]) | |
| start = int(ds.clip_start[idx]) | |
| base = int(ds.offsets[ep]) | |
| rows = [base + start + i * FRAMESKIP for i in range(NUM_CONTEXT)] | |
| px = torch.from_numpy(f['pixels'][rows[0] : rows[-1] + 1 : FRAMESKIP]) | |
| px = px.permute(0, 3, 1, 2).float() / 255.0 | |
| px = ((px - IMAGENET_MEAN) / IMAGENET_STD).unsqueeze(0).to(device) | |
| with torch.no_grad(): | |
| fresh = model.encode({'pixels': px})['emb'][0].cpu() | |
| cached = ds[idx]['context'] | |
| rel = (cached - fresh).norm() / fresh.norm() | |
| assert rel < 0.02, f'latent cache mismatch at {idx}: {rel}' | |
| print(f'latent cache matches fresh encode (rel err < 2%) at {sample_idx}') | |
| # --- action blocks are the raw actions between the context frames ----- | |
| idx = 12345 | |
| ep = int(ds.clip_episode[idx]) | |
| start = int(ds.clip_start[idx]) | |
| base = int(ds.offsets[ep]) | |
| item = ds[idx] | |
| for i in range(NUM_CONTEXT - 1): | |
| lo = base + start + i * FRAMESKIP | |
| raw = f['action'][lo : lo + FRAMESKIP] # (5, 2) | |
| want = ((raw - ds.action_mean) / ds.action_std).reshape(-1) | |
| got = item['past_actions'][i].numpy() | |
| assert np.allclose(got, want, atol=1e-5), f'block {i} misaligned' | |
| print('past action blocks align with raw actions between context frames') | |
| # --- goal offsets respect the curriculum ------------------------------ | |
| for max_offset in (2, 3, 5): | |
| ds.set_max_offset(max_offset) | |
| offsets = [int(ds[i]['goal_offset']) for i in range(200)] | |
| assert min(offsets) >= 1 and max(offsets) <= max_offset, offsets | |
| print(f' max_offset={max_offset}: sampled {sorted(set(offsets))}') | |
| # --- reachability: real actions must beat a do-nothing plan ----------- | |
| ds.set_max_offset(5) | |
| loader = torch.utils.data.DataLoader(ds, batch_size=64, shuffle=True) | |
| batch = next(iter(loader)) | |
| ctx = batch['context'].to(device) | |
| past = batch['past_actions'].to(device) | |
| goal = batch['goal'].to(device) | |
| real = batch['real_action'].to(device) | |
| # replay the real first block, then hold still | |
| H = 5 | |
| plan = torch.zeros(ctx.size(0), H, 10, device=device) | |
| plan[:, 0] = real | |
| with torch.no_grad(): | |
| pred = rollout_plan(model, ctx, past, plan) | |
| d_real = goal_distance(pred, goal) | |
| zero = torch.zeros_like(plan) | |
| d_zero = goal_distance(rollout_plan(model, ctx, past, zero), goal) | |
| # distance from the current frame to the goal, before acting at all | |
| d_start = (ctx[:, -1] - goal).pow(2).mean(-1) | |
| print(f'start distance to goal {d_start.mean():.4f}') | |
| print(f'after real 1st block (d_1) {d_real[:, 0].mean():.4f}') | |
| print(f'after zero 1st block (d_1) {d_zero[:, 0].mean():.4f}') | |
| assert d_real[:, 0].mean() < d_start.mean(), ( | |
| 'real actions do not move toward the goal — indexing is likely wrong' | |
| ) | |
| assert d_real[:, 0].mean() < d_zero[:, 0].mean(), ( | |
| 'real actions are no better than doing nothing' | |
| ) | |
| print('reachability ok: real actions reduce latent goal distance') | |
| # --- one-step goals: teacher forcing should nearly reach -------------- | |
| ds.set_max_offset(1) | |
| batch = next(iter(torch.utils.data.DataLoader(ds, batch_size=64, shuffle=True))) | |
| ctx = batch['context'].to(device) | |
| past = batch['past_actions'].to(device) | |
| goal = batch['goal'].to(device) | |
| plan = batch['real_action'].to(device).unsqueeze(1) | |
| with torch.no_grad(): | |
| d1 = goal_distance(rollout_plan(model, ctx, past, plan), goal)[:, 0] | |
| d0 = (ctx[:, -1] - goal).pow(2).mean(-1) | |
| print(f'1-step goal: before {d0.mean():.4f} -> after {d1.mean():.4f} ' | |
| f'({100 * (1 - d1.mean() / d0.mean()):.0f}% closed)') | |
| assert d1.mean() < 0.5 * d0.mean(), 'one-step goals not being reached' | |
| print('ALL DATA TESTS PASSED') | |
| if __name__ == '__main__': | |
| main() | |