Commit exp14b harness scripts (arms, judge, monitor, score) to close the reproducibility gap now that the meaning-battery experiment is ratified and closed at D38
This commit is contained in:
parent
2d760fdb4c
commit
0be308498c
4 changed files with 375 additions and 0 deletions
110
eval/exp14b_arms.py
Normal file
110
eval/exp14b_arms.py
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14b — армы батареи смысл-трапов + P1a↔F-disc. ПРОМПТ константен (дискурс), варьируем
|
||||
editor-модель: C=glm-5 / F=gpt-5.4 / X=grok-4.3(reasoning-ON). Общий flash-черновик.
|
||||
|
||||
Реюз exp14-выходов где есть (не пере-оплата). Per-call кап ОБЯЗАТЕЛЕН. ЧЕСТНЫЙ учёт ошибок
|
||||
(finish=length/empty/http/retry — раздельно; НЕ «0 ошибок»). $ персист.
|
||||
|
||||
Использование: exp14b_arms.py [--dry]
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, json, shutil, sys, time
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import exp14_common as C
|
||||
import exp14_prompts as P
|
||||
|
||||
RERUN = Path("/home/ubuntu/books/gu-zhenren/rerun")
|
||||
RECS = {(r["chapter"], r["chunk_idx"]): r for r in json.load(open(RERUN / "records.json"))}
|
||||
INJ = json.load(open(Path("/home/ubuntu/books/gu-zhenren/exp14") / "injection_blocks.json"))
|
||||
EX14 = Path("/home/ubuntu/books/gu-zhenren/exp14/arms")
|
||||
OUT = Path("/home/ubuntu/books/gu-zhenren/exp14b"); OUT.mkdir(parents=True, exist_ok=True)
|
||||
ARMS = OUT / "arms"; ARMS.mkdir(exist_ok=True)
|
||||
|
||||
# базовые (пре-рег §1) + добавленные семьи-редакторы (расширение панели floor-теста, прозрачно)
|
||||
MODEL = {"C": "glm-5", "F": "gpt-5.4", "X": "grok-4.3",
|
||||
"D": "deepseek-v4-pro", "K": "kimi-k2.6", "M": "mistral-large-2512"}
|
||||
EX14_ARM = {"C": "P1a", "F": "F-disc", "X": "X-disc"} # соответствие для реюза (только базовые)
|
||||
CAPS = {"glm-5": 0.08, "gpt-5.4": 0.40, "grok-4.3": 0.40,
|
||||
"deepseek-v4-pro": 0.06, "kimi-k2.6": 0.20, "mistral-large-2512": 0.10}
|
||||
|
||||
# battery chunks + stage2 (для head-to-head C vs F)
|
||||
BATTERY = ["7.0", "8.1", "11.1", "17.0", "19.0", "10.1", "6.0", "16.0", "2.1", "9.1"]
|
||||
STAGE2 = ["17.0", "17.1", "18.0", "18.1", "18.2", "19.0", "19.1"]
|
||||
# что каждому арму нужно покрыть
|
||||
NEED = {
|
||||
"C": sorted(set(BATTERY) | set(STAGE2)),
|
||||
"F": sorted(set(BATTERY) | set(STAGE2)), # F на stage2 уже есть в exp14 → реюз
|
||||
"X": sorted(set(BATTERY)),
|
||||
"D": sorted(set(BATTERY)), # deepseek-v4-pro — только батарея (floor-тест)
|
||||
"K": sorted(set(BATTERY)), # kimi-k2.6
|
||||
"M": sorted(set(BATTERY)), # mistral-large-2512
|
||||
}
|
||||
|
||||
|
||||
def esys():
|
||||
return P.editor_system("discourse")
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument("--dry", action="store_true"); a = ap.parse_args()
|
||||
sp = C.Spender(OUT / "costs.jsonl", CAPS)
|
||||
errs = {"length": 0, "empty": 0, "http": 0, "content_filter": 0, "other": 0, "retried_ok": 0}
|
||||
sysd = esys()
|
||||
for arm, chunks in NEED.items():
|
||||
d = ARMS / arm; d.mkdir(exist_ok=True)
|
||||
model = MODEL[arm]
|
||||
for cid in chunks:
|
||||
outp = d / f"{cid}.txt"
|
||||
if outp.exists():
|
||||
continue
|
||||
# РЕЮЗ exp14 (только базовые C/F/X — D/K/M новых семей в exp14 не было)
|
||||
if arm in EX14_ARM:
|
||||
ex = EX14 / EX14_ARM[arm] / f"{cid}.txt"
|
||||
if ex.exists():
|
||||
shutil.copy(ex, outp)
|
||||
print(f" [reuse exp14] {arm} {cid} ← {EX14_ARM[arm]}")
|
||||
continue
|
||||
ch, ck = cid.split("."); ch = int(ch); ck = int(ck)
|
||||
r = RECS.get((ch, ck))
|
||||
if not r:
|
||||
print(f" [MISS rec] {cid}"); continue
|
||||
einj = INJ.get(f"{ch}/{ck}", {}).get("editor_constraint", "")
|
||||
user = P.editor_user(r["source"], r["draft"])
|
||||
msgs = C.messages_with_injection(sysd, einj, user)
|
||||
fam = "deepseek" if model.startswith("deepseek") else "glm"
|
||||
in_est = C.count_tokens(sysd + einj + user, fam)
|
||||
s = C.spec(model, temp=0.4)
|
||||
ok, pred = sp.guard(model, in_est, s["max_tokens"])
|
||||
print(f" {arm} {cid}: in≈{in_est} predict=${pred:.4f} cap=${CAPS[model]} {'OK' if ok else 'OVER'}")
|
||||
if a.dry or not ok:
|
||||
continue
|
||||
# до 2 ретраев на transient (honest error accounting)
|
||||
text, err, usage, attempts = "", None, {}, 0
|
||||
for attempt in range(3):
|
||||
attempts += 1
|
||||
text, err, usage = C.call(s, msgs, timeout=360)
|
||||
sp.record({"arm": arm, "model": model, "keyenv": s["keyenv"], "id": cid,
|
||||
"attempt": attempts, "err": err, "usage": usage})
|
||||
if not err:
|
||||
if attempt > 0: errs["retried_ok"] += 1
|
||||
break
|
||||
# классификация ошибки
|
||||
if "length" in (err or ""): errs["length"] += 1
|
||||
elif "empty" in (err or ""): errs["empty"] += 1
|
||||
elif "http" in (err or ""): errs["http"] += 1
|
||||
elif "content_filter" in (err or ""): errs["content_filter"] += 1
|
||||
else: errs["other"] += 1
|
||||
if "http" in (err or "") or "transport" in (err or ""):
|
||||
time.sleep(3); continue # transient → retry
|
||||
break # length/empty/filter → не ретраим слепо
|
||||
if err:
|
||||
print(f" ERR {err[:60]} (attempts={attempts})"); continue
|
||||
outp.write_text(text, encoding="utf-8")
|
||||
print(f" ok {len(text)}ch")
|
||||
print(f"\n=== ЧЕСТНЫЙ учёт ошибок: {errs} ===")
|
||||
print(f"=== spend by key: {dict((k,round(v,4)) for k,v in sp.by_key.items())} ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
105
eval/exp14b_judge.py
Normal file
105
eval/exp14b_judge.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14b — судейский проход по SOFT-классам (e чэнъюй, f кореференция) × армы C/F/X.
|
||||
≥2 КРОСС-семейных span-цитирующих судьи, self-family exclusion (урок D37: exp14 фронтир
|
||||
судил ОДИН судья). leave-one-out. Катастроф-скрин к победителю. $ персист, честный учёт.
|
||||
|
||||
Судьи: gpt-5-mini(openai) + kimi(moonshot) + mistral(mistral). Для F(gpt) gpt-5-mini исключён.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse, json, re, sys
|
||||
from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import exp14_common as C
|
||||
|
||||
ARMS = Path("/home/ubuntu/books/gu-zhenren/exp14b/arms")
|
||||
OUT = Path("/home/ubuntu/books/gu-zhenren/exp14b")
|
||||
ARM_FAMILY = {"C": "zai", "F": "openai", "X": "xai",
|
||||
"D": "deepseek", "K": "moonshot", "M": "mistral"} # K→kimi-судья excl; M→mistral-судья excl
|
||||
JUDGE_FAMILY = {"gpt-5-mini": "openai", "kimi-k2.6": "moonshot", "mistral-large-2512": "mistral"}
|
||||
ALL_ARMS = ("C", "F", "X", "D", "K", "M")
|
||||
CAPS = {"gpt-5-mini": 0.05, "kimi-k2.6": 0.08, "mistral-large-2512": 0.05}
|
||||
|
||||
SOFT = [
|
||||
("e1", "8.1", "物是人非", "контраст «вещи/места ПРЕЖНИЕ — люди уже НЕ ТЕ»; плоское «всё изменилось» теряет половину (物是)."),
|
||||
("e2", "2.1", "韬光养晦", "«скрывать блеск, выжидать» — таиться/копить силы, не выпячиваться; не «развиваться/трудиться»."),
|
||||
("f1", "16.0", "这促使它不得不外出,寻找野生的酒囊花", "субъект «它» = ВИННЫЙ ЧЕРВЬ (酒虫); это ОН вынужден выйти искать дикие酒囊花. Не спутать субъект."),
|
||||
("f2", "11.1", "他们的身形隐没在阴影中…分不清男女", "двое (пол НЕЯСЕН) в тени; НЕ навязывать род/пол, НЕ выдумывать кто это."),
|
||||
]
|
||||
SYS = ("Ты — билингвальный эксперт zh→ru. Оцени, верно ли русский перевод передаёт смысл ОДНОГО "
|
||||
"указанного китайского фрагмента. Ответь СТРОГО JSON без markdown: "
|
||||
'{"verdict":"correct|partial|wrong","catastrophe":true|false,'
|
||||
'"evidence":"дословная цитата из перевода ≤15 слов","note":"кратко"}. '
|
||||
"catastrophe=true при подмене субъекта/участника или перевёрнутом смысле.")
|
||||
|
||||
|
||||
def run():
|
||||
ap = argparse.ArgumentParser(); ap.add_argument("--dry", action="store_true"); a = ap.parse_args()
|
||||
sp = C.Spender(OUT / "judge_costs.jsonl", CAPS)
|
||||
vpath = OUT / "soft_verdicts.jsonl"; out = []; done = set()
|
||||
if vpath.exists():
|
||||
for l in vpath.read_text(encoding="utf-8").splitlines():
|
||||
if l.strip():
|
||||
v = json.loads(l); out.append(v); done.add((v["trap"], v["arm"], v["judge"]))
|
||||
tasks = []
|
||||
for tid, cid, span, meaning in SOFT:
|
||||
for arm in ALL_ARMS:
|
||||
fp = ARMS / arm / f"{cid}.txt"
|
||||
if not fp.exists(): continue
|
||||
for jm in JUDGE_FAMILY:
|
||||
if JUDGE_FAMILY[jm] == ARM_FAMILY[arm]: continue # self-family exclusion
|
||||
if (tid, arm, jm) in done: continue
|
||||
tasks.append((tid, cid, span, meaning, arm, jm, fp.read_text(encoding="utf-8")))
|
||||
print(f"soft-judge tasks: {len(tasks)} new ({len(done)} done)")
|
||||
if a.dry:
|
||||
from collections import Counter; print(Counter(t[5] for t in tasks)); return
|
||||
errs = {"empty": 0, "http": 0, "other": 0}
|
||||
for tid, cid, span, meaning, arm, jm, text in tasks:
|
||||
user = (f"КИТАЙСКИЙ ФРАГМЕНТ: {span}\nДОПУСТИМЫЙ СМЫСЛ: {meaning}\n\n"
|
||||
f"РУССКИЙ ПЕРЕВОД ОТРЫВКА:\n{text}\n\nОцени передачу ТОЛЬКО этого фрагмента. JSON.")
|
||||
s = C.spec(jm, max_tokens=(16000 if jm == "kimi-k2.6" else 6000))
|
||||
in_est = C.count_tokens(SYS + user, "glm")
|
||||
ok, pred = sp.guard(jm, in_est, s["max_tokens"])
|
||||
if not ok:
|
||||
print(f" OVER {tid} {arm} {jm}"); continue
|
||||
t, err, usage = C.call(s, [{"role": "system", "content": SYS}, {"role": "user", "content": user}], timeout=300)
|
||||
cst = sp.record({"model": jm, "judge": jm, "keyenv": s["keyenv"], "trap": tid, "arm": arm, "err": err, "usage": usage})
|
||||
if err:
|
||||
errs["empty" if "empty" in err else ("http" if "http" in err else "other")] += 1
|
||||
v = parse(t) if not err else {"verdict": "ERR"}
|
||||
v.update(trap=tid, arm=arm, judge=jm)
|
||||
out.append(v)
|
||||
with open(vpath, "a", encoding="utf-8") as f: f.write(json.dumps(v, ensure_ascii=False) + "\n")
|
||||
print(f" {tid} {arm} {jm:<18} → {v.get('verdict'):<8} cat={v.get('catastrophe')} ${cst:.4f}")
|
||||
print(f"\n=== ошибки судей (честно): {errs} ===")
|
||||
summarize(out)
|
||||
|
||||
|
||||
def parse(text):
|
||||
m = re.search(r"\{.*\}", text, re.S)
|
||||
if not m: return {"verdict": "PARSE"}
|
||||
try:
|
||||
d = json.loads(m.group(0))
|
||||
return {"verdict": str(d.get("verdict", "?")).lower(), "catastrophe": bool(d.get("catastrophe")),
|
||||
"evidence": (d.get("evidence") or "")[:100], "note": (d.get("note") or "")[:100]}
|
||||
except Exception:
|
||||
vv = re.search(r'"verdict"\s*:\s*"(\w+)"', m.group(0))
|
||||
return {"verdict": vv.group(1).lower() if vv else "PARSE", "catastrophe": "true" in m.group(0).lower()}
|
||||
|
||||
|
||||
def summarize(out):
|
||||
from collections import defaultdict
|
||||
agg = defaultdict(lambda: defaultdict(list))
|
||||
for v in out: agg[v["trap"]][v["arm"]].append(v)
|
||||
print("\n=== SOFT-классы: вердикты (арм: судьи) ===")
|
||||
for tid, cid, span, meaning in SOFT:
|
||||
if tid not in agg: continue
|
||||
print(f"\n{tid} ch{cid} [{span}]:")
|
||||
for arm in ALL_ARMS:
|
||||
vs = agg[tid].get(arm)
|
||||
if not vs: continue
|
||||
zv = [(v["judge"][:4], v["verdict"], "CAT" if v.get("catastrophe") else "") for v in vs]
|
||||
print(f" {arm}: {zv}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
52
eval/exp14b_monitor.py
Normal file
52
eval/exp14b_monitor.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14b §2 — слепой head-to-head пакет P1a(=C, glm-5 дискурс) ↔ F-disc(=F, gpt-5.4 дискурс)
|
||||
на главах 17/18/19 (+ оригинал для верности). Метки U1/U2 перемешаны по главам, ключ отдельно.
|
||||
Копирайт: ВНЕ git.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json, random
|
||||
from pathlib import Path
|
||||
import exp14_common as C
|
||||
|
||||
ARMS = Path("/home/ubuntu/books/gu-zhenren/exp14b/arms")
|
||||
RERUN = Path("/home/ubuntu/books/gu-zhenren/rerun")
|
||||
RECS = {(r["chapter"], r["chunk_idx"]): r for r in json.load(open(RERUN / "records.json"))}
|
||||
OUT = Path("/home/ubuntu/books/gu-zhenren/exp14b/monitor"); OUT.mkdir(parents=True, exist_ok=True)
|
||||
FINALISTS = {"cheap-P1a": "C", "frontier-F-disc": "F"}
|
||||
CHAPTERS = [19, 17, 18]
|
||||
CHUNKS = {19: [0, 1], 17: [0, 1], 18: [0, 1, 2]}
|
||||
SALT = "exp14b-h2h-blind-v1"
|
||||
|
||||
|
||||
def ch_text(arm, ch):
|
||||
return "\n\n".join((ARMS / arm / f"{ch}.{ck}.txt").read_text(encoding="utf-8") for ck in CHUNKS[ch])
|
||||
def ch_src(ch):
|
||||
return "\n".join(RECS[(ch, ck)]["source"] for ck in CHUNKS[ch])
|
||||
|
||||
|
||||
def main():
|
||||
miss = [(f, ch, ck) for f, a in FINALISTS.items() for ch in CHAPTERS for ck in CHUNKS[ch]
|
||||
if not (ARMS / a / f"{ch}.{ck}.txt").exists()]
|
||||
if miss:
|
||||
print("⚠ нет выходов:", miss[:8]); return
|
||||
key = {}
|
||||
md = ["# exp14b head-to-head — P1a(дешёвый дискурс) ↔ F-disc(фронтир). Слепо.\n",
|
||||
"Две версии на главу (U1/U2, порядок в каждой главе СВОЙ). Сверху китайский оригинал. "
|
||||
"Оцени РАЗДЕЛЬНО: (A) какая читается лучше как русская проза; (B) верность смыслу (сверяя с "
|
||||
"оригиналом) — смысловые ошибки/инверсии со спанами. Насколько дешёвый близок к фронтиру? Ключ — `_КЛЮЧ.json`, ПОСЛЕ.\n"]
|
||||
for ch in CHAPTERS:
|
||||
order = list(FINALISTS); random.Random(f"{SALT}-{ch}").shuffle(order)
|
||||
labels = {f"U{i+1}": order[i] for i in range(len(order))}
|
||||
key[str(ch)] = {lab: {"finalist": fin, "arm": FINALISTS[fin]} for lab, fin in labels.items()}
|
||||
md.append(f"\n\n{'='*60}\n## Глава {ch}\n{'='*60}\n\n### ОРИГИНАЛ (zh)\n\n```\n{ch_src(ch)}\n```\n")
|
||||
for lab in ("U1", "U2"):
|
||||
md.append(f"\n### {lab}\n\n{ch_text(FINALISTS[labels[lab]], ch)}\n")
|
||||
(OUT / "h2h.md").write_text("\n".join(md), encoding="utf-8")
|
||||
(OUT / "_КЛЮЧ.json").write_text(json.dumps({"salt": SALT, "finalists": FINALISTS, "per_chapter": key}, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
print(f"→ {OUT/'h2h.md'} (2 версии × 3 главы + оригинал)")
|
||||
for ch in CHAPTERS:
|
||||
print(f" гл.{ch}: " + ", ".join(f"{lab}={key[str(ch)][lab]['finalist']}" for lab in ("U1","U2")))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
108
eval/exp14b_score.py
Normal file
108
eval/exp14b_score.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env python3
|
||||
"""exp14b — детерминированный скоринг DET-классов (a/b/c/d): извлекает span-рендер трапа в
|
||||
каждом арме (C/F/X) + применяет правило (полярность/число/ранг/направление) → fix/fail/ambiguous
|
||||
+ печатает span для верификации. $0. Реюз P0 для контраста где есть.
|
||||
|
||||
SOFT-классы (e/f) — отдельным судейским проходом (exp14b_judge.py).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import json, re
|
||||
from pathlib import Path
|
||||
|
||||
ARMS = Path("/home/ubuntu/books/gu-zhenren/exp14b/arms")
|
||||
EX14 = Path("/home/ubuntu/books/gu-zhenren/exp14/arms")
|
||||
|
||||
# DET-трапы: (id, chunk, anchor-regex для поиска строки, rule(line)->'fix'/'fail'/'?', описание)
|
||||
def rule_polarity_all(line):
|
||||
l = line.lower()
|
||||
if re.search(r'ни один|никто|не знал ни|ни одного человека не', l): return 'fail'
|
||||
if re.search(r'все |всем |каждый|мёртв|не было ни одного,? (кто|который)', l): return 'fix'
|
||||
return '?'
|
||||
def rule_polarity_envy(line):
|
||||
l = line.lower()
|
||||
if 'завист' not in l and 'зависти' not in l and 'зависть' not in l: return '?'
|
||||
if re.search(r'никто не|не было зависти|без зависти', l): return 'fail'
|
||||
return 'fix' # завист присутствует в утвердит. контексте
|
||||
def rule_num_b1(line):
|
||||
l = line.lower()
|
||||
# 十二分之一 = 1/12 ; 三成 = 30%
|
||||
has12 = bool(re.search(r'одну двенадцатую|двенадцатую часть|1/12|двенадцатой', l))
|
||||
has30 = bool(re.search(r'тридцать процент|30\s*%|три десят|целых три десят', l))
|
||||
bad = bool(re.search(r'\b3\s*%|\bтри процент', l)) # 三成→3% = 10× ошибка
|
||||
if bad and not has30: return 'fail'
|
||||
if has30 or has12: return 'fix'
|
||||
return '?'
|
||||
def rule_num_b2(line):
|
||||
l = line.lower()
|
||||
if re.search(r'сорок четыре процент|44\s*%|сорока четырёх процент', l): return 'fix'
|
||||
if re.search(r'4[.,]4|\b4\s*%|четыре процент', l): return 'fail'
|
||||
return '?'
|
||||
def rule_rank_above(line):
|
||||
l = line.lower()
|
||||
if re.search(r'среди людей|одним из', l): return 'fail'
|
||||
if re.search(r'над людьми|выше (других|прочих|обычных)|возвыс|человеком над', l): return 'fix'
|
||||
return '?'
|
||||
def rule_grade_bing(line):
|
||||
l = line.lower()
|
||||
# 丙 = 3-й = В/C ; ошибка = Б/второй/A/B
|
||||
if re.search(r'разряд[а-я]* в|класс[а-я]* в|«в»|третьего разряда|третий разряд', l): return 'fix'
|
||||
if re.search(r'разряд[а-я]* б|класс[а-я]* б|«б»|второго разряда', l): return 'fail'
|
||||
return '?'
|
||||
def rule_patriarch(line):
|
||||
l = line.lower()
|
||||
if re.search(r'старейшин', l): return 'fail'
|
||||
if re.search(r'глав[аеуы].{0,20}поколени|четвёртого поколения|патриарх|четвёртый глава', l): return 'fix'
|
||||
return '?'
|
||||
def rule_counterfactual(line):
|
||||
l = line.lower()
|
||||
if re.search(r'иначе|в противном случае|не то|если бы не', l): return 'fix'
|
||||
return '?'
|
||||
|
||||
TRAPS = [
|
||||
("a1", "7.0", r'Гуюэ.*(знал|извест|мёртв)|(знал|извест).*Гуюэ', rule_polarity_all, "все знали (двойн.отриц.)"),
|
||||
("a2", "7.0", r'завист|зависти|зависть', rule_polarity_envy, "все полны зависти (无不)"),
|
||||
("b1", "10.1", r'двенадцат|тридцать|десят|процент|祭', rule_num_b1, "1/12 & 30%"),
|
||||
("b2", "6.0", r'сорок|44|процент|апертур', rule_num_b2, "44%"),
|
||||
("c1", "17.0", r'людьми|людей|возвыс', rule_rank_above, "над людьми (人上之人)"),
|
||||
("c2", "9.1", r'Фан Юань.*(разряд|класс)|(разряд|класс).*Фан', rule_grade_bing, "丙=разряд В"),
|
||||
("c3", "16.0", r'族长|поколени|старейшин|глава клана|патриарх', rule_patriarch, "族长=глава, не старейшина"),
|
||||
("d1", "19.0", r'иначе|счастью|повезло|противном', rule_counterfactual, "幸亏…否则 контрфактив"),
|
||||
("d2", "7.0", r'иначе|противном|揭破|разоблач', rule_counterfactual, "否则 контрфактив"),
|
||||
]
|
||||
ARM_MODELS = {"C": "glm-5(P1a)", "F": "gpt-5.4(F-disc)", "X": "grok-4.3(X-disc)", "P0": "prod-baseline",
|
||||
"D": "deepseek-v4-pro", "K": "kimi-k2.6", "M": "mistral-large"}
|
||||
ALL_ARMS = ("C", "F", "X", "D", "K", "M")
|
||||
|
||||
|
||||
def find_span(text, anchor):
|
||||
for line in text.split("\n"):
|
||||
if line.strip() and re.search(anchor, line):
|
||||
return line.strip()
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
print("=== exp14b DET-скоринг (a/b/c/d) — fix/fail/? + span ===\n")
|
||||
rates = {arm: {"fix": 0, "fail": 0, "?": 0} for arm in ALL_ARMS}
|
||||
for tid, cid, anchor, rule, desc in TRAPS:
|
||||
print(f"── {tid} ch{cid} [{desc}] ──")
|
||||
for arm in ("P0",) + ALL_ARMS:
|
||||
base = ARMS if arm in ALL_ARMS else EX14
|
||||
fp = base / arm / f"{cid}.txt" if arm != "P0" else EX14 / "P0" / f"{cid}.txt"
|
||||
if not fp.exists():
|
||||
print(f" {arm:<3} —"); continue
|
||||
span = find_span(fp.read_text(encoding="utf-8"), anchor)
|
||||
verd = rule(span) if span else '?'
|
||||
if arm in rates: rates[arm][verd] += 1
|
||||
mark = {"fix": "✓", "fail": "✗КАТ" if tid in ("a1","a2","c1") else "✗", "?": "?"}[verd]
|
||||
print(f" {arm:<3} {mark:<5} {span[:88] if span else '(span не найден)'}")
|
||||
print()
|
||||
print("=== FIX-RATE по армам (DET, 9 инстансов) ===")
|
||||
for arm in ALL_ARMS:
|
||||
r = rates[arm]; n = sum(r.values())
|
||||
print(f" {arm} {ARM_MODELS[arm]:<18}: fix {r['fix']}/{n}, fail {r['fail']}, ? {r['?']}")
|
||||
json.dump(rates, open(Path("/home/ubuntu/books/gu-zhenren/exp14b")/"det_rates.json","w"), ensure_ascii=False, indent=1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Reference in a new issue