Download scripts/analyze_convergence.py from cudabenchmarktest/dropbear-locomotion: direct link, hf CLI and curl.
- Browser
- Download file 4.12 kB
-
https://huggingface.co/cudabenchmarktest/dropbear-locomotion/resolve/main/scripts/analyze_convergence.py
- Command line
-
hf download hf://cudabenchmarktest/dropbear-locomotion/scripts/analyze_convergence.py
-
curl -L -o analyze_convergence.py https://huggingface.co/cudabenchmarktest/dropbear-locomotion/resolve/main/scripts/analyze_convergence.py
4.12 kB
| #!/usr/bin/env python3 | |
| """Analyze sustained Dropbear learning trends from TensorBoard events.""" | |
| from __future__ import annotations | |
| import argparse | |
| from pathlib import Path | |
| import numpy as np | |
| from tensorboard.backend.event_processing.event_accumulator import EventAccumulator | |
| WORKSPACE_ROOT = Path(__file__).resolve().parents[1] | |
| LOG_ROOT = WORKSPACE_ROOT / "logs" / "rsl_rl" / "dropbear_velocity" | |
| TAGS = { | |
| "reward": "Train/mean_reward", | |
| "ep_len": "Train/mean_episode_length", | |
| "lin_track": "Episode_Reward/track_lin_vel_xy", | |
| "vel_err": "Metrics/base_velocity/error_vel_xy", | |
| "gait": "Episode_Reward/gait", | |
| "cmd_lvl": "Curriculum/lin_vel_cmd_levels", | |
| "act_std": "Policy/mean_std", | |
| "bad_orient": "Episode_Termination/bad_orientation", | |
| "fps": "Perf/total_fps", | |
| } | |
| def scalar_series(accumulator: EventAccumulator, tag: str) -> tuple[np.ndarray, np.ndarray]: | |
| events = accumulator.Scalars(tag) | |
| steps = np.asarray([event.step for event in events], dtype=np.float64) | |
| values = np.asarray([event.value for event in events], dtype=np.float64) | |
| return steps, values | |
| def summarize(values: np.ndarray, steps: np.ndarray, window: int) -> tuple[float, float, float]: | |
| count = min(window, values.size) | |
| recent_values = values[-count:] | |
| recent_steps = steps[-count:] | |
| mean = float(np.mean(recent_values)) | |
| std = float(np.std(recent_values)) | |
| if count < 2 or np.ptp(recent_steps) == 0: | |
| slope_per_100 = 0.0 | |
| else: | |
| slope_per_100 = float(np.polyfit(recent_steps, recent_values, 1)[0] * 100.0) | |
| return mean, std, slope_per_100 | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("run", help="Run directory or its basename") | |
| parser.add_argument("--window", type=int, default=25, help="Recent iterations to summarize") | |
| args = parser.parse_args() | |
| run_dir = Path(args.run) | |
| if not run_dir.is_dir(): | |
| run_dir = LOG_ROOT / args.run | |
| event_files = sorted(run_dir.glob("events.out*"), key=lambda path: path.stat().st_mtime) | |
| if not event_files: | |
| raise SystemExit(f"No TensorBoard event file in {run_dir}") | |
| accumulator = EventAccumulator(str(event_files[-1]), size_guidance={"scalars": 0}) | |
| accumulator.Reload() | |
| scalar_tags = set(accumulator.Tags().get("scalars", [])) | |
| print(f"run: {run_dir.name}") | |
| print(f"window: last {args.window} iterations") | |
| print("metric latest mean std slope/100") | |
| print("----------- -------- -------- -------- ---------") | |
| history = 0 | |
| summaries: dict[str, tuple[float, float, float, float]] = {} | |
| for label, tag in TAGS.items(): | |
| if tag not in scalar_tags: | |
| continue | |
| steps, values = scalar_series(accumulator, tag) | |
| history = max(history, values.size) | |
| mean, std, slope = summarize(values, steps, args.window) | |
| latest = float(values[-1]) | |
| summaries[label] = (latest, mean, std, slope) | |
| print(f"{label:11} {latest:8.3f} {mean:8.3f} {std:8.3f} {slope:9.3f}") | |
| print(f"\nhistory: {history} logged iterations") | |
| if history < max(100, args.window * 4): | |
| print("assessment: EARLY — insufficient history for a convergence claim") | |
| return | |
| command = summaries.get("cmd_lvl", (0.0, 0.0, 0.0, 0.0))[1] | |
| tracking = summaries.get("lin_track", (0.0, 0.0, 0.0, 0.0))[1] | |
| episode_length = summaries.get("ep_len", (0.0, 0.0, 0.0, 0.0))[1] | |
| bad_orientation = summaries.get("bad_orient", (1.0, 1.0, 0.0, 0.0))[1] | |
| reward_slope = summaries.get("reward", (0.0, 0.0, 0.0, 1.0))[3] | |
| gates = { | |
| "full command curriculum": command >= 1.0, | |
| "linear tracking >= 0.65": tracking >= 0.65, | |
| "episode length >= 700": episode_length >= 700.0, | |
| "bad orientation <= 0.30": bad_orientation <= 0.30, | |
| "reward plateau |slope/100| <= 1": abs(reward_slope) <= 1.0, | |
| } | |
| for name, passed in gates.items(): | |
| print(f"{'PASS' if passed else 'WAIT'}: {name}") | |
| print(f"assessment: {'CONVERGED CANDIDATE' if all(gates.values()) else 'LEARNING'}") | |
| if __name__ == "__main__": | |
| main() | |