| """
|
| Confidence-based rejection (selective prediction) on the wild test.
|
|
|
| The calibration analysis showed the studio model's wrong cross-source predictions
|
| sit at low confidence. Here we quantify the practical consequence: if we abstain
|
| on low-confidence predictions, accuracy on the accepted subset rises. This is an
|
| honest deployment knob (it trades coverage for accuracy), not a fix for the
|
| collapse. Reads calibration_s*.json (studio-trained model, wild test).
|
| """
|
| import sys, os, glob, json
|
| from pathlib import Path
|
|
|
| _base = "/mnt/d/SpiceNet" if os.path.exists("/mnt/d/SpiceNet") else "D:/SpiceNet"
|
| sys.path.insert(0, _base)
|
|
|
| import numpy as np
|
| import figstyle
|
|
|
| ROOT = Path(_base)
|
| OUT = ROOT / "outputs" / "rejection"
|
|
|
|
|
| def main():
|
| files = [f for f in sorted(glob.glob(str(ROOT / "outputs" / "calibration_s*.json")))
|
| if "smoke" not in Path(f).name.lower()]
|
| if not files:
|
| raise SystemExit("no calibration_s*.json — run eval_calibration.py first")
|
| conf, corr = [], []
|
| for f in files:
|
| d = json.load(open(f))["in"]["ss"]
|
| conf += d["confidence"]; corr += d["correct"]
|
| conf, corr = np.asarray(conf), np.asarray(corr, float)
|
|
|
| order = np.argsort(-conf)
|
| c = corr[order]
|
| k = np.arange(1, len(c) + 1)
|
| coverage = k / len(c)
|
| accuracy = np.cumsum(c) / k
|
|
|
|
|
| marks = {}
|
| for tgt in (0.90, 0.95, 0.99):
|
| idx = np.where(accuracy >= tgt)[0]
|
| marks[tgt] = float(coverage[idx[-1]]) if len(idx) else 0.0
|
|
|
| json.dump({"n": int(len(c)), "base_accuracy": float(corr.mean()),
|
| "coverage_at_acc": {str(k): v for k, v in marks.items()}},
|
| open(ROOT / "outputs" / "rejection.json", "w"), indent=2)
|
|
|
| figstyle.apply()
|
| import matplotlib.pyplot as plt
|
| P = figstyle.PALETTE
|
| fig, ax = plt.subplots(figsize=(6.4, 4.4))
|
| ax.plot(coverage * 100, accuracy * 100, color=P["wild"], lw=2)
|
| ax.axhline(corr.mean() * 100, color=P["chance"], ls=":", lw=1,
|
| label=f"full-coverage acc {corr.mean()*100:.0f}%")
|
| for tgt, cov in marks.items():
|
| if cov > 0:
|
| ax.plot(cov * 100, tgt * 100, "o", color=P["cross_broken"], ms=6)
|
| ax.annotate(f"{tgt*100:.0f}% acc @ {cov*100:.0f}% coverage",
|
| (cov * 100, tgt * 100), textcoords="offset points",
|
| xytext=(6, -10), fontsize=8)
|
| ax.set_xlabel("coverage (% of wild predictions kept)")
|
| ax.set_ylabel("accuracy on accepted (%)")
|
| ax.set_xlim(0, 100); ax.set_ylim(50, 101)
|
| ax.set_title("Selective prediction on the wild test (studio-trained model)", fontsize=11)
|
| ax.legend(loc="lower left", fontsize=9); ax.grid(alpha=0.25)
|
| fig.tight_layout()
|
| figstyle.save(fig, str(OUT))
|
| print(f"base acc {corr.mean()*100:.1f}% | "
|
| + " ".join(f">= {int(t*100)}% acc @ {v*100:.0f}% cov" for t, v in marks.items()))
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|