Navigam commited on
Commit
368fe4f
·
1 Parent(s): 6e2b9c3

feat: add summary generation and visualization for model evaluation results

Browse files

Introduced new functions in plot_results.py to generate a summary of evaluation results, including average rewards and success rates, and output them to CSV and Markdown formats. Enhanced plotting functions to visualize model performance across tasks with improved styling and labeling. Added new images for reward curves and success rates, facilitating better analysis of model behavior in CORP-ENV scenarios.

notebook466c72b6cd.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
plot_results.py CHANGED
@@ -3,12 +3,28 @@
3
  from __future__ import annotations
4
 
5
  import argparse
 
6
  import json
7
  from collections import defaultdict
8
  from pathlib import Path
9
  from typing import Any, Dict, Iterable, List
10
 
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def expand_inputs(inputs: Iterable[str]) -> List[Path]:
13
  paths: List[Path] = []
14
  for raw in inputs:
@@ -48,6 +64,31 @@ def read_rows(paths: Iterable[str]) -> List[Dict[str, Any]]:
48
  return rows
49
 
50
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  def grouped_mean(rows: List[Dict[str, Any]], metric: str) -> Dict[str, Dict[str, float]]:
52
  grouped: Dict[str, Dict[str, List[float]]] = defaultdict(lambda: defaultdict(list))
53
  for row in rows:
