| |
| """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() |
|
|