115 lines
5.9 KiB
Python
115 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
||
"""$0 детерминированная батарея верности на ГОТОВЫХ парах база↔митигация P4 (09.07).
|
||
|
||
Это тот источник, на который промт сессии указывал прямо: `~/books/gu-zhenren/research15-probes/`.
|
||
Генерация за них уже оплачена 09.07 — здесь не тратится ни цента.
|
||
|
||
Судья отвечает на «исказила ли митигация СМЫСЛ». Половина этого вопроса — про ПРОПУСКИ и обрывы —
|
||
решается детерминированно и бесплатно: митигация, которая режет текст, видна по длине и по числу
|
||
предложений относительно ОДНОГО И ТОГО ЖЕ исходника. Оставшаяся половина (искажение при сохранённом
|
||
объёме) без судьи не берётся, и это здесь честно помечено, а не замазано.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import collections
|
||
import json
|
||
import re
|
||
import statistics
|
||
import sys
|
||
import unicodedata
|
||
from pathlib import Path
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
sys.path.insert(0, str(REPO / "eval"))
|
||
from refusal_bench import split_sentences
|
||
|
||
RAW = Path.home() / "books" / "gu-zhenren" / "research15-probes" / "probes" / "raw"
|
||
ARM_NAME = {"G0": "база", "G1": "инстр.ПОСЛЕ", "G2": "few-shot", "G3": "яз.констрейнт", "G4": "комбо",
|
||
"D0": "база", "D1": "инстр.ПОСЛЕ", "D2": "few-shot", "DP": "префилл"}
|
||
|
||
|
||
def cjk_share(s: str) -> float:
|
||
if not s:
|
||
return 1.0
|
||
return sum(1 for c in s if "CJK UNIFIED" in (unicodedata.name(c, "") or "")) / len(s)
|
||
|
||
|
||
def load():
|
||
out = {}
|
||
for f in sorted(RAW.glob("echo-*.json")):
|
||
d = json.load(open(f))
|
||
t = d.get("text") or ""
|
||
# исходник фрагмента лежит в user-сообщении между шапкой и (для арма G1) постинструкцией
|
||
u = d.get("user") or ""
|
||
src = u.split("Переведи следующий фрагмент:\n\n", 1)[-1]
|
||
src = src.split("\n\nНапоминание:")[0]
|
||
out[d["tag"]] = dict(tag=d["tag"], text=t, src=src, cjk=cjk_share(t), echo=cjk_share(t) > 0.15)
|
||
return out
|
||
|
||
|
||
def metrics(src: str, out: str) -> dict:
|
||
src_ns = len(re.sub(r"\s", "", src))
|
||
out_ns = len(re.sub(r"\s", "", out))
|
||
return dict(chars=len(out), len_ratio=round(out_ns / src_ns, 3) if src_ns else None,
|
||
sent_src=len(split_sentences(src)), sent_out=len(split_sentences(out)),
|
||
cjk=round(cjk_share(out), 4))
|
||
|
||
|
||
def main() -> int:
|
||
d = load()
|
||
groups = collections.defaultdict(dict)
|
||
for tag, r in d.items():
|
||
m = re.match(r"echo-grok-(gu-\d+)-(G\d)$", tag)
|
||
if m:
|
||
groups[("grok", m.group(1))][m.group(2)] = r
|
||
continue
|
||
m = re.match(r"echo-ds-gu008-(D\w+)-s(\d)$", tag)
|
||
if m:
|
||
groups[("deepseek", "gu-008-s" + m.group(2))][m.group(1)] = r
|
||
|
||
print("=== ПАРЫ база↔митигация из P4 (генерация оплачена 09.07; здесь $0) ===\n")
|
||
print(f"{'семейство':<9}{'фрагмент':<14}{'арм':<14}{'len_ratio':>10}{'предл. вых/исх':>16}"
|
||
f"{'симв':>7}{'Δlen_ratio к базе':>19}")
|
||
rows = []
|
||
for (fam, frag), arms in sorted(groups.items()):
|
||
base_key = "G0" if fam == "grok" else "D0"
|
||
base = arms.get(base_key)
|
||
if not base or base["echo"] or not base["text"]:
|
||
continue
|
||
bm = metrics(base["src"], base["text"])
|
||
print(f"{fam:<9}{frag:<14}{ARM_NAME[base_key]:<14}{bm['len_ratio']:>10}"
|
||
f"{str(bm['sent_out']) + '/' + str(bm['sent_src']):>16}{bm['chars']:>7}{'—':>19}")
|
||
for a in ("G1", "G2", "G4", "D1", "D2", "DP"):
|
||
m = arms.get(a)
|
||
if not m or m["echo"] or not m["text"]:
|
||
continue
|
||
mm = metrics(base["src"], m["text"])
|
||
delta = mm["len_ratio"] - bm["len_ratio"]
|
||
rows.append(dict(fam=fam, frag=frag, arm=a, d_len=delta,
|
||
d_sent=mm["sent_out"] - bm["sent_out"],
|
||
base_lr=bm["len_ratio"], mit_lr=mm["len_ratio"]))
|
||
print(f"{'':<9}{'':<14}{ARM_NAME[a]:<14}{mm['len_ratio']:>10}"
|
||
f"{str(mm['sent_out']) + '/' + str(mm['sent_src']):>16}{mm['chars']:>7}{delta:>+19.3f}")
|
||
print()
|
||
|
||
print(f"ВСЕГО ПАР: {len(rows)}\n")
|
||
print("=== СВОДКА ПО МИТИГАЦИИ: режет ли она текст относительно базы на ТОМ ЖЕ исходнике ===")
|
||
print(f"{'митигация':<16}{'пар':>5}{'медиана Δlen_ratio':>21}{'пар с Δ<-0.15':>15}"
|
||
f"{'пар с Δ>+0.15':>15}{'медиана Δпредл.':>17}")
|
||
for a in ("G1", "G2", "G4", "D1", "D2", "DP"):
|
||
rs = [r for r in rows if r["arm"] == a]
|
||
if not rs:
|
||
continue
|
||
dl = [r["d_len"] for r in rs]
|
||
ds = [r["d_sent"] for r in rs]
|
||
print(f"{ARM_NAME[a]:<16}{len(rs):>5}{statistics.median(dl):>+21.3f}"
|
||
f"{sum(1 for x in dl if x < -0.15):>15}{sum(1 for x in dl if x > 0.15):>15}"
|
||
f"{statistics.median(ds):>+17.1f}")
|
||
print("\nЧТЕНИЕ: Δlen_ratio < −0.15 = митигированный перевод заметно КОРОЧЕ базы на том же")
|
||
print("исходнике — подпись пропуска. Δ > +0.15 — многословнее (отсебятина или развёрнутость).")
|
||
print("⚠ Ось «искажение при сохранённом объёме» этим прибором НЕ берётся — только судьёй.")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|