Buckets:
| """Which thoughts will be CATASTROPHIC? A different target from predict_l2.py. | |
| `predict_l2.py` asked which of two thoughts scores higher. That is hard: TF-IDF | |
| manages 60.5% and a 30B analyst did no better, so the branch was closed. | |
| But the influence law says the leaderboard is not decided by winning the typical | |
| row -- it is decided by how often a model detonates: | |
| corr( mean L2 , P(L2 < -0.05) ) = -0.963 over 19 submitted models | |
| So the useful question is not "is this thought better?" but "is this thought | |
| about to cost 0.05+?". That should be far easier, because it is a question about | |
| how DIRECTIVE a thought is -- a surface property -- rather than about whether it | |
| happens to match an unobserved sample from the teacher. | |
| Two things come out of this: | |
| * a number -- can catastrophe be predicted at all, out of sample, on turns the | |
| model never saw? If not, the influence law has no textual handle and any | |
| intervention has to be behavioural rather than targeted. | |
| * a description -- the features that carry the signal ARE the behaviour to | |
| suppress, which is what a weight edit has to be aimed at. | |
| Held out BY TURN, never by row: two thoughts on the same turn share a target and | |
| would leak straight across the split. | |
| python3 harness/predict_influence.py --dumps ../evals/chal-005*.json.gz | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import collections | |
| import gzip | |
| import hashlib | |
| import json | |
| import pathlib | |
| import re | |
| import numpy as np | |
| from sklearn.feature_extraction.text import TfidfVectorizer | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.metrics import roc_auc_score | |
| def held_out(turn: str, frac: float) -> bool: | |
| h = hashlib.sha256(turn.encode()).digest() | |
| return (int.from_bytes(h[:4], "big") / 2**32) < frac | |
| def load(paths: list[pathlib.Path], threshold: float) -> list[dict]: | |
| rows = [] | |
| for p in paths: | |
| if p.name == "index.jsonl": | |
| continue | |
| d = json.load(gzip.open(p)) | |
| refs = d.get("teacher_refs") or {} | |
| for side in ("king_rows", "challenger_rows"): | |
| for r in d.get(side) or []: | |
| tref = refs.get(r["turn_id"]) or [] | |
| for i, pr in enumerate(r.get("pairs") or []): | |
| if i >= len(tref): | |
| continue | |
| z = (pr.get("z_a") or "").strip() | |
| if not z: | |
| continue | |
| l2 = pr["lpC_yc_za"] - pr["lpC_yc_e"] | |
| rows.append({"turn": r["turn_id"], "z": z, "l2": l2, | |
| "bad": int(l2 < -threshold)}) | |
| return rows | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--dumps", nargs="+", required=True) | |
| ap.add_argument("--threshold", type=float, default=0.05) | |
| ap.add_argument("--frac", type=float, default=0.3) | |
| args = ap.parse_args() | |
| rows = load([pathlib.Path(x) for x in args.dumps], args.threshold) | |
| tr = [r for r in rows if not held_out(r["turn"], args.frac)] | |
| te = [r for r in rows if held_out(r["turn"], args.frac)] | |
| base = np.mean([r["bad"] for r in te]) | |
| print(f"rows {len(rows)} train {len(tr)} test {len(te)} " | |
| f"(disjoint turns) base rate P(L2<-{args.threshold}) = {base:.2%}") | |
| vec = TfidfVectorizer(ngram_range=(1, 2), min_df=5, max_features=60000, | |
| sublinear_tf=True, strip_accents="unicode") | |
| Xtr = vec.fit_transform([r["z"] for r in tr]) | |
| Xte = vec.transform([r["z"] for r in te]) | |
| ytr = np.array([r["bad"] for r in tr]) | |
| yte = np.array([r["bad"] for r in te]) | |
| clf = LogisticRegression(max_iter=2000, C=1.0, class_weight="balanced") | |
| clf.fit(Xtr, ytr) | |
| p = clf.predict_proba(Xte)[:, 1] | |
| auc = roc_auc_score(yte, p) | |
| print(f"\nheld-out AUC {auc:.4f} (0.5 = no signal)") | |
| print(f"\n{'top decile':<14} {'n':>6} {'P(catastrophe)':>16} {'lift':>7}") | |
| order = np.argsort(-p) | |
| for name, sel in (("riskiest 10%", order[:len(order)//10]), | |
| ("riskiest 25%", order[:len(order)//4]), | |
| ("safest 25%", order[-len(order)//4:]), | |
| ("safest 10%", order[-len(order)//10:])): | |
| rate = yte[sel].mean() | |
| print(f"{name:<14} {len(sel):>6} {rate:>15.2%} {rate/base:>7.2f}x") | |
| # mean L2 of each slice: the quantity that actually decides the crown | |
| l2te = np.array([r["l2"] for r in te]) | |
| print(f"\n{'slice':<14} {'mean L2':>10}") | |
| print(f"{'all':<14} {l2te.mean():>+10.5f}") | |
| print(f"{'riskiest 10%':<14} {l2te[order[:len(order)//10]].mean():>+10.5f}") | |
| print(f"{'safest 90%':<14} {l2te[order[len(order)//10:]].mean():>+10.5f}") | |
| print(" -> the gap between 'all' and 'safest 90%' is what a perfect filter " | |
| "would be worth") | |
| names = np.array(vec.get_feature_names_out()) | |
| co = clf.coef_[0] | |
| print(f"\n{'RAISES risk':<34} {'LOWERS risk':<34}") | |
| hi, lo = names[np.argsort(-co)[:18]], names[np.argsort(co)[:18]] | |
| for a, b in zip(hi, lo): | |
| print(f" {a:<32} {b:<32}") | |
| if __name__ == "__main__": | |
| main() | |
Xet Storage Details
- Size:
- 5.09 kB
- Xet hash:
- 71fe778994c4890abac22733c01d7eaf34e7c88d6703032b9ad119f6fdccfc79
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.