indicators-env / evaluation /plot_learning_curve.py
bawsi99's picture
Final clean submission
3a7314c
Raw
History Blame Contribute Delete
5.81 kB
"""
plot_learning_curve.py β€” Visualize GRPO training progress from HF Trainer checkpoints.
The HF Trainer automatically saves `trainer_state.json` inside every checkpoint folder.
This script reads all those files, extracts per-step metrics, and plots a learning curve.
Usage (run in Colab after training):
python evaluation/plot_learning_curve.py \\
--checkpoint_dir "/content/drive/MyDrive/indicators_grpo_v2"
Output:
- learning_curve.png saved to the checkpoint_dir
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from typing import List, Dict, Any
import matplotlib
matplotlib.use("Agg") # Headless (no display needed in Colab)
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
def _load_trainer_state(checkpoint_dir: str) -> List[Dict[str, Any]]:
"""
Scan all checkpoint-* subdirectories and collect log history
from trainer_state.json.
"""
base = Path(checkpoint_dir)
all_logs = []
seen_steps = set()
checkpoints = sorted(
[d for d in base.iterdir() if d.is_dir() and d.name.startswith("checkpoint-")],
key=lambda d: int(d.name.split("-")[1])
)
for ckpt in checkpoints:
state_file = ckpt / "trainer_state.json"
if not state_file.exists():
print(f" ⚠️ No trainer_state.json in {ckpt.name}")
continue
with open(state_file) as f:
state = json.load(f)
for entry in state.get("log_history", []):
step = entry.get("step")
if step is not None and step not in seen_steps:
seen_steps.add(step)
all_logs.append(entry)
all_logs.sort(key=lambda x: x.get("step", 0))
print(f" βœ… Loaded {len(all_logs)} log entries from {len(checkpoints)} checkpoints")
return all_logs
def _extract_series(logs: List[Dict], key: str):
"""Extract (steps, values) for a given metric key."""
steps, values = [], []
for entry in logs:
if key in entry and "step" in entry:
steps.append(entry["step"])
values.append(entry[key])
return steps, values
def plot_learning_curve(checkpoint_dir: str) -> None:
print(f"[Plot] Reading trainer logs from: {checkpoint_dir}")
logs = _load_trainer_state(checkpoint_dir)
if not logs:
print("❌ No log entries found. Make sure trainer_state.json exists in checkpoint folders.")
return
# Extract all available metrics
reward_steps, reward_vals = _extract_series(logs, "reward")
loss_steps, loss_vals = _extract_series(logs, "loss")
train_loss_steps, train_loss_vals = _extract_series(logs, "train_loss")
# Fallback: use train_loss if loss not found
if not loss_vals and train_loss_vals:
loss_steps, loss_vals = train_loss_steps, train_loss_vals
# ── Build Plot ──────────────────────────────────────────────────────────
n_plots = sum([bool(reward_vals), bool(loss_vals)])
if n_plots == 0:
print("❌ No 'reward' or 'loss' keys found in log history.")
print(" Available keys:", list(set(k for e in logs for k in e.keys())))
return
fig, axes = plt.subplots(1, n_plots, figsize=(8 * n_plots, 5))
if n_plots == 1:
axes = [axes]
fig.suptitle(
"IndicatorsEnv β€” GRPO Training Learning Curve\n"
"Qwen2.5-1.5B-Instruct + QLoRA | Anti-Bias Reward",
fontsize=13, fontweight="bold"
)
ax_idx = 0
if reward_vals:
ax = axes[ax_idx]; ax_idx += 1
ax.plot(reward_steps, reward_vals, color="#4CAF50", linewidth=2, alpha=0.8, label="Per-step Reward")
# Rolling average
if len(reward_vals) >= 10:
import statistics
window = 10
rolled = [
statistics.mean(reward_vals[max(0, i-window//2):i+window//2+1])
for i in range(len(reward_vals))
]
ax.plot(reward_steps, rolled, color="#1B5E20", linewidth=2.5, linestyle="--", label="Rolling Avg (10)")
ax.set_xlabel("Training Step", fontsize=11)
ax.set_ylabel("Reward", fontsize=11)
ax.set_title("Reward per Step", fontsize=12)
ax.legend(); ax.grid(True, alpha=0.3)
ax.axhline(y=0, color="red", linestyle=":", alpha=0.5, label="Break-even")
if loss_vals:
ax = axes[ax_idx]; ax_idx += 1
ax.plot(loss_steps, loss_vals, color="#2196F3", linewidth=2, alpha=0.8, label="Training Loss")
ax.set_xlabel("Training Step", fontsize=11)
ax.set_ylabel("Loss", fontsize=11)
ax.set_title("Training Loss", fontsize=12)
ax.legend(); ax.grid(True, alpha=0.3)
plt.tight_layout()
out_path = Path(checkpoint_dir) / "learning_curve.png"
plt.savefig(out_path, dpi=150, bbox_inches="tight")
print(f"\nβœ… Learning curve saved to: {out_path}")
print(" Add this image to your mentor report as Section 4b.")
# Also print raw numbers
if reward_vals:
mid = len(reward_vals) // 2
print(f"\n First 10 steps avg reward: {sum(reward_vals[:10])/10:.4f}")
print(f" Mid 10 steps avg reward: {sum(reward_vals[mid:mid+10])/10:.4f}")
print(f" Last 10 steps avg reward: {sum(reward_vals[-10:])/10:.4f}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Plot GRPO training learning curve from HF checkpoints")
parser.add_argument(
"--checkpoint_dir",
default="/content/drive/MyDrive/indicators_grpo_v2",
help="Path to the output_dir used during training"
)
args = parser.parse_args()
plot_learning_curve(args.checkpoint_dir)