Download src/train_bc.py from Conditiondark/ogbench-cube-rigid-body-artifacts: direct link, hf CLI and curl.
- Browser
- Download file 4.81 kB
-
https://huggingface.co/Conditiondark/ogbench-cube-rigid-body-artifacts/resolve/main/src/train_bc.py
- Command line
-
hf download hf://Conditiondark/ogbench-cube-rigid-body-artifacts/src/train_bc.py
-
curl -L -o train_bc.py https://huggingface.co/Conditiondark/ogbench-cube-rigid-body-artifacts/resolve/main/src/train_bc.py
4.81 kB
| """Goal-conditioned behavior cloning (compact BC baseline) on OGBench state data. | |
| Trains a small MLP policy pi(obs, goal) -> action with MSE loss and hindsight | |
| future goals sampled within the same trajectory. Trajectory-level nested demo | |
| subsets (10 subset of 100 subset of 1000 ...) via a fixed permutation seed. | |
| """ | |
| import argparse, os, json, time | |
| import numpy as np, torch, torch.nn as nn | |
| import ogbench | |
| from common import get_traj_bounds, nested_traj_order, seed_everything | |
| class MLPPolicy(nn.Module): | |
| def __init__(self, in_dim, act_dim, hidden=512, layers=3): | |
| super().__init__() | |
| mods, d = [], in_dim | |
| for _ in range(layers): | |
| mods += [nn.Linear(d, hidden), nn.ReLU()] | |
| d = hidden | |
| mods += [nn.Linear(d, act_dim)] | |
| self.net = nn.Sequential(*mods) | |
| def forward(self, obs, goal): | |
| return torch.tanh(self.net(torch.cat([obs, goal], dim=-1))) | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--dataset", default="cube-single-play-v0") | |
| ap.add_argument("--demos", type=int, default=1000) | |
| ap.add_argument("--steps", type=int, default=100000) | |
| ap.add_argument("--batch", type=int, default=1024) | |
| ap.add_argument("--hidden", type=int, default=512) | |
| ap.add_argument("--layers", type=int, default=3) | |
| ap.add_argument("--lr", type=float, default=3e-4) | |
| ap.add_argument("--seed", type=int, default=0) | |
| ap.add_argument("--split_seed", type=int, default=0) | |
| ap.add_argument("--outdir", default="runs") | |
| args = ap.parse_args() | |
| seed_everything(args.seed) | |
| dev = "cuda" if torch.cuda.is_available() else "cpu" | |
| t0 = time.time() | |
| env, train_ds, _ = ogbench.make_env_and_datasets(args.dataset, compact_dataset=False) | |
| obs = train_ds["observations"].astype(np.float32) | |
| act = train_ds["actions"].astype(np.float32) | |
| term = train_ds["terminals"].astype(np.float32) | |
| bounds = get_traj_bounds(term) | |
| order = nested_traj_order(len(bounds), seed=args.split_seed) | |
| sel = np.sort(order[:args.demos]) # selected trajectory ids (nested by prefix) | |
| # per selected transition: global index gi and its trajectory end te (for future-goal sampling) | |
| gi, te = [], [] | |
| for ti in sel: | |
| s, e = bounds[int(ti)] | |
| gi.append(np.arange(s, e)); te.append(np.full(e - s, e)) | |
| gi = np.concatenate(gi); te = np.concatenate(te) | |
| # normalization stats from the SELECTED observations only | |
| obs_mean = obs[gi].mean(0); obs_std = obs[gi].std(0) + 1e-6 | |
| # move to GPU | |
| obs_t = torch.tensor((obs - obs_mean) / obs_std, device=dev) # full, normalized | |
| act_t = torch.tensor(act, device=dev) | |
| gi_t = torch.tensor(gi, device=dev, dtype=torch.long) | |
| te_t = torch.tensor(te, device=dev, dtype=torch.long) | |
| N = gi_t.numel() | |
| model = MLPPolicy(obs.shape[1] * 2, act.shape[1], args.hidden, args.layers).to(dev) | |
| opt = torch.optim.Adam(model.parameters(), lr=args.lr) | |
| print(f"[train] dataset={args.dataset} demos={args.demos} trajs_selected={len(sel)} " | |
| f"transitions={N} obs_dim={obs.shape[1]} act_dim={act.shape[1]} device={dev}") | |
| model.train() | |
| for step in range(1, args.steps + 1): | |
| p = torch.randint(0, N, (args.batch,), device=dev) | |
| cur = gi_t[p] # current global idx | |
| end = te_t[p] # trajectory end (exclusive) | |
| u = torch.rand(args.batch, device=dev) | |
| gj = (cur + (u * (end - cur).float()).long()).clamp(max=obs_t.shape[0] - 1) # future goal idx | |
| o = obs_t[cur]; g = obs_t[gj]; a = act_t[cur] | |
| pred = model(o, g) | |
| loss = ((pred - a) ** 2).mean() | |
| opt.zero_grad(); loss.backward(); opt.step() | |
| if step % 5000 == 0 or step == 1: | |
| print(f" step {step:6d}/{args.steps} mse={loss.item():.4f} " | |
| f"elapsed={time.time()-t0:.0f}s", flush=True) | |
| run = f"bc_{args.dataset}_demos{args.demos}_seed{args.seed}" | |
| outdir = os.path.join(args.outdir, run); os.makedirs(outdir, exist_ok=True) | |
| torch.save({ | |
| "model": model.state_dict(), | |
| "obs_mean": obs_mean, "obs_std": obs_std, | |
| "config": vars(args), | |
| "obs_dim": int(obs.shape[1]), "act_dim": int(act.shape[1]), | |
| "hidden": args.hidden, "layers": args.layers, | |
| }, os.path.join(outdir, "ckpt.pt")) | |
| # immutable split manifest (trajectory ids used) -> reproducibility deliverable | |
| with open(os.path.join(outdir, "manifest.json"), "w") as f: | |
| json.dump({"dataset": args.dataset, "demos": args.demos, | |
| "split_seed": args.split_seed, "trajectory_ids": sel.tolist(), | |
| "n_transitions": int(N)}, f) | |
| print(f"[train] saved -> {outdir} ({time.time()-t0:.0f}s total)") | |
| if __name__ == "__main__": | |
| main() | |