Upload scripts/peer/peer_score.py with huggingface_hub
Browse files- scripts/peer/peer_score.py +119 -0
scripts/peer/peer_score.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
peer_score.py -- score peer generations, matching longeval_score.py EXACTLY.
|
| 4 |
+
|
| 5 |
+
The scoring rule is copied deliberately, not reimplemented: norm() strips every
|
| 6 |
+
non-alphanumeric character and lowercases, and a prompt scores 1 only when EVERY
|
| 7 |
+
expected string appears in the normalised generation (RULER's recall criterion).
|
| 8 |
+
Any divergence here would make peer numbers incomparable to ours, which is the
|
| 9 |
+
entire point of the exercise.
|
| 10 |
+
|
| 11 |
+
Writes summary.csv and depth_summary.csv in the SAME shape run_longeval.sh emits,
|
| 12 |
+
so the existing notebook builders read peer results without modification.
|
| 13 |
+
|
| 14 |
+
usage: peer_score.py --gen-dir gen/ --meta-dir prompts/ --out-dir results/
|
| 15 |
+
--models qwen3-4b,spark-4b --tasks niah_single,val_backward
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import csv
|
| 20 |
+
import json
|
| 21 |
+
import math
|
| 22 |
+
import os
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def norm(s):
|
| 26 |
+
return "".join(ch for ch in s.lower() if ch.isalnum())
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def wilson(h, n):
|
| 30 |
+
if not n:
|
| 31 |
+
return float("nan")
|
| 32 |
+
z, p = 1.96, h / n
|
| 33 |
+
return 100.0 * z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / (1 + z * z / n)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def score_one(gen_path, meta_path):
|
| 37 |
+
"""-> (hits, n, {depth: [hits, n]})"""
|
| 38 |
+
meta = json.load(open(meta_path))
|
| 39 |
+
items = {it["i"]: it for it in meta["items"]}
|
| 40 |
+
hits = n = 0
|
| 41 |
+
by_depth = {}
|
| 42 |
+
with open(gen_path) as f:
|
| 43 |
+
for line in f:
|
| 44 |
+
line = line.strip()
|
| 45 |
+
if not line:
|
| 46 |
+
continue
|
| 47 |
+
g = json.loads(line)
|
| 48 |
+
it = items.get(g["i"])
|
| 49 |
+
if it is None:
|
| 50 |
+
continue
|
| 51 |
+
tn = norm(g["generated"])
|
| 52 |
+
ok = all(norm(e) in tn for e in it["expected"])
|
| 53 |
+
hits += ok
|
| 54 |
+
n += 1
|
| 55 |
+
d = it["depth"]
|
| 56 |
+
by_depth.setdefault(d, [0, 0])
|
| 57 |
+
by_depth[d][0] += ok
|
| 58 |
+
by_depth[d][1] += 1
|
| 59 |
+
return hits, n, by_depth
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def main():
|
| 63 |
+
ap = argparse.ArgumentParser()
|
| 64 |
+
ap.add_argument("--gen-dir", required=True)
|
| 65 |
+
ap.add_argument("--meta-dir", required=True)
|
| 66 |
+
ap.add_argument("--out-dir", required=True)
|
| 67 |
+
ap.add_argument("--length", required=True,
|
| 68 |
+
help="prompt length; metas live at <meta-dir>/<model>/<length>/")
|
| 69 |
+
ap.add_argument("--models", required=True, help="comma list of model keys")
|
| 70 |
+
ap.add_argument("--tasks", required=True, help="comma list of task names")
|
| 71 |
+
args = ap.parse_args()
|
| 72 |
+
|
| 73 |
+
models = args.models.split(",")
|
| 74 |
+
tasks = args.tasks.split(",")
|
| 75 |
+
os.makedirs(args.out_dir, exist_ok=True)
|
| 76 |
+
|
| 77 |
+
per_task = {} # task -> model -> (hits, n)
|
| 78 |
+
per_depth = {} # depth -> model -> [hits, n]
|
| 79 |
+
for m in models:
|
| 80 |
+
for t in tasks:
|
| 81 |
+
g = os.path.join(args.gen_dir, f"{m}_{t}.jsonl")
|
| 82 |
+
# layout is <meta-dir>/<model>/<length>/<task>_meta.json -- the
|
| 83 |
+
# length level was missing here, so every meta lookup failed and the
|
| 84 |
+
# scorer wrote a header-only summary instead of erroring.
|
| 85 |
+
md = os.path.join(args.meta_dir, m, args.length, f"{t}_meta.json")
|
| 86 |
+
if not (os.path.exists(g) and os.path.exists(md)):
|
| 87 |
+
print(f" skip {m}/{t}: missing {'gen' if not os.path.exists(g) else 'meta'}")
|
| 88 |
+
continue
|
| 89 |
+
h, n, bd = score_one(g, md)
|
| 90 |
+
if not n:
|
| 91 |
+
print(f" skip {m}/{t}: 0 scorable"); continue
|
| 92 |
+
per_task.setdefault(t, {})[m] = (h, n)
|
| 93 |
+
for d, (dh, dn) in bd.items():
|
| 94 |
+
cell = per_depth.setdefault(d, {}).setdefault(m, [0, 0])
|
| 95 |
+
cell[0] += dh; cell[1] += dn
|
| 96 |
+
print(f" {m:<16} {t:<16} {h}/{n} = {100.0*h/n:5.1f}% +-{wilson(h,n):4.1f}")
|
| 97 |
+
|
| 98 |
+
have = [m for m in models if any(m in v for v in per_task.values())]
|
| 99 |
+
if not have:
|
| 100 |
+
raise SystemExit(f" FATAL: nothing scorable for length {args.length}. "
|
| 101 |
+
f"Checked gen={args.gen_dir}/<model>_<task>.jsonl and "
|
| 102 |
+
f"meta={args.meta_dir}/<model>/{args.length}/<task>_meta.json")
|
| 103 |
+
with open(os.path.join(args.out_dir, "summary.csv"), "w", newline="") as f:
|
| 104 |
+
w = csv.writer(f); w.writerow(["task"] + have)
|
| 105 |
+
for t in sorted(per_task):
|
| 106 |
+
w.writerow([t] + [f"{100.0*per_task[t][m][0]/per_task[t][m][1]:.1f}"
|
| 107 |
+
if m in per_task[t] else "" for m in have])
|
| 108 |
+
with open(os.path.join(args.out_dir, "depth_summary.csv"), "w", newline="") as f:
|
| 109 |
+
w = csv.writer(f); w.writerow(["depth", "model", "hits", "n", "pct", "wilson95"])
|
| 110 |
+
for d in sorted(per_depth):
|
| 111 |
+
for m in have:
|
| 112 |
+
if m in per_depth[d]:
|
| 113 |
+
h, n = per_depth[d][m]
|
| 114 |
+
w.writerow([d, m, h, n, f"{100.0*h/n:.1f}", f"{wilson(h,n):.1f}"])
|
| 115 |
+
print(f"\n wrote {args.out_dir}/summary.csv and depth_summary.csv")
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
main()
|