Download model-3/scripts/singleton_probe.py from Jyo-K/PoPTard5: direct link, hf CLI and curl.
- Browser
- Download file 6.16 kB
-
https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/scripts/singleton_probe.py
- Command line
-
hf download hf://Jyo-K/PoPTard5/model-3/scripts/singleton_probe.py
-
curl -L -o singleton_probe.py https://huggingface.co/Jyo-K/PoPTard5/resolve/main/model-3/scripts/singleton_probe.py
6.16 kB
| """Singletons are the largest unexamined loss. Is any of it recoverable? | |
| From the recovered metrics.json of the submitted run (n=50,000 val entities): | |
| f05_singletons 0.9438 | |
| f05_non_singletons 0.9834 | |
| singleton_share 0.05484 | |
| A singleton scores 1.0 for an empty prediction and 0.0 for ANY prediction, so 0.9438 means | |
| 5.62% of true singletons were handed at least one confident false positive. Macro cost: | |
| 0.05484 * 0.0562 = 0.00308 | |
| which is larger than the corroboration, second-cross-encoder and per-country-threshold levers | |
| combined. FINAL_SOLUTION.md s32 item 7 names this exact failure and s29 then EXCLUDES a singleton | |
| classifier; this script tests whether that exclusion was justified. | |
| Two entity-level failures exist and both score exactly 0: | |
| A true singleton, we predicted something | |
| B true non-singleton, we predicted nothing | |
| Fixing A by raising the bar necessarily creates more B. The question is whether the two | |
| populations are SEPARABLE by anything we can compute without labels. | |
| """ | |
| 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, THR = b["miss_mass"], b["param"] | |
| cand = pd.read_parquet(CACHE) | |
| truths = {int(k): set(v) for k, v in json.load(open(CACHE + ".truth.json")).items()} | |
| ids = sorted(truths) | |
| shipped = DC.predict_sets(cand, "q", "c", "p", b["method"], THR, MM, True) | |
| m0, per = macro_f05(shipped, truths, ids) | |
| print(f"baseline F0.5 {m0['f05']:.5f} (rule {b['method']}({THR}))") | |
| is_sing = {q: (len(truths[q]) == 0) for q in ids} | |
| n_sing = sum(is_sing.values()) | |
| print(f"\nval entities {len(ids):,} true singletons {n_sing:,} ({n_sing/len(ids):.3%})") | |
| sing_ids = [q for q in ids if is_sing[q]] | |
| non_ids = [q for q in ids if not is_sing[q]] | |
| ms, _ = macro_f05({q: shipped.get(q, []) for q in sing_ids}, truths, sing_ids) | |
| mn, _ = macro_f05({q: shipped.get(q, []) for q in non_ids}, truths, non_ids) | |
| print(f" F0.5 singletons {ms['f05']:.5f} ({n_sing:,} entities)") | |
| print(f" F0.5 non-singletons {mn['f05']:.5f} ({len(non_ids):,} entities)") | |
| share = n_sing / len(ids) | |
| print(f" macro cost of singleton errors : {share*(1-ms['f05']):.5f}") | |
| print(f" macro cost of non-sing errors : {(1-share)*(1-mn['f05']):.5f}") | |
| # ------------------------------------------------------------------ the two failure classes | |
| A = [q for q in sing_ids if shipped.get(q)] # singleton, predicted something | |
| B = [q for q in non_ids if not shipped.get(q)] # non-singleton, predicted nothing | |
| print(f"\nclass A true singleton but predicted : {len(A):,} / {n_sing:,} " | |
| f"({len(A)/max(n_sing,1):.2%})") | |
| print(f"class B non-singleton but empty : {len(B):,} / {len(non_ids):,} " | |
| f"({len(B)/max(len(non_ids),1):.2%})") | |
| print(f" each costs a full 1.0; together {(len(A)+len(B))/len(ids):.4%} of the macro average") | |
| by_q = {q: g for q, g in cand.groupby("q")} | |
| def profile(qs, label): | |
| """Label-free signals available at inference time.""" | |
| rows = [] | |
| for q in qs: | |
| g = by_q.get(q) | |
| if g is None or not len(g): | |
| continue | |
| p = g["p"].to_numpy() | |
| srt = np.sort(p)[::-1] | |
| rows.append({ | |
| "n_cand": len(p), | |
| "top1": srt[0], | |
| "top2": srt[1] if len(srt) > 1 else np.nan, | |
| "margin": srt[0] - (srt[1] if len(srt) > 1 else 0.0), | |
| "n_above_thr": int((p >= THR).sum()), | |
| "psum": float(p.sum()), | |
| "mean_p": float(p.mean()), | |
| }) | |
| d = pd.DataFrame(rows) | |
| print(f"\n--- {label} n={len(d):,} ---") | |
| if not len(d): | |
| return d | |
| print(d[["n_cand", "top1", "top2", "margin", "n_above_thr", "psum", "mean_p"]] | |
| .describe().loc[["mean", "50%"]].to_string()) | |
| return d | |
| dA = profile(A, "A: true singleton, we predicted (the 1.0 losses)") | |
| dS_ok = profile([q for q in sing_ids if not shipped.get(q)], | |
| "singletons we correctly left empty") | |
| dN_ok = profile([q for q in non_ids if shipped.get(q)], | |
| "non-singletons we predicted (the good case)") | |
| dB = profile(B, "B: non-singleton, we predicted nothing") | |
| print("\n=== ARE CLASS-A ERRORS SEPARABLE FROM CORRECT NON-SINGLETON PREDICTIONS? ===") | |
| print("If class A had systematically lower top1 / psum, a higher bar for weak entities would") | |
| print("trade A-errors for B-errors profitably. Compare the two distributions:") | |
| for col in ("top1", "margin", "psum", "n_above_thr", "n_cand"): | |
| a = dA[col].dropna() if len(dA) else pd.Series(dtype=float) | |
| n = dN_ok[col].dropna() if len(dN_ok) else pd.Series(dtype=float) | |
| if not len(a) or not len(n): | |
| continue | |
| # overlap: fraction of class A above the 10th percentile of the good case | |
| q10 = np.percentile(n, 10) | |
| print(f" {col:<12} A median {a.median():.4f} good median {n.median():.4f} " | |
| f"A above good-p10 ({q10:.4f}): {(a >= q10).mean():.1%}") | |
| print("\n=== DIRECT TEST: raise the bar and count the actual trade ===") | |
| print("Requires top1 >= tau before any prediction is allowed for that entity.") | |
| print(f"{'tau':>7}{'A fixed':>10}{'B created':>11}{'net entities':>14}{'F0.5':>11}{'delta':>10}") | |
| for tau in [0.70, 0.80, 0.90, 0.95, 0.98, 0.99, 0.995, 0.999]: | |
| pr = {} | |
| for q, cs in shipped.items(): | |
| g = by_q.get(q) | |
| if g is None: | |
| continue | |
| if float(g["p"].max()) >= tau: | |
| pr[q] = cs | |
| m, _ = macro_f05(pr, truths, ids) | |
| a_fixed = sum(1 for q in A if q not in pr) | |
| b_new = sum(1 for q in non_ids if shipped.get(q) and q not in pr) | |
| print(f"{tau:>7.3f}{a_fixed:>10,}{b_new:>11,}{a_fixed-b_new:>14,}" | |
| f"{m['f05']:>11.5f}{m['f05']-m0['f05']:>+10.5f}") | |
| print("\n=== CEILING: what a PERFECT singleton detector would be worth ===") | |
| perfect = {q: cs for q, cs in shipped.items() if not is_sing[q]} | |
| m, _ = macro_f05(perfect, truths, ids) | |
| print(f" drop every prediction on a TRUE singleton (oracle): {m['f05']:.5f} " | |
| f"({m['f05']-m0['f05']:+.5f})") | |
| print(f" that is the hard ceiling on all singleton work.") | |