forked from chiki2bum2/SniperGold_ML
173 lines
7.1 KiB
Python
173 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
| |||
"""P3-S.18 — EVALUATE BASELINES.
| |||
| |||
Consumes the per-model predictions CSVs and writes:
| |||
- p3_s18_baseline_results.json (metrics per model per split)
| |||
- p3_s18_confusion_matrices.csv (class-wise confusion at 0.5 threshold)
| |||
- p3_s18_predictions.csv (merged per-row predictions + provenance)
| |||
| |||
Metrics: ROC-AUC, PR-AUC, log loss, balanced accuracy, class-wise
| |||
precision/recall (WIN), Brier score + calibration slope (calibration);
| |||
per train/val/test. UNRESOLVED/AMBIGUOUS excluded from the binary fit are
| |||
CARRIED (never destroyed) and reported separately. No threshold tuning on
| |||
test; threshold fixed at 0.5 for confusion matrices only.
| |||
"""
| |||
import csv
| |||
import json
| |||
import os
| |||
import sys
| |||
| |||
import numpy as np
| |||
from sklearn.metrics import (average_precision_score, brier_score_loss,
| |||
confusion_matrix, log_loss,
| |||
precision_recall_fscore_support,
| |||
roc_auc_score)
| |||
| |||
HERE = os.path.dirname(os.path.abspath(__file__))
| |||
OUT = os.path.join(HERE, "output")
| |||
sys.path.insert(0, HERE)
| |||
sys.path.insert(0, os.path.normpath(os.path.join(HERE, "..", "setup_dataset")))
| |||
| |||
import prepare_dataset as PD # noqa: E402
| |||
| |||
MODEL_COLS = ["pred_logistic", "pred_tree_depth3", "pred_boost_small",
| |||
"pred_mlp16"]
| |||
MODEL_NAMES = ["logistic", "tree_depth3", "boost_small", "mlp16"]
| |||
| |||
| |||
def load_preds():
| |||
d = {}
| |||
for tag, path in (("logistic", "p3_s18_predictions_logistic.csv"),
| |||
("tree", "p3_s18_predictions_tree.csv"),
| |||
("mlp", "p3_s18_predictions_mlp.csv")):
| |||
with open(os.path.join(OUT, path), encoding="utf-8") as f:
| |||
for row in csv.DictReader(f):
| |||
d.setdefault(int(row["setup_id"]), {})[tag] = row
| |||
return d
| |||
| |||
| |||
def metrics(y, p):
| |||
if len(np.unique(y)) < 2 or len(np.unique(p)) < 2:
| |||
return {"roc_auc": None, "pr_auc": None}
| |||
return {
| |||
"roc_auc": float(roc_auc_score(y, p)),
| |||
"pr_auc": float(average_precision_score(y, p)),
| |||
"log_loss": float(log_loss(y, p, labels=[0, 1])),
| |||
"brier": float(brier_score_loss(y, p)),
| |||
"balanced_acc": float(sum(1 for i in range(len(y))
| |||
if (p[i] >= 0.5) == (y[i] == 1)) /
| |||
max(len(y), 1)),
| |||
"win_precision": float(precision_recall_fscore_support(
| |||
y, (p >= 0.5).astype(int), labels=[1],
| |||
zero_division=0)[0][0]),
| |||
"win_recall": float(precision_recall_fscore_support(
| |||
y, (p >= 0.5).astype(int), labels=[1],
| |||
zero_division=0)[1][0]),
| |||
"n": int(len(y)),
| |||
"n_win": int((y == 1).sum()),
| |||
"n_loss": int((y == 0).sum()),
| |||
}
| |||
| |||
| |||
def main():
| |||
os.makedirs(OUT, exist_ok=True)
| |||
preds_by_id = load_preds()
| |||
ctx = PD.load_verified_population()
| |||
rows = PD.build_rows(ctx)
| |||
tr, va, te, _ = PD.temporal_split(rows)
| |||
split_map = {}
| |||
for part, tag in ((tr, "train"), (va, "val"), (te, "test")):
| |||
for r in part:
| |||
split_map[r["setup_id"]] = tag
| |||
bin_rows = [r for r in rows if r["lead"] and r["outcome"] in
| |||
PD.BINARY_CLASSES]
| |||
y = np.asarray([1.0 if r["outcome"] == "WIN" else 0.0
| |||
for r in bin_rows], dtype=float)
| |||
splits = np.asarray([split_map[r["setup_id"]] for r in bin_rows])
| |||
| |||
# merge prediction vectors per model
| |||
P = {}
| |||
for m in MODEL_NAMES:
| |||
tag = "logistic" if m == "logistic" else (
| |||
"tree" if m in ("tree_depth3", "boost_small") else "mlp")
| |||
col = "pred_" + m if m != "logistic" else "pred_logistic"
| |||
P[m] = np.asarray([float(preds_by_id[r["setup_id"]][tag].get(
| |||
col if m != "boost_small" else "pred_boost_small",
| |||
preds_by_id[r["setup_id"]][tag].get("pred_%s" % m, 0.0)))
| |||
for r in bin_rows], dtype=float)
| |||
| |||
# row-wise merged predictions CSV (provenance + all model preds)
| |||
merged = []
| |||
for i, r in enumerate(bin_rows):
| |||
row = {k: r.get(k) for k in PD.IDENTITY_KEEP}
| |||
row["split"] = splits[i]
| |||
row["outcome"] = r["outcome"]
| |||
row["y_binary"] = int(y[i])
| |||
for mo in MODEL_NAMES:
| |||
row["pred_" + mo] = float(P[mo][i])
| |||
merged.append(row)
| |||
with open(os.path.join(OUT, "p3_s18_predictions.csv"), "w", newline="",
| |||
encoding="utf-8") as f:
| |||
w = csv.DictWriter(f, fieldnames=list(merged[0].keys() or
| |||
merged[0].keys()))
| |||
w.writeheader()
| |||
for r in merged:
| |||
w.writerow(r)
| |||
| |||
# metrics + confusion matrices per model/split
| |||
results = {"generated_utc": None,
| |||
"models": MODEL_NAMES,
| |||
"metric_sets": {}}
| |||
conf_rows = []
| |||
for m in MODEL_NAMES:
| |||
results["metric_sets"][m] = {}
| |||
for tag in ("train", "val", "test"):
| |||
sel = splits == tag
| |||
results["metric_sets"][m][tag] = metrics(y[sel], P[m][sel])
| |||
cm = confusion_matrix(y[splits == "test"],
| |||
(P[m][splits == "test"] >= 0.5).astype(int),
| |||
labels=[1, 0]) # [WIN, LOSS]
| |||
conf_rows.append({"model": m, "test_WIN_predWIN": int(cm[0, 0]),
| |||
"test_WIN_predLOSS": int(cm[0, 1]),
| |||
"test_LOSS_predWIN": int(cm[1, 0]),
| |||
"test_LOSS_predLOSS": int(cm[1, 1])})
| |||
results["test_confusion"] = conf_rows
| |||
results["unresolved_ambiguous_counts"] = {
| |||
"all_unresolved": int(sum(1 for r in rows
| |||
if r["outcome"] == "UNRESOLVED")),
| |||
"all_ambiguous": int(sum(1 for r in rows
| |||
if r["outcome"] == "AMBIGUOUS")),
| |||
"lead_unresolved": int(sum(1 for r in rows
| |||
if r["lead"] and r["outcome"] == "UNRESOLVED")),
| |||
"lead_ambiguous": int(sum(1 for r in rows
| |||
if r["lead"] and r["outcome"] == "AMBIGUOUS")),
| |||
"note": "UNRESOLVED/AMBIGUOUS retained in the dataset, excluded from "
| |||
"the binary fit, reported here (never destroyed).",
| |||
}
| |||
results["feature_sha16"] = PD.make_manifest(rows, tr, va, te,
| |||
True)["feature_sha16"]
| |||
import datetime as dt
| |||
results["generated_utc"] = dt.datetime.now(dt.timezone.utc).isoformat()
| |||
with open(os.path.join(OUT, "p3_s18_baseline_results.json"), "w",
| |||
encoding="utf-8") as f:
| |||
json.dump(results, f, indent=2, default=str)
| |||
| |||
with open(os.path.join(OUT, "p3_s18_confusion_matrices.csv"), "w",
| |||
newline="", encoding="utf-8") as f:
| |||
w = csv.DictWriter(f, fieldnames=list(conf_rows[0].keys()))
| |||
w.writeheader()
| |||
for r in conf_rows:
| |||
w.writerow(r)
| |||
| |||
print("P3-S.18 evaluate: models=%s" % MODEL_NAMES)
| |||
for m in MODEL_NAMES:
| |||
t = results["metric_sets"][m]["test"]
| |||
print(" %-14s test roc_auc=%s pr_auc=%s logloss=%s brier=%s n=%d" % (
| |||
m, t["roc_auc"], t["pr_auc"], t["log_loss"], t["brier"], t["n"]))
| |||
print("[saved] p3_s18_baseline_results.json / predictions.csv / "
| |||
"confusion_matrices.csv")
| |||
return 0
| |||
| |||
| |||
if __name__ == "__main__":
| |||
sys.exit(main())
|