PoPTard5 / model-3 /scripts /reject_probe.py
Jyo-K's picture
Upload folder using huggingface_hub
e79d917 verified
Raw History Blame Contribute Delete
4.23 kB
"""The 1,763 true pairs we RETRIEVED but REJECTED: are they rankable, or hopeless?
If their calibrated p sits just under the 0.70 threshold, a better-calibrated or
slightly better model recovers them cheaply. If p ~ 0, the reranker genuinely cannot
tell them from negatives and only a different model/feature would help.
Also measures, exactly:
- how many FPs we would buy per TP recovered at each threshold band (the real
precision/recall exchange rate, which is what F0.5 actually cares about)
- the rank of the rejected true pair within its entity's candidate list
- whether one-owner arbitration is stealing any of them
"""
import json, sys
import numpy as np, pandas as pd, joblib
sys.path.insert(0, "src")
import decide as DC
from evaluate import macro_f05
ART, CACHE = "work/results_A", "work/val_scored_20000.parquet"
b = joblib.load(f"{ART}/model_bundle.joblib")
mm = b["miss_mass"]
cand = pd.read_parquet(CACHE)
truths = {int(k): set(v) for k, v in json.load(open(CACHE + ".truth.json")).items()}
ids = sorted(truths)
THR = b["param"]
print(f"shipped: {b['method']}({THR}) miss_mass={mm:.4f}")
shipped = DC.predict_sets(cand, "q", "c", "p", b["method"], THR, mm, True)
m0, _ = macro_f05(shipped, truths, ids)
print(f"shipped F0.5 {m0['f05']:.5f} P {m0['precision']:.4f} R {m0['recall']:.4f}\n")
# label every scored candidate pair
tset = {q: truths[q] for q in ids}
cand = cand.copy()
cand["y"] = [1 if c in tset.get(q, ()) else 0 for q, c in zip(cand["q"], cand["c"])]
pred_pairs = {(q, c) for q, cs in shipped.items() for c in cs}
cand["sel"] = [1 if (q, c) in pred_pairs else 0 for q, c in zip(cand["q"], cand["c"])]
rej = cand[(cand.y == 1) & (cand.sel == 0)]
sel = cand[cand.sel == 1]
print(f"retrieved-but-rejected true pairs : {len(rej):,}")
print(f"selected pairs : {len(sel):,} (of which false {int((sel.y==0).sum()):,})")
print("\n=== p-distribution of the REJECTED true pairs ===")
bins = [0,.01,.05,.1,.2,.3,.4,.5,.6,.65,.70,.8,.9,1.01]
h, _ = np.histogram(rej["p"], bins=bins)
cum = 0
for lo, hi, c in zip(bins[:-1], bins[1:], h):
cum += c
print(f" p in [{lo:.2f},{hi:.2f}) : {c:>6,} ({c/len(rej):6.2%}) cum {cum/len(rej):6.2%}")
print(f" median p of a rejected true pair: {rej['p'].median():.4f}")
print(f" ** fraction with p >= 0.5 (near-misses, cheap to recover): {(rej['p']>=0.5).mean():.2%}")
print(f" ** fraction with p < 0.05 (model is confidently wrong) : {(rej['p']<0.05).mean():.2%}")
print("\n=== EXCHANGE RATE: what each threshold step actually buys ===")
print(f"{'band':>14} {'TP gained':>10} {'FP gained':>10} {'ratio':>8}")
unsel = cand[cand.sel == 0]
for lo, hi in [(0.65,0.70),(0.60,0.65),(0.50,0.60),(0.40,0.50),(0.30,0.40),(0.10,0.30),(0.0,0.10)]:
s = unsel[(unsel.p >= lo) & (unsel.p < hi)]
tp, fp = int((s.y==1).sum()), int((s.y==0).sum())
r = f"{tp/fp:.2f}" if fp else "inf"
print(f" [{lo:.2f},{hi:.2f}) {tp:>10,} {fp:>10,} {r:>8}")
print(" (F0.5 rises only while a band's TP:FP beats roughly 1:4 at our operating point)")
print("\n=== rank of the rejected true pair inside its entity ===")
cand["rk"] = cand.groupby("q")["p"].rank(ascending=False, method="first")
rj = cand[(cand.y==1) & (cand.sel==0)]
for k in [1,2,3,4,5,8,12]:
print(f" rank <= {k:<3}: {(rj.rk<=k).mean():6.2%}")
print("\n=== is one-owner arbitration stealing true pairs? ===")
d = DC.resolve_conflicts(cand, "p")
kept = {(q,c) for q,c in zip(d["q"], d["c"])}
lost = cand[(cand.y==1) & (~pd.Series(list(zip(cand['q'],cand['c']))).isin(kept).values)]
print(f" true pairs dropped by arbitration: {len(lost):,}")
print("\n=== ORACLE-RERANK: if we could perfectly reorder within each entity, ===")
print("=== keeping the SAME number of predictions per entity (no precision cost) ===")
ns = {q: len(cs) for q, cs in shipped.items()}
orc = {}
for q, g in cand.groupby("q"):
k = ns.get(q, 0)
if k:
g2 = g.sort_values("y", ascending=False, kind="mergesort")
orc[q] = list(g2["c"].values[:k])
m1, _ = macro_f05(orc, truths, ids)
print(f" perfect within-entity ranking, same k : F0.5 {m1['f05']:.5f} (+{m1['f05']-m0['f05']:.5f})")
print(" ^ this is what a STRICTLY BETTER RERANKER alone can win, holding k fixed")