@@ -60,6 +101,83 @@ def grouped_mean(rows: List[Dict[str, Any]], metric: str) -> Dict[str, Dict[str,
60
  }
61
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  def plot_grouped_bars(
64
  data: Dict[str, Dict[str, float]],
65
  title: str,
@@ -70,24 +188,46 @@ def plot_grouped_bars(
70
  ) -> None:
71
  import matplotlib.pyplot as plt
72
 
73
- stages = list(data.keys())
 
74
  tasks = sorted({task for by_task in data.values() for task in by_task})
75
  x = list(range(len(tasks)))
76
  width = 0.8 / max(len(stages), 1)
77
 
78
- fig, ax = plt.subplots(figsize=(10, 5))
79
  for idx, stage in enumerate(stages):
80
  vals = [data[stage].get(task, 0.0) for task in tasks]
81
  offsets = [pos - 0.4 + width / 2 + idx * width for pos in x]
82
- ax.bar(offsets, vals, width, label=stage)
83
- ax.set_title(title)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  ax.set_xlabel("Task")
85
  ax.set_ylabel(ylabel)
86
  ax.set_xticks(x)
87
- ax.set_xticklabels(tasks, rotation=20, ha="right")
88
  if clamp_unit:
89
  ax.set_ylim(0, 1.05)
90
- ax.legend()
 
91
  fig.tight_layout()
92
  fig.savefig(output, dpi=160)
93
  plt.close(fig)
@@ -96,22 +236,41 @@ def plot_grouped_bars(
96
  def plot_reward_curve(rows: List[Dict[str, Any]], output: Path) -> None:
97
  import matplotlib.pyplot as plt
98
 
99
- fig, ax = plt.subplots(figsize=(10, 5))
100
- plotted = False
101
  for row in rows:
102
- trace = row.get("reward_trace") or []
103
- if not trace:
104
- continue
105
- label = f"{row.get('model_stage', 'model')}:{row.get('task_id', 'task')}:{row.get('episode_index', 0)}"
106
- ax.plot(list(range(1, len(trace) + 1)), trace, alpha=0.45, label=label)
107
- plotted = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  if not plotted:
109
  ax.text(0.5, 0.5, "No reward traces found", ha="center", va="center")
110
- ax.set_title("Episode Reward Traces")
 
111
  ax.set_xlabel("Environment step")
112
  ax.set_ylabel("Step reward")
 
113
  if plotted:
114
- ax.legend(fontsize=7, ncol=2)
115
  fig.tight_layout()
116
  fig.savefig(output, dpi=160)
117
  plt.close(fig)
@@ -151,7 +310,9 @@ def main() -> None:
151
  clamp_unit=True,
152
  )
153
  plot_reward_curve(rows, out / "reward_curve.png")
 
154
  print(f"Wrote plots to {out}")
 
155
 
156
 
157
  if __name__ == "__main__":
 
3
  from __future__ import annotations
4
 
5
  import argparse
6
+ import csv
7
  import json
8
  from collections import defaultdict
9
  from pathlib import Path
10
  from typing import Any, Dict, Iterable, List
11
 
12
 
13
+ STAGE_ORDER = ("baseline", "base", "sft", "grpo", "oracle")
14
+ TASK_LABELS = {
15
+ "e1_launch_readiness": "E1 Launch",
16
+ "m1_budget_reallocation": "M1 Budget",
17
+ "h1_acquisition_defence": "H1 Acquisition",
18
+ }
19
+ COLORS = {
20
+ "baseline": "#8c8c8c",
21
+ "base": "#4C78A8",
22
+ "sft": "#54A24B",
23
+ "grpo": "#F58518",
24
+ "oracle": "#B279A2",
25
+ }
26
+
27
+
28
  def expand_inputs(inputs: Iterable[str]) -> List[Path]:
29
  paths: List[Path] = []
30
  for raw in inputs:
 
64
  return rows
65
 
66
 
67
+ def stage_family(stage: str) -> str:
68
+ low = stage.lower()
69
+ for family in STAGE_ORDER:
70
+ if family in low:
71
+ return family
72
+ return low
73
+
74
+
75
+ def stage_sort_key(stage: str) -> tuple:
76
+ family = stage_family(stage)
77
+ try:
78
+ family_idx = STAGE_ORDER.index(family)
79
+ except ValueError:
80
+ family_idx = len(STAGE_ORDER)
81
+ return (family_idx, stage)
82
+
83
+
84
+ def task_label(task_id: str) -> str:
85
+ return TASK_LABELS.get(task_id, task_id.replace("_", " "))
86
+
87
+
88
+ def stage_label(stage: str) -> str:
89
+ return stage.replace("_", " ").replace("-", " ")
90
+
91
+
92
  def grouped_mean(rows: List[Dict[str, Any]], metric: str) -> Dict[str, Dict[str, float]]:
93
  grouped: Dict[str, Dict[str, List[float]]] = defaultdict(lambda: defaultdict(list))
94
  for row in rows:
 
101
  }
102
 
103
 
104
+ def summary_rows(rows: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
105
+ grouped: Dict[tuple, List[Dict[str, Any]]] = defaultdict(list)
106
+ for row in rows:
107
+ grouped[(str(row.get("model_stage", "unknown")), str(row.get("task_id", "unknown")))].append(row)
108
+
109
+ out: List[Dict[str, Any]] = []
110
+ for (stage, task), vals in sorted(grouped.items(), key=lambda x: (stage_sort_key(x[0][0]), x[0][1])):
111
+ steps = [float(v.get("steps", 0) or 0) for v in vals]
112
+ invalid_rates = [
113
+ float(v.get("invalid_action_count", 0) or 0) / max(float(v.get("steps", 0) or 0), 1.0)
114
+ for v in vals
115
+ ]
116
+ out.append(
117
+ {
118
+ "model_stage": stage,
119
+ "task_id": task,
120
+ "episodes": len(vals),
121
+ "avg_terminal_reward": round(sum(float(v.get("terminal_reward", 0.0)) for v in vals) / len(vals), 6),
122
+ "avg_total_reward": round(sum(float(v.get("total_reward", 0.0)) for v in vals) / len(vals), 6),
123
+ "avg_verifier_pass_rate": round(sum(float(v.get("verifier_pass_rate", 0.0)) for v in vals) / len(vals), 6),
124
+ "success_rate": round(sum(1 for v in vals if v.get("success")) / len(vals), 6),
125
+ "avg_invalid_action_rate": round(sum(invalid_rates) / len(invalid_rates), 6),
126
+ "avg_steps": round(sum(steps) / len(steps), 3),
127
+ }
128
+ )
129
+ return out
130
+
131
+
132
+ def write_summary(rows: List[Dict[str, Any]], output_dir: Path) -> None:
133
+ summary = summary_rows(rows)
134
+ csv_path = output_dir / "comparison_summary.csv"
135
+ md_path = output_dir / "comparison_summary.md"
136
+ if not summary:
137
+ return
138
+ with csv_path.open("w", newline="", encoding="utf-8") as f:
139
+ writer = csv.DictWriter(f, fieldnames=list(summary[0].keys()))
140
+ writer.writeheader()
141
+ writer.writerows(summary)
142
+
143
+ headers = [
144
+ "Model Stage",
145
+ "Task",
146
+ "Episodes",
147
+ "Terminal Reward",
148
+ "Verifier Pass",
149
+ "Success",
150
+ "Invalid Rate",
151
+ "Avg Steps",
152
+ ]
153
+ lines = [
154
+ "# CORP-ENV Result Comparison",
155
+ "",
156
+ "| " + " | ".join(headers) + " |",
157
+ "| " + " | ".join(["---"] * len(headers)) + " |",
158
+ ]
159
+ for row in summary:
160
+ lines.append(
161
+ "| "
162
+ + " | ".join(
163
+ [
164
+ str(row["model_stage"]),
165
+ task_label(str(row["task_id"])),
166
+ str(row["episodes"]),
167
+ f"{row['avg_terminal_reward']:.3f}",
168
+ f"{row['avg_verifier_pass_rate']:.3f}",
169
+ f"{row['success_rate']:.3f}",
170
+ f"{row['avg_invalid_action_rate']:.3f}",
171
+ f"{row['avg_steps']:.1f}",
172
+ ]
173
+ )
174
+ + " |"
175
+ )
176
+ lines.append("")
177
+ lines.append("Generated by `plot_results.py` from eval JSONL files.")
178
+ md_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
179
+
180
+
181
  def plot_grouped_bars(
182
  data: Dict[str, Dict[str, float]],
183
  title: str,
 
188
  ) -> None:
189
  import matplotlib.pyplot as plt
190
 
191
+ plt.style.use("seaborn-v0_8-whitegrid")
192
+ stages = sorted(data.keys(), key=stage_sort_key)
193
  tasks = sorted({task for by_task in data.values() for task in by_task})
194
  x = list(range(len(tasks)))
195
  width = 0.8 / max(len(stages), 1)
196
 
197
+ fig, ax = plt.subplots(figsize=(max(10, len(tasks) * 2.2), 5.8))
198
  for idx, stage in enumerate(stages):
199
  vals = [data[stage].get(task, 0.0) for task in tasks]
200
  offsets = [pos - 0.4 + width / 2 + idx * width for pos in x]
201
+ family = stage_family(stage)
202
+ bars = ax.bar(
203
+ offsets,
204
+ vals,
205
+ width,
206
+ label=stage_label(stage),
207
+ color=COLORS.get(family),
208
+ edgecolor="white",
209
+ linewidth=0.8,
210
+ )
211
+ for bar, val in zip(bars, vals):
212
+ if val > 0:
213
+ ax.text(
214
+ bar.get_x() + bar.get_width() / 2,
215
+ bar.get_height() + (0.015 if clamp_unit else 0.02),
216
+ f"{val:.2f}",
217
+ ha="center",
218
+ va="bottom",
219
+ fontsize=8,
220
+ rotation=0,
221
+ )
222
+ ax.set_title(title, fontsize=15, weight="bold", pad=14)
223
  ax.set_xlabel("Task")
224
  ax.set_ylabel(ylabel)
225
  ax.set_xticks(x)
226
+ ax.set_xticklabels([task_label(t) for t in tasks], rotation=0, ha="center")
227
  if clamp_unit:
228
  ax.set_ylim(0, 1.05)
229
+ ax.spines[["top", "right"]].set_visible(False)
230
+ ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.12), ncol=max(1, min(len(stages), 4)), frameon=False)
231
  fig.tight_layout()
