MedFlow-OpenEnv / scripts /plot_rewards.py
shriom17
real logic added
e9ca231
Raw History Blame Contribute Delete
4.45 kB
"""Generate reward plots from episode JSON logs.
Usage:
python scripts/plot_rewards.py --logs outputs/logs --out outputs/plots
Produces:
- outputs/plots/reward_curve.png (mean per-step reward curve)
- outputs/plots/reward_hist.png (histogram / boxplot of total rewards)
"""
from __future__ import annotations
import argparse
from pathlib import Path
import json
import math
import matplotlib.pyplot as plt
try:
import numpy as np
except Exception:
np = None
def load_episodes(log_dir: Path):
files = sorted(log_dir.glob("episode_*.json"))
episodes = []
for p in files:
try:
d = json.loads(p.read_text(encoding="utf-8"))
except Exception:
continue
# try common shapes
total = d.get("total_reward") if isinstance(d, dict) else None
if total is None:
total = d.get("reward") if isinstance(d, dict) else None
steps = d.get("steps") or d.get("episode") or d.get("trajectory") or []
if total is None and steps:
total = sum(s.get("reward", 0) for s in steps if isinstance(s, dict))
# extract per-step rewards
per_step = []
if steps and isinstance(steps, list):
for s in steps:
if isinstance(s, dict):
per_step.append(float(s.get("reward", 0)))
else:
# if step is numeric
try:
per_step.append(float(s))
except Exception:
pass
episodes.append({"name": p.name, "total": total, "per_step": per_step})
return episodes
def ensure_out(dirpath: Path):
dirpath.mkdir(parents=True, exist_ok=True)
def plot_reward_curve(episodes, out_path: Path):
# build aligned per-step arrays
per_steps = [e["per_step"] for e in episodes if e["per_step"]]
if not per_steps:
print("No per-step data found to plot reward curve.")
return
max_len = max(len(p) for p in per_steps)
if np is not None:
arr = np.full((len(per_steps), max_len), np.nan, dtype=float)
for i, p in enumerate(per_steps):
arr[i, : len(p)] = p
mean = np.nanmean(arr, axis=0)
std = np.nanstd(arr, axis=0)
x = list(range(1, len(mean) + 1))
else:
# fallback: compute mean manually
sums = [0.0] * max_len
counts = [0] * max_len
for p in per_steps:
for i, v in enumerate(p):
sums[i] += v
counts[i] += 1
mean = [sums[i] / counts[i] if counts[i] else 0.0 for i in range(max_len)]
std = [0.0] * max_len
x = list(range(1, max_len + 1))
plt.figure(figsize=(9, 4))
plt.plot(x, mean, label="mean reward per step")
try:
plt.fill_between(x, [m - s for m, s in zip(mean, std)], [m + s for m, s in zip(mean, std)], alpha=0.2)
except Exception:
pass
plt.xlabel("Step")
plt.ylabel("Reward")
plt.title("Mean per-step reward (shaded = std)")
plt.grid(alpha=0.2)
plt.legend()
plt.tight_layout()
plt.savefig(out_path / "reward_curve.png", dpi=150)
plt.close()
def plot_total_hist(episodes, out_path: Path):
totals = [e["total"] for e in episodes if e["total"] is not None]
if not totals:
print("No total reward data found to plot histogram.")
return
plt.figure(figsize=(6, 4))
plt.subplot(1, 2, 1)
plt.hist(totals, bins=min(30, max(5, len(totals)//2)), alpha=0.7)
plt.xlabel("Total reward")
plt.ylabel("Count")
plt.title("Total reward distribution")
plt.subplot(1, 2, 2)
plt.boxplot(totals, vert=False)
plt.title("Total reward (box)")
plt.tight_layout()
plt.savefig(out_path / "reward_hist.png", dpi=150)
plt.close()
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--logs", type=Path, default=Path("outputs/logs"))
parser.add_argument("--out", type=Path, default=Path("outputs/plots"))
args = parser.parse_args()
if not args.logs.exists():
print(f"Logs directory not found: {args.logs}")
return
ensure_out(args.out)
episodes = load_episodes(args.logs)
print(f"Found {len(episodes)} episode files in {args.logs}")
plot_reward_curve(episodes, args.out)
plot_total_hist(episodes, args.out)
print(f"Saved plots to {args.out}")
if __name__ == "__main__":
main()