SPB Git

spb/wpai0001_uqo

Public
14.0 KB · 259 lines python
1#!/usr/bin/env python32"""Generate LaTeX table fragments in paper/tables/ from registered run results (no computation).34Usage: python scripts/build_tables.py --graft <run_id> [--pilot <run_id>] [--tier1 <run_id>] [--confirmatory <run_id>] [--tier1-confirmatory <run_id>]5Each fragment starts with a comment naming its source run so that every number is traceable.6"""7from __future__ import annotations89import argparse10import json11import sys12from pathlib import Path1314import numpy as np1516ROOT = Path(__file__).resolve().parents[1]17sys.path.insert(0, str(ROOT / "src"))18OUT = ROOT / "paper" / "tables"1920PRETTY = {21    "act_identity_all": "Activation graft, identity map", "act_procrustes_all": "Activation graft, Procrustes (task rows)",22    "act_unrelated_procrustes_all": "Activation graft, Procrustes (task-free rows)", "ctl_unrelated_data_map": "Activation graft, ridge (task-free rows)",23    "act_ridge_all": "Activation graft, ridge (task rows)", "act_cca_all": "Activation graft, CCA (task rows)",24    "ctl_sham_map": "Control: sham map (random orthogonal)", "ctl_norm_matched_random": "Control: norm-matched random rows",25    "ctl_shuffled_token": "Control: shuffled-item donor rows", "ctl_shuffled_layer": "Control: donor layer 3 rows (ridge)",26    "ctl_unrelated_donor": "Control: unrelated-skill donor (ridge)", "steer_a1.0": "Steering vector, $\\alpha=1$", "steer_a4.0": "Steering vector, $\\alpha=4$",27    "subspace_k8": "Subspace graft, $k=8$", "subspace_k32": "Subspace graft, $k=32$", "subspace_k128": "Subspace graft, $k=128$",28    "subspace_random_k32": "Control: random subspace, $k=32$", "adapter_graft": "Adapter graft (donor-free, ridge interface)",29    "baseline_supervised_adapter": "Baseline: supervised adapter (labels)", "baseline_distillation": "Baseline: distillation (donor outputs)",30    "weight_L0_mlp": "Weight graft: L0 MLP", "weight_L0_attn": "Weight graft: L0 attention", "weight_L0_block_ln": "Weight graft: L0 block",31    "weight_L012_blocks_embed": "Weight graft: L0--L2 blocks + $\\Omega$ embedding", "weight_embed_omega": "Weight graft: $\\Omega$ embedding only",32    "ctl_weight_shuffled_layer": "Control: donor L3 block into L0", "ctl_weight_unrelated_donor": "Control: unrelated-skill donor L0 block",33}343536def fmt(x, nd=3):37    return "---" if x is None else f"{x:.{nd}f}"383940def load(run):41    return json.loads((ROOT / "experiments" / "summaries" / run / "results.json").read_text())424344def pairs_of(r, same=None):45    pm = r["pair_meta"]46    return [p for p, m in pm.items() if same is None or m["same_init_seed"] == same]474849def mean_sd(r, cond, pairs, split="target_iid", key="accuracy"):50    v = [r["conditions"][cond][p][split][key] for p in pairs if p in r["conditions"].get(cond, {}) and split in r["conditions"][cond][p]]51    if not v:52        return None, None, 053    return float(np.mean(v)), float(np.std(v)), len(v)545556def table_main(r, run, name="tab_main_effects.tex"):57    same, diff = pairs_of(r, True), pairs_of(r, False)58    rows = []59    for cond in PRETTY:60        if cond not in r["conditions"]:61            continue62        ts, tsd, n1 = mean_sd(r, cond, same)63        cs, _, _ = mean_sd(r, cond, same, "control_iid")64        td, tdd, n2 = mean_sd(r, cond, diff)65        cd, _, _ = mean_sd(r, cond, diff, "control_iid")66        # control-adjusted effect vs sham on the matched pairs (mean of per-pair estimates and CI bounds)67        adj = [r["contrasts"][p][cond]["control_adjusted"] for p in same + diff if cond in r["contrasts"].get(p, {}) and "control_adjusted" in r["contrasts"][p][cond]]68        adj_s = f"{np.mean([a['estimate'] for a in adj]):+.3f} [{np.mean([a['low'] for a in adj]):+.3f}, {np.mean([a['high'] for a in adj]):+.3f}]" if adj else "---"69        rows.append(f"{PRETTY[cond]} & {fmt(ts)} ({fmt(tsd,2)}) & {fmt(cs)} & {fmt(td) if n2 else '---'} {('('+fmt(tdd,2)+')') if n2 else ''} & {fmt(cd) if n2 else '---'} & {adj_s} \\\\")70    base_t = [r["conditions"]["base"][k]["target_iid"]["accuracy"] for k in r["conditions"]["base"] if k.startswith("recipient_s") and "_s2_" not in k and "w96" not in k and "L6" not in k]71    hdr = f"""% source: experiments/summaries/{run}/results.json (generated by scripts/build_tables.py)72\\begin{{tabular}}{{lccccc}}73\\toprule74 & \\multicolumn{{2}}{{c}}{{same initialization ($n={len(same)}$ pairs)}} & \\multicolumn{{2}}{{c}}{{different initialization ($n={len(diff)}$)}} & control-adjusted \\\\75\\cmidrule(lr){{2-3}} \\cmidrule(lr){{4-5}}76Condition & target (sd) & control & target (sd) & control & $\\Delta_T^{{\\mathrm{{adj}}}}$ [95\\% CI] \\\\77\\midrule78Recipient, unmodified & {fmt(np.mean(base_t))} ({fmt(np.std(base_t),2)}) & 1.000 & & & \\\\79"""80    (OUT / name).write_text(hdr + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n")818283def table_maps(r, run):84    same, diff = pairs_of(r, True), pairs_of(r, False)85    rows = []86    for fam, label in (("identity", "identity"), ("procrustes", "Procrustes (task rows)"), ("unrelated_procrustes", "Procrustes (task-free rows)"), ("ridge", "ridge (task rows)"), ("unrelated_data", "ridge (task-free rows)"), ("cca", "CCA (task rows)"), ("random_orth", "random orthogonal (sham)")):87        def get(p):88            m = r["maps"][f"{p}|{fam}"]89            return m.get("r2_fit", m.get("r2_on_target_rows"))90        a = [get(p) for p in same]91        b = [get(p) for p in diff]92        npar = r["maps"][f"{same[0]}|{fam}"]["n_params"]93        rows.append(f"{label} & {npar} & {fmt(np.mean(a))} & {fmt(np.mean(b))} \\\\")94    txt = f"""% source: experiments/summaries/{run}/results.json95\\begin{{tabular}}{{lrcc}}96\\toprule97Map & parameters & $R^2$ on $\\Omega$ rows, same init & $R^2$, different init \\\\98\\midrule99""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"100    (OUT / "tab_maps.tex").write_text(txt)101102103def table_mismatch(r, run):104    rows = []105    for cond, d in r["conditions"].items():106        if cond.startswith("mismatch_") or cond.startswith("stride2_"):107            for p, v in d.items():108                rows.append(f"{p.replace('_', '\\_')} & {cond.replace('_', '\\_')} & {fmt(v['target_iid']['accuracy'])} & {fmt(v['target_iid'].get('omega_step_acc'))} & {fmt(v['control_iid']['accuracy'])} & {fmt(v.get('r2_fit'))} \\\\")109    for key in ("recipient_w96_s0", "recipient_L6_s0", "recipient_s2_s0", "donor_s2_s0"):110        if key in r["conditions"]["base"]:111            v = r["conditions"]["base"][key]112            rows.insert(0, f"{key.replace('_', '\\_')} & base & {fmt(v['target_iid']['accuracy'])} & {fmt(v['target_iid'].get('omega_step_acc'))} & {fmt(v['control_iid']['accuracy'])} & --- \\\\")113    txt = f"""% source: experiments/summaries/{run}/results.json114\\begin{{tabular}}{{llcccc}}115\\toprule116Pair & condition & target & $\\Omega$-step & control & map $R^2$ \\\\117\\midrule118""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"119    (OUT / "tab_mismatch.tex").write_text(txt)120121122def table_mechanism(r, run):123    rows = []124    for cond in ("mech_donor_ablated_attn0", "mech_rescue_from_intact_donor", "mech_rescue_sham", "mech_reverse_recipient_to_donor"):125        for p, v in r["conditions"].get(cond, {}).items():126            rows.append(f"{cond.replace('mech_', '').replace('_', ' ')} & {p.replace('_', '\\_')} & {fmt(v['target_iid']['accuracy'])} & {fmt(v['target_iid'].get('omega_step_acc'))} & {fmt(v['control_iid']['accuracy'])} \\\\")127    h5 = r["rule_vs_cache"]128    for k, v in h5.items():129        if isinstance(v, dict) and "seen" in v:130            rows.append(f"rule vs cache: {k.replace('h5_', '').replace('_', ' ')} & seen / unseen $\\Omega$ inputs & {fmt(v['seen']['estimate'])} [{fmt(v['seen']['low'])}, {fmt(v['seen']['high'])}] & {fmt(v['unseen']['estimate'])} [{fmt(v['unseen']['low'])}, {fmt(v['unseen']['high'])}] & --- \\\\")131    txt = f"""% source: experiments/summaries/{run}/results.json132\\begin{{tabular}}{{llccc}}133\\toprule134Test & pair / stratum & target & $\\Omega$-step & control \\\\135\\midrule136""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"137    (OUT / "tab_mechanism.tex").write_text(txt)138139140def table_tier1(r, run, name="tab_tier1.tex"):141    rows = []142    for p, pr in r["pairs"].items():143        for tn, tr in pr["tasks"].items():144            conds = tr["conditions"]145            best_name, best = max(((k, v) for k, v in conds.items() if not k.startswith("ctl") and "random_orth" not in k), key=lambda kv: kv[1]["accuracy"])146            ctl_name, ctl = max(((k, v) for k, v in conds.items() if k.startswith("ctl") or "random_orth" in k), key=lambda kv: kv[1]["accuracy"])147            g = best["gain_vs_zero"]148            rows.append(f"{p.replace('_', '\\_')} & {tn} & {fmt(tr['recipient_zero'],2)} & {fmt(tr['recipient_few'],2)} & {fmt(tr['recipient_own_vector'],2)} & {fmt(best['accuracy'],2)} & {best_name.replace('_', '\\_').replace('|', ' ')} & {fmt(ctl['accuracy'],2)} & {g['estimate']:+.2f} [{g['low']:+.2f}, {g['high']:+.2f}] \\\\")149    txt = f"""% source: experiments/summaries/{run}/results.json150\\begin{{tabular}}{{llccccccc}}151\\toprule152Pair & task & zero-shot & few-shot & own vector & best transfer & condition & best control & gain vs zero [95\\% CI] \\\\153\\midrule154""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"155    (OUT / name).write_text(txt)156157158def table_tier1_localization(r, run, name="tab_tier1_localization.tex"):159    rows = []160    for mk, m in r["models"].items():161        loc = m.get("localization")162        if not loc:163            continue164        for tn, t in loc["tasks"].items():165            rows.append(f"{mk} ({m['n_layers']} layers, $d={m['hidden']}$) & {tn} & {fmt(t['zero_shot'],2)} & {fmt(t['few_shot'],2)} & L{t['best_layer']} & {fmt(t['sufficiency_by_layer'][str(t['best_layer'])],2)} & {fmt(t['random_vector_same_norm_at_best'],2)} & {fmt(t['necessity_fewshot_minus_direction'],2)} \\\\")166    txt = f"""% source: experiments/summaries/{run}/results.json167\\begin{{tabular}}{{llcccccc}}168\\toprule169Model & task & zero-shot & few-shot & best layer & vector added (zero-shot) & random vector & few-shot minus direction \\\\170\\midrule171""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"172    (OUT / name).write_text(txt)173174175def table_tier1_confirmatory(run):176    c = json.loads((ROOT / "experiments" / "summaries" / run / "tier1_confirmatory.json").read_text())177    rows = []178    for p, pr in c["pairs"].items():179        for tn, t in pr["tasks"].items():180            if "error" in t:181                continue182            el = "yes" if t["eligible"] else "no"183            rows.append(f"{p.replace('->', ' $\\to$ ')} & {tn} & {el} & {t['selected'].replace('_', '\\_').replace('|', ' ')} & {fmt(t['conf_zero'],2)} & {fmt(t['conf_few'],2)} & {fmt(t['conf_own'],2)} & {fmt(t['conf_selected'],2)} & {fmt(t['best_control_acc'],2)} & {t['diff']:+.2f} [{t['ci'][0]:+.2f}, {t['ci'][1]:+.2f}]{' $\\checkmark$' if t['supported'] else ''} & {fmt(max(t['shuffled_query_diagnostic'].values()) if t['shuffled_query_diagnostic'] else None,2)} \\\\")184        rows.append(f"\\multicolumn{{11}}{{l}}{{\\emph{{{p.replace('->', ' $\\to$ ')}: H6 {pr['H6']} ({pr['n_supported_tasks']} task(s))}}}} \\\\ \\addlinespace")185    txt = f"""% source: experiments/summaries/{run}/tier1_confirmatory.json (scripts/tier1_analysis.py)186\\begin{{tabular}}{{llllcccccll}}187\\toprule188Pair & task & eligible & selected condition (dev) & zero & few & own & selected & best null control & selected $-$ control [95\\% CI] & shuffled-query diag. \\\\189\\midrule190""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"191    (OUT / f"tab_tier1_confirmatory_{run.split('_')[-1][:6]}.tex").write_text(txt)192193194def table_pilot(r, run):195    rows = []196    for en in ("target_iid", "control_iid", "comp_heldout", "len_ood"):197        d, rc = r["across_seeds"][en]["donor"], r["across_seeds"][en]["recipient"]198        g = r["seeds"]["0"]["gap"][en]199        rows.append(f"{en.replace('_', '\\_')} & {fmt(r['chance_level'][en])} & {fmt(d['mean'])} ({fmt(d['sd'] or 0,3)}) & {fmt(rc['mean'])} ({fmt(rc['sd'] or 0,3)}) & {g['estimate']:+.3f} [{g['low']:+.3f}, {g['high']:+.3f}] \\\\")200    txt = f"""% source: experiments/summaries/{run}/results.json201\\begin{{tabular}}{{lcccc}}202\\toprule203Stratum & chance & donor (sd) & recipient (sd) & paired gap, seed 0 [95\\% CI] \\\\204\\midrule205""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"206    (OUT / "tab_pilot_gap.tex").write_text(txt)207208209def table_models(index_path: Path):210    idx = json.loads(index_path.read_text())211    rows = []212    for k, v in idx.items():213        m = v["model"]214        t = v["task"]215        rows.append(f"{k.replace('_', '\\_')} & {m['n_layers']} & {m['d_model']} & {m['n_heads']} & {', '.join(t['excluded_operators']) or '---'} & {'vex' if 'vex' in t['operators'] else '---'} & {t.get('trace_stride', 1)} & {fmt(v['final_dev_acc'])} & {v['train_hash']} \\\\")216    txt = f"""% source: {index_path.relative_to(ROOT)}217\\begin{{tabular}}{{lcccccccl}}218\\toprule219Model & layers & $d$ & heads & excluded & extra op. & stride & dev acc. & train split hash \\\\220\\midrule221""" + "\n".join(rows) + "\n\\bottomrule\n\\end{tabular}\n"222    (OUT / "tab_models.tex").write_text(txt)223224225def main() -> None:226    ap = argparse.ArgumentParser()227    ap.add_argument("--graft")228    ap.add_argument("--pilot")229    ap.add_argument("--tier1", nargs="*", default=[])230    ap.add_argument("--confirmatory")231    ap.add_argument("--tier1-confirmatory", nargs="*", default=[])232    args = ap.parse_args()233    OUT.mkdir(parents=True, exist_ok=True)234    if args.graft:235        r = load(args.graft)236        table_main(r, args.graft)237        table_maps(r, args.graft)238        table_mismatch(r, args.graft)239        table_mechanism(r, args.graft)240    if args.confirmatory:241        table_main(load(args.confirmatory), args.confirmatory, "tab_confirmatory.tex")242    if args.pilot:243        table_pilot(load(args.pilot), args.pilot)244    for run in args.tier1:245        r = load(run)246        suf = run.split("_")[-1][:6]247        table_tier1(r, run, f"tab_tier1_{suf}.tex")248        table_tier1_localization(r, run, f"tab_tier1_localization_{suf}.tex")249    for run in args.tier1_confirmatory:250        table_tier1_confirmatory(run)251    idx = ROOT / "artifacts" / "models" / "matrix_v1" / "index.json"252    if idx.exists():253        table_models(idx)254    print("tables written:", sorted(p.name for p in OUT.glob("*.tex")))255256257if __name__ == "__main__":258    main()259
Files · main
Menu

Theme