232
  fig.savefig(output, dpi=160)
233
  plt.close(fig)
 
236
  def plot_reward_curve(rows: List[Dict[str, Any]], output: Path) -> None:
237
  import matplotlib.pyplot as plt
238
 
239
+ plt.style.use("seaborn-v0_8-whitegrid")
240
+ grouped: Dict[tuple, List[List[float]]] = defaultdict(list)
241
  for row in rows:
242
+ trace = [float(x) for x in (row.get("reward_trace") or [])]
243
+ if trace:
244
+ grouped[(str(row.get("model_stage", "model")), str(row.get("task_id", "task")))].append(trace)
245
+
246
+ fig, ax = plt.subplots(figsize=(12, 6.5))
247
+ plotted = bool(grouped)
248
+ for (stage, task), traces in sorted(grouped.items(), key=lambda x: (stage_sort_key(x[0][0]), x[0][1])):
249
+ max_len = max(len(t) for t in traces)
250
+ means: List[float] = []
251
+ mins: List[float] = []
252
+ maxs: List[float] = []
253
+ for idx in range(max_len):
254
+ vals = [trace[idx] for trace in traces if idx < len(trace)]
255
+ means.append(sum(vals) / len(vals))
256
+ mins.append(min(vals))
257
+ maxs.append(max(vals))
258
+ xs = list(range(1, max_len + 1))
259
+ family = stage_family(stage)
260
+ label = f"{stage_label(stage)} · {task_label(task)}"
261
+ color = COLORS.get(family)
262
+ ax.plot(xs, means, marker="o", linewidth=2.2, markersize=4, label=label, color=color)
263
+ if len(traces) > 1:
264
+ ax.fill_between(xs, mins, maxs, alpha=0.12, color=color)
265
  if not plotted:
266
  ax.text(0.5, 0.5, "No reward traces found", ha="center", va="center")
267
+ ax.axhline(0, color="#666666", linewidth=0.9, alpha=0.5)
268
+ ax.set_title("Episode Reward Curve By Model Stage", fontsize=15, weight="bold", pad=14)
269
  ax.set_xlabel("Environment step")
270
  ax.set_ylabel("Step reward")
271
+ ax.spines[["top", "right"]].set_visible(False)
272
  if plotted:
273
+ ax.legend(fontsize=8, ncol=2, frameon=False, loc="upper center", bbox_to_anchor=(0.5, -0.12))
274
  fig.tight_layout()
