Download model-3/scripts/reject_probe.py from Jyo-K/PoPTard5: direct link, hf CLI and curl.
- Browser
- Download file 4.23 kB
-
https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/scripts/reject_probe.py
- Command line
-
hf download hf://Jyo-K/PoPTard5/model-3/scripts/reject_probe.py
-
curl -L -o reject_probe.py https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/scripts/reject_probe.py
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") | |