File size: 3,848 Bytes
de3e3f2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | #!/usr/bin/env python3
"""Render a compact release report from one RSL-RL TensorBoard event file."""
from __future__ import annotations
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from tensorboard.backend.event_processing.event_accumulator import (
EventAccumulator,
)
TAGS = {
"reward": "Train/mean_reward",
"episode_length": "Train/mean_episode_length",
"velocity_error": "Metrics/base_velocity/error_vel_xy",
"bad_orientation": "Episode_Termination/bad_orientation",
"timeout": "Episode_Termination/time_out",
"value_loss": "Loss/value",
"surrogate_loss": "Loss/surrogate",
}
def load_series(
accumulator: EventAccumulator,
tag: str,
) -> tuple[np.ndarray, np.ndarray]:
events = accumulator.Scalars(tag)
steps = np.asarray([event.step for event in events])
values = np.asarray([event.value for event in events])
return steps, values
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("event_file", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--title", default="Dropbear Stage 46")
args = parser.parse_args()
accumulator = EventAccumulator(
str(args.event_file),
size_guidance={"scalars": 0},
)
accumulator.Reload()
available = set(accumulator.Tags()["scalars"])
missing = [tag for tag in TAGS.values() if tag not in available]
if missing:
raise RuntimeError(f"Missing TensorBoard tags: {missing}")
data = {
name: load_series(accumulator, tag)
for name, tag in TAGS.items()
}
plt.style.use("dark_background")
fig, axes = plt.subplots(2, 2, figsize=(13, 8), sharex=True)
fig.patch.set_facecolor("#07111f")
for axis in axes.flat:
axis.set_facecolor("#0c1727")
axis.grid(color="#334155", alpha=0.28)
axis.spines[["top", "right"]].set_visible(False)
axes[0, 0].plot(*data["reward"], color="#5eead4", linewidth=2)
axes[0, 0].set_title("Mean episode reward")
axes[0, 0].set_ylabel("reward")
axes[0, 1].plot(
*data["episode_length"],
color="#60a5fa",
linewidth=2,
label="episode length",
)
axes[0, 1].axhline(
1000,
color="#a7f3d0",
linestyle=":",
linewidth=1,
label="full horizon",
)
axes[0, 1].set_title("Survival recovery")
axes[0, 1].set_ylabel("steps")
axes[0, 1].legend(frameon=False, fontsize=8)
axes[1, 0].plot(
*data["velocity_error"],
color="#fbbf24",
linewidth=2,
)
axes[1, 0].set_title("Planar velocity tracking error")
axes[1, 0].set_ylabel("m/s")
axes[1, 0].set_xlabel("checkpoint iteration")
axes[1, 1].plot(
*data["bad_orientation"],
color="#fb7185",
linewidth=2,
label="bad orientation",
)
axes[1, 1].plot(
*data["timeout"],
color="#4ade80",
linewidth=2,
label="time-out",
)
axes[1, 1].set_title("Episode completion")
axes[1, 1].set_ylabel("fraction")
axes[1, 1].set_xlabel("checkpoint iteration")
axes[1, 1].legend(frameon=False, fontsize=8)
start = int(data["reward"][0][0])
end = int(data["reward"][0][-1])
fig.suptitle(
f"{args.title} · iterations {start}–{end}",
x=0.065,
ha="left",
fontsize=17,
fontweight="bold",
)
fig.text(
0.985,
0.015,
"RSL-RL PPO continuation · 512 parallel environments",
ha="right",
color="#94a3b8",
fontsize=8,
)
fig.tight_layout(rect=(0.04, 0.04, 0.99, 0.93))
args.output.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(args.output, dpi=180, facecolor=fig.get_facecolor())
if __name__ == "__main__":
main()
|