275
  fig.savefig(output, dpi=160)
276
  plt.close(fig)
 
310
  clamp_unit=True,
311
  )
312
  plot_reward_curve(rows, out / "reward_curve.png")
313
+ write_summary(rows, out)
314
  print(f"Wrote plots to {out}")
315
+ print(f"Wrote summaries to {out / 'comparison_summary.md'} and {out / 'comparison_summary.csv'}")
316
 
317
 
318
  if __name__ == "__main__":
results/model_compare/comparison_summary.csv ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ model_stage,task_id,episodes,avg_terminal_reward,avg_total_reward,avg_verifier_pass_rate,success_rate,avg_invalid_action_rate,avg_steps
2
+ base-qwen2.5-7b,e1_launch_readiness,1,0.91,1.39,1.0,1.0,0.0,3.0
3
+ base-qwen2.5-7b,h1_acquisition_defence,1,0.753333,2.283333,0.666667,0.0,0.0,8.0
4
+ base-qwen2.5-7b,m1_budget_reallocation,1,0.806667,2.056667,0.666667,0.0,0.0,6.0
5
+ sft-qwen2.5-7b,e1_launch_readiness,1,0.91,1.58,1.0,1.0,0.0,4.0
6
+ sft-qwen2.5-7b,h1_acquisition_defence,1,0.515,2.915,0.5,0.0,0.0,15.0
7
+ sft-qwen2.5-7b,m1_budget_reallocation,1,0.943333,2.093333,1.0,1.0,0.0,6.0
8
+ grpo-qwen2.5-7b,e1_launch_readiness,1,0.91,1.58,1.0,1.0,0.0,4.0
9
+ grpo-qwen2.5-7b,h1_acquisition_defence,1,-0.01,1.54,0.416667,0.0,0.783333,60.0
10
+ grpo-qwen2.5-7b,m1_budget_reallocation,1,0.548333,1.508333,0.5,0.0,0.0,5.0
results/model_compare/comparison_summary.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CORP-ENV Result Comparison
2
+
3
+ | Model Stage | Task | Episodes | Terminal Reward | Verifier Pass | Success | Invalid Rate | Avg Steps |
4
+ | --- | --- | --- | --- | --- | --- | --- | --- |
5
+ | base-qwen2.5-7b | E1 Launch | 1 | 0.910 | 1.000 | 1.000 | 0.000 | 3.0 |
6
+ | base-qwen2.5-7b | H1 Acquisition | 1 | 0.753 | 0.667 | 0.000 | 0.000 | 8.0 |
7
+ | base-qwen2.5-7b | M1 Budget | 1 | 0.807 | 0.667 | 0.000 | 0.000 | 6.0 |
8
+ | sft-qwen2.5-7b | E1 Launch | 1 | 0.910 | 1.000 | 1.000 | 0.000 | 4.0 |
9
+ | sft-qwen2.5-7b | H1 Acquisition | 1 | 0.515 | 0.500 | 0.000 | 0.000 | 15.0 |
10
+ | sft-qwen2.5-7b | M1 Budget | 1 | 0.943 | 1.000 | 1.000 | 0.000 | 6.0 |
11
+ | grpo-qwen2.5-7b | E1 Launch | 1 | 0.910 | 1.000 | 1.000 | 0.000 | 4.0 |
12
+ | grpo-qwen2.5-7b | H1 Acquisition | 1 | -0.010 | 0.417 | 0.000 | 0.783 | 60.0 |
13
+ | grpo-qwen2.5-7b | M1 Budget | 1 | 0.548 | 0.500 | 0.000 | 0.000 | 5.0 |
14
+
15
+ Generated by `plot_results.py` from eval JSONL files.
results/model_compare/invalid_action_rate.png ADDED

Git LFS Details

  • SHA256: ae3ca07c82abc6b0ee8e0a3cff6dac3e0c1686ae8531cedd0cecbd247f4126ee
  • Pointer size: 130 Bytes
  • Size of remote file: 44.3 kB
results/model_compare/model_comparison.png ADDED

Git LFS Details

  • SHA256: b69b893e81c9d0a045e5b1a17a00532e3f4e3d6746d60dc1432c322948b9e6fa
  • Pointer size: 130 Bytes
  • Size of remote file: 49 kB
results/model_compare/reward_curve.png ADDED

Git LFS Details

  • SHA256: a6513cac372a83222924331bcac6d9d4503b6f9044add0f79dc76832f55ce4d4
  • Pointer size: 131 Bytes
  • Size of remote file: 116 kB
results/model_compare/success_by_task.png ADDED

Git LFS Details

  • SHA256: fe090c8decb426d04d3150011f1c0f3b26a6b52d02d49acbb9d9bafb999def59
  • Pointer size: 130 Bytes
  • Size of remote file: 44.5 kB