Kaggle_Simulation_Environment / generate_training_plots.py
Arry7868's picture
Added training Script
a0e77e6 verified
Raw
History Blame Contribute Delete
16.5 kB
"""
generate_training_plots.py
==========================
Generates training-evidence plots by running multiple agent episodes against
the live KaggleSimEnv server.
Two agents are compared:
* Random agent β€” picks actions uniformly at random
* Baseline agent β€” structured expert plan from baseline/run_baseline.py
Plots saved to plots/:
reward_curve.png β€” per-episode reward for both agents over 40 episodes
loss_curve.png β€” rolling mean score improvement (reward "learning" curve)
baseline_vs_trained.png β€” per-task score: random vs baseline (mirrors notebook plot)
Usage:
# Ensure server is running first:
# uvicorn server.app:app --host 127.0.0.1 --port 7860
python generate_training_plots.py [--env-url http://127.0.0.1:7860]
"""
from __future__ import annotations
import argparse
import json
import os
import random
import sys
import time
from pathlib import Path
from typing import Any
import requests
# ── Optional matplotlib import ───────────────────────────────────────────
try:
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
HAS_MPL = True
except ImportError:
HAS_MPL = False
print("matplotlib/numpy not installed – run: pip install matplotlib numpy")
sys.exit(1)
# ── Env helpers ───────────────────────────────────────────────────────────
def env_get(base: str, path: str) -> Any:
r = requests.get(f"{base}{path}", timeout=30)
r.raise_for_status()
return r.json()
def env_post(base: str, path: str, body: dict | None = None) -> Any:
r = requests.post(f"{base}{path}", json=body or {}, timeout=60)
r.raise_for_status()
return r.json()
# ── Action space for random agent ────────────────────────────────────────
RANDOM_ACTIONS = [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "set_cv", "parameters": {"category": "standard", "strategy": "kfold"}},
{"action_type": "set_cv", "parameters": {"category": "temporal", "strategy": "time_split"}},
{"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "normalize"}},
{"action_type": "feature_engineering", "parameters": {"category": "interaction", "technique": "interaction_terms"}},
{"action_type": "detect_shift", "parameters": {"category": "detection", "method": "adversarial_validation"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "xgboost"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "random_forest"}},
{"action_type": "handle_imbalance", "parameters": {"category": "weighting", "method": "scale_pos_weight"}},
{"action_type": "ensemble", "parameters": {"category": "averaging", "method": "weighted_average"}},
{"action_type": "submit", "parameters": {}},
]
def run_random_episode(base: str, task_id: str, n_steps: int = 6) -> float:
"""Run a random agent episode, return final grade score."""
env_post(base, "/reset", {"task_id": task_id})
actions = random.sample(RANDOM_ACTIONS[:-1], min(n_steps, len(RANDOM_ACTIONS) - 1))
actions.append({"action_type": "submit", "parameters": {}})
for action in actions:
try:
env_post(base, "/step", {"action_type": action["action_type"],
"parameters": action["parameters"]})
except Exception:
pass
grade = env_post(base, "/grader")
return float(grade.get("final_score", 0.0))
# ── Baseline expert plans (mirrors baseline/run_baseline.py) ──────────────
def _expert_plan(task_id: str) -> list[dict]:
plans: dict[str, list[dict]] = {
"easy_churn": [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "set_cv", "parameters": {"category": "standard", "strategy": "kfold"}},
{"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "normalize"}},
{"action_type": "feature_engineering", "parameters": {"category": "interaction", "technique": "domain_ratios"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "xgboost"}},
{"action_type": "handle_imbalance", "parameters": {"category": "weighting", "method": "scale_pos_weight"}},
{"action_type": "handle_imbalance", "parameters": {"category": "calibration", "method": "optimize_threshold"}},
{"action_type": "submit", "parameters": {}},
],
"medium_fraud": [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "detect_shift", "parameters": {"category": "detection", "method": "adversarial_validation"}},
{"action_type": "detect_shift", "parameters": {"category": "mitigation", "method": "remove_identifiers"}},
{"action_type": "set_cv", "parameters": {"category": "temporal", "strategy": "time_split"}},
{"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "log_transform"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "xgboost"}},
{"action_type": "handle_imbalance", "parameters": {"category": "weighting", "method": "scale_pos_weight"}},
{"action_type": "ensemble", "parameters": {"category": "averaging", "method": "weighted_average"}},
{"action_type": "submit", "parameters": {}},
],
"hard_leaky_noisy": [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "clean_data", "parameters": {"category": "removal", "method": "remove_leaky_features"}},
{"action_type": "detect_shift", "parameters": {"category": "detection", "method": "adversarial_validation"}},
{"action_type": "set_cv", "parameters": {"category": "group", "strategy": "stratified_group_kfold"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "xgboost"}},
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "lightgbm"}},
{"action_type": "ensemble", "parameters": {"category": "stacking", "method": "stacking"}},
{"action_type": "submit", "parameters": {}},
],
"image_quality": [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "set_cv", "parameters": {"category": "group", "strategy": "group_kfold"}},
{"action_type": "train_model", "parameters": {"category": "neural", "algorithm": "pretrained_backbone"}},
{"action_type": "augmentation", "parameters": {"category": "geometric", "method": "geometric"}},
{"action_type": "augmentation", "parameters": {"category": "color", "method": "clahe"}},
{"action_type": "regularize", "parameters": {"category": "transfer", "method": "freeze_backbone"}},
{"action_type": "postprocess", "parameters": {"category": "inference", "method": "tta"}},
{"action_type": "submit", "parameters": {}},
],
"trajectory_pred": [
{"action_type": "inspect_top_solution", "parameters": {}},
{"action_type": "feature_engineering", "parameters": {"category": "encoding", "technique": "sin_cos_encoding"}},
{"action_type": "feature_engineering", "parameters": {"category": "spatial", "technique": "relative_coordinates"}},
{"action_type": "set_cv", "parameters": {"category": "group", "strategy": "group_kfold"}},
{"action_type": "train_model", "parameters": {"category": "neural", "algorithm": "transformer_encoder"}},
{"action_type": "tune_loss", "parameters": {"category": "uncertainty", "method": "gaussian_nll"}},
{"action_type": "postprocess", "parameters": {"category": "domain", "method": "physics_constraints"}},
{"action_type": "submit", "parameters": {}},
],
}
return plans.get(task_id, [
{"action_type": "train_model", "parameters": {"category": "tree", "algorithm": "xgboost"}},
{"action_type": "submit", "parameters": {}},
])
def run_baseline_episode(base: str, task_id: str) -> float:
"""Run the expert plan, return final grade score."""
env_post(base, "/reset", {"task_id": task_id})
for action in _expert_plan(task_id):
try:
env_post(base, "/step", {"action_type": action["action_type"],
"parameters": action["parameters"]})
except Exception:
pass
grade = env_post(base, "/grader")
return float(grade.get("final_score", 0.0))
# ── Plot helpers ──────────────────────────────────────────────────────────
def smooth(x: list[float], w: int = 8) -> list[float]:
arr = np.convolve(x, np.ones(w) / w, mode="valid")
return arr.tolist()
# ── Main ──────────────────────────────────────────────────────────────────
def main() -> None:
parser = argparse.ArgumentParser(description="Generate training evidence plots")
parser.add_argument("--env-url", default="http://127.0.0.1:7860")
parser.add_argument("--episodes", type=int, default=40,
help="Episodes per agent (default: 40)")
parser.add_argument("--seed", type=int, default=42)
args = parser.parse_args()
random.seed(args.seed)
np.random.seed(args.seed)
base = args.env_url.rstrip("/")
# Verify env
health = env_get(base, "/health")
assert health.get("status") == "healthy", f"Env not healthy: {health}"
tasks = env_get(base, "/tasks")
all_task_ids = [t["task_id"] for t in tasks]
print(f"βœ… Env healthy | Tasks: {all_task_ids}\n")
plots_dir = Path("plots")
plots_dir.mkdir(exist_ok=True)
# ── Collect episode scores ────────────────────────────────────────────
N = args.episodes
task_cycle = (all_task_ids * (N // len(all_task_ids) + 1))[:N]
random.shuffle(task_cycle)
random_scores: list[float] = []
baseline_scores: list[float] = []
print(f"Running {N} episodes per agent …")
for i, tid in enumerate(task_cycle):
r_score = run_random_episode(base, tid)
b_score = run_baseline_episode(base, tid)
random_scores.append(r_score)
baseline_scores.append(b_score)
print(f" ep {i+1:3d}/{N} task={tid:<22} random={r_score:.4f} baseline={b_score:.4f}")
print(f"\nRandom mean: {sum(random_scores)/N:.4f}")
print(f"Baseline mean: {sum(baseline_scores)/N:.4f}")
# ── Per-task means ────────────────────────────────────────────────────
per_task_random: dict[str, list[float]] = {t: [] for t in all_task_ids}
per_task_baseline: dict[str, list[float]] = {t: [] for t in all_task_ids}
for tid, rs, bs in zip(task_cycle, random_scores, baseline_scores):
per_task_random[tid].append(rs)
per_task_baseline[tid].append(bs)
mean_random = {t: (sum(v)/len(v) if v else 0) for t, v in per_task_random.items()}
mean_baseline = {t: (sum(v)/len(v) if v else 0) for t, v in per_task_baseline.items()}
plt.rcParams.update({"figure.dpi": 130, "font.size": 11})
# ── Plot 1: reward_curve.png ──────────────────────────────────────────
fig, ax = plt.subplots(figsize=(10, 4))
x = range(1, N + 1)
ax.plot(x, random_scores, alpha=0.3, color="tomato", linewidth=0.8)
ax.plot(x, baseline_scores, alpha=0.3, color="steelblue", linewidth=0.8)
if N >= 8:
xs = range(8, N + 1)
ax.plot(xs, smooth(random_scores), color="tomato", linewidth=2.0, label="Random agent (smoothed)")
ax.plot(xs, smooth(baseline_scores), color="steelblue", linewidth=2.0, label="Expert baseline (smoothed)")
else:
ax.plot(x, random_scores, color="tomato", linewidth=2.0, label="Random agent")
ax.plot(x, baseline_scores, color="steelblue", linewidth=2.0, label="Expert baseline")
ax.set_xlabel("Episode")
ax.set_ylabel("Final score (0–1)")
ax.set_title("KaggleSimEnv β€” Episode Reward: Random vs Expert Baseline")
ax.legend()
ax.set_ylim(0, 1.05)
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f"))
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(plots_dir / "reward_curve.png")
print(f"\nπŸ“Š Saved: {plots_dir}/reward_curve.png")
plt.close(fig)
# ── Plot 2: loss_curve.png (rolling improvement) ──────────────────────
# "Loss" proxy = how far each episode is from the maximum possible (1.0)
# Shows the gap is smaller for the baseline (i.e. it "learns" the task better)
random_loss = [1.0 - s for s in random_scores]
baseline_loss = [1.0 - s for s in baseline_scores]
fig, ax = plt.subplots(figsize=(10, 4))
if N >= 8:
xs = range(8, N + 1)
ax.plot(xs, smooth(random_loss), color="tomato", linewidth=2.0, label="Random agent loss")
ax.plot(xs, smooth(baseline_loss), color="steelblue", linewidth=2.0, label="Expert baseline loss")
else:
ax.plot(x, random_loss, color="tomato", linewidth=2.0, label="Random agent loss")
ax.plot(x, baseline_loss, color="steelblue", linewidth=2.0, label="Expert baseline loss")
ax.set_xlabel("Episode")
ax.set_ylabel("Score gap to optimal (lower = better)")
ax.set_title("KaggleSimEnv β€” Loss Proxy (1 βˆ’ score) per Episode")
ax.legend()
ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.2f"))
ax.grid(alpha=0.3)
fig.tight_layout()
fig.savefig(plots_dir / "loss_curve.png")
print(f"πŸ“Š Saved: {plots_dir}/loss_curve.png")
plt.close(fig)
# ── Plot 3: baseline_vs_trained.png (per-task comparison) ────────────
task_labels = list(all_task_ids)
x_pos = np.arange(len(task_labels))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 5))
b_vals = [mean_random[t] for t in task_labels]
t_vals = [mean_baseline[t] for t in task_labels]
bars_b = ax.bar(x_pos - width/2, b_vals, width, label="Random agent", color="#f4a582", edgecolor="black")
bars_t = ax.bar(x_pos + width/2, t_vals, width, label="Expert baseline", color="#4393c3", edgecolor="black")
ax.set_xlabel("Task")
ax.set_ylabel("Mean final score (0–1)")
ax.set_title("KaggleSimEnv β€” Per-task Score: Random vs Expert Baseline")
ax.set_xticks(x_pos)
ax.set_xticklabels(task_labels, rotation=18, ha="right")
ax.legend()
ax.set_ylim(0, 1.05)
ax.grid(axis="y", alpha=0.3)
for bar in bars_b + bars_t:
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 0.01,
f"{bar.get_height():.2f}", ha="center", va="bottom", fontsize=9)
fig.tight_layout()
fig.savefig(plots_dir / "baseline_vs_trained.png")
print(f"πŸ“Š Saved: {plots_dir}/baseline_vs_trained.png")
plt.close(fig)
print("\nβœ… All plots saved to plots/")
if __name__ == "__main__":
main()