Buckets:
| """The two tests that decide whether the selective-speaking policy is real. | |
| TEST 1 — PROMPT-ONLY. This is the deployability question, and it is forced by | |
| the chat contract rather than by convenience. `evalsrv/chat.py` builds | |
| `z = latent <think> content + the visible THOUGHT section`, so a model CANNOT | |
| reason privately and then decide to stay quiet: any chain of thought it produces | |
| is already the thing being scored. The speak/stay-silent decision therefore has | |
| to be made from the PROMPT ALONE, before a single reasoning token exists. | |
| If the signal lives only in the thought text, a classifier over thoughts is | |
| a fine post-hoc diagnostic and an undeployable policy. | |
| TEST 2 — TRANSFER. Every duel scores two models over ~2060 turns and slices | |
| overlap heavily, so the same turn appears under many models. A plain | |
| leave-model-out split leaves the held-out model's TURNS in training under | |
| everyone else's rows, and a classifier can learn "turn X is hard" instead of | |
| "this thought is good". Production slices are freshly seeded per duel | |
| (`blake2b(reveal_block_hash || hotkey)`), so that knowledge is worth nothing. | |
| Each fold here holds out a model AND restricts to trajectories absent from | |
| training; the leaky variant is reported alongside so the gap measures the leak. | |
| python3 harness/reason_deployable.py | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import collections | |
| import pathlib | |
| import sys | |
| import numpy as np | |
| sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent)) | |
| import reason_signal as R | |
| def tfidf(train_txt, ytr, test_txt, min_df=10, maxf=40000): | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| tv = TfidfVectorizer(min_df=min_df, ngram_range=(1, 2), max_features=maxf, | |
| sublinear_tf=True) | |
| A = tv.fit_transform(train_txt) | |
| B = tv.transform(test_txt) | |
| lr = LogisticRegression(max_iter=3000, class_weight="balanced") | |
| lr.fit(A, ytr) | |
| return lr.predict_proba(B)[:, 1] | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--data", default=str(R.ROOT / "data" / "reason.jsonl.gz")) | |
| ap.add_argument("--folds", type=int, default=4) | |
| args = ap.parse_args() | |
| rows = R.load(args.data, drop_silent=True) | |
| import screen | |
| corpus = screen.load_corpus() | |
| kept = [] | |
| for r in rows: | |
| c = corpus.get(r["turn_id"]) | |
| msgs = (c or {}).get("prefix") or [] | |
| if not msgs: | |
| continue | |
| r["_prompt"] = " ".join(str(m.get("content") or "") for m in msgs[-2:])[-1500:] | |
| kept.append(r) | |
| if len(kept) < 0.5 * len(rows): | |
| raise SystemExit(f"prompt joined for only {len(kept)}/{len(rows)} rows") | |
| print(f"{len(kept)} pairs with a joined prompt " | |
| f"({100*len(kept)/len(rows):.1f}% of {len(rows)})\n") | |
| tr, va, te = R.split(kept, 0) | |
| ytr = np.array([r["reason"] > 0 for r in tr], dtype=int) | |
| yte = np.array([r["reason"] > 0 for r in te], dtype=int) | |
| r_te = np.array([r["reason"] for r in te]) | |
| r_va = np.array([r["reason"] for r in va]) | |
| yva = np.array([r["reason"] > 0 for r in va], dtype=int) | |
| print("=== TEST 1 — PROMPT-ONLY (the deployable policy) ===") | |
| p_va = tfidf([r["_prompt"] for r in tr], ytr, [r["_prompt"] for r in va]) | |
| p_te = tfidf([r["_prompt"] for r in tr], ytr, [r["_prompt"] for r in te]) | |
| R.show("prompt-only (val)", R.metrics(yva, p_va)) | |
| R.show("prompt-only (test)", R.metrics(yte, p_te)) | |
| grid = np.quantile(p_va, np.linspace(0.0, 0.98, 40)) | |
| best = max(R.policy_curve(r_va, p_va, grid), key=lambda c: c["margin"]) | |
| print(f"\n best VAL threshold {best['thr']:.4f}: speak {100*best['speak']:.1f}% " | |
| f"hit {100*best['hit']:.1f}% margin {best['margin']:+.6f}") | |
| print(" frozen on TEST:") | |
| print(f" {'thr':>7} {'speak%':>8} {'hit%':>7} {'meanReason':>12} " | |
| f"{'margin':>11} {'bar':>10} crowns") | |
| for c in R.policy_curve(r_te, p_te, [best["thr"]]): | |
| print(f" {c['thr']:>7.4f} {100*c['speak']:>7.1f}% {100*c['hit']:>6.1f}% " | |
| f"{c['mean']:>+12.6f} {c['margin']:>+11.6f} {c['bar']:>10.6f} " | |
| f"{'YES' if c['crowns'] else 'no'}") | |
| trajs = np.array([r["traj_id"] for r in te]) | |
| lo, hi = R.boot_margin(r_te, p_te, best["thr"], trajs) | |
| print(f" trajectory bootstrap 95% CI: [{lo:+.6f}, {hi:+.6f}]") | |
| print(" full TEST curve:") | |
| for c in R.policy_curve(r_te, p_te, np.quantile(p_va, [.25, .5, .75, .9, .95])): | |
| print(f" {c['thr']:>7.4f} {100*c['speak']:>7.1f}% {100*c['hit']:>6.1f}% " | |
| f"{c['mean']:>+12.6f} {c['margin']:>+11.6f}") | |
| print("\n=== TEST 2 — TRANSFER: unseen MODEL *and* unseen TRAJECTORY ===") | |
| by_model = collections.defaultdict(list) | |
| for r in kept: | |
| by_model[r["model"]].append(r) | |
| big = sorted(by_model, key=lambda m: -len(by_model[m]))[: args.folds] | |
| print(f"{'held-out model':<38} {'n':>7} {'AUCleak':>8} {'AUChonest':>10} " | |
| f"{'hit@top20%':>11}") | |
| leak, honest = [], [] | |
| for m in big: | |
| test = by_model[m] | |
| tt = {r["traj_id"] for r in test} | |
| other = [r for r in kept if r["model"] != m] | |
| clean = [r for r in other if r["traj_id"] not in tt] | |
| if not clean: | |
| continue | |
| yt = np.array([r["reason"] > 0 for r in test], dtype=int) | |
| if len(set(yt)) < 2: | |
| continue | |
| pa = tfidf([r["z_a"] for r in other], | |
| np.array([r["reason"] > 0 for r in other], dtype=int), | |
| [r["z_a"] for r in test]) | |
| pb = tfidf([r["z_a"] for r in clean], | |
| np.array([r["reason"] > 0 for r in clean], dtype=int), | |
| [r["z_a"] for r in test]) | |
| ma, mb = R.metrics(yt, pa), R.metrics(yt, pb) | |
| k = max(int(0.20 * len(pb)), 1) | |
| hit = float(yt[np.argsort(pb)[-k:]].mean()) | |
| leak.append(ma["auc"]); honest.append(mb["auc"]) | |
| print(f"{m[:36]:<38} {mb['n']:>7} {ma['auc']:>8.4f} {mb['auc']:>10.4f} " | |
| f"{hit:>11.4f}") | |
| if honest: | |
| print(f"\n mean AUC, turn leak allowed : {np.mean(leak):.4f}") | |
| print(f" mean AUC, model+trajectory disjoint: {np.mean(honest):.4f}") | |
| print(f" turn-memorisation leak : {np.mean(leak)-np.mean(honest):+.4f}") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 6.36 kB
- Xet hash:
- c32dbd2b5ef10649f9e327536d82233396e4a0d4c0efe6ed9a92e0597190ba57
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.