runic-reader-2 / code /matched_noise.py
kesha-humonen's picture
Add files using upload-large-folder tool
ed46c5d verified
Raw History Blame Contribute Delete
5.62 kB
"""Real recognition errors vs synthetic corruption on the same transliterations (reviewer 3, point 4).
For every leak-free test photograph, the recognizer output (crop recognizer, loop-cut) and the synthetic corruptions of
the same reference transliteration (noise levels of E4) are translated by the same translator. Two comparisons:
bins photographs grouped by recognizer CER; chrF++ retention (output / reference input) for the real output
and for each synthetic level on the same photographs, with the synthetic CER measured on those items
matched for each photograph the synthetic level whose CER on that item is closest to the recognizer's CER
(only photographs with a level within 10 CER points); corpus chrF++ real vs matched synthetic, cluster
paired bootstrap
Writes results/matched_noise.json.
"""
import json
import os
import random
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import metrics # noqa: E402
import sacrebleu # noqa: E402
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
P = f"{ROOT}/preds"
KEEP = set(json.load(open(f"{ROOT}/data/leakfree.json"))["keep_test"])
OCR_TAG = os.environ.get("OCR_TAG", "ocr4b_cropcut")
CHRF = sacrebleu.CHRF(word_order=2)
def load(name):
return [r for r in metrics.read(f"{P}/{name}.jsonl") if (r.get("row_id") or r.get("base_id") or r["id"]) in KEEP]
def cer(ref, hyp):
d, n = metrics.cer_parts(ref, hyp)
return 100 * d / n
def chrf(hyps, refs):
return CHRF.corpus_score(hyps, [refs]).score
ocr = {r["id"]: r for r in load(f"ocr_test_{OCR_TAG}")}
res = {}
for tr in ("mt4b", "mt4b_noise"):
real = {r["id"]: r for r in load(f"e2e_{OCR_TAG}_{tr}_fromocr")}
gold = {r["id"]: r for r in load(f"e2e_ocr4b_mixcut_{tr}_fromgold")}
noise = {}
for r in load(f"noise_{tr}"):
noise[(r["base_id"], r["level"])] = r
levels = sorted({lv for _, lv in noise})
photos = [i for i in ocr if i in real and i in gold and (ocr[i]["row_id"], 0.0) in noise]
out = {"n_photos": len(photos), "bins": {}, "matched": {}}
def retention(ids, hyp_of):
g = chrf([gold[i]["pred"] for i in ids], [gold[i]["tgt"] for i in ids])
h = chrf([hyp_of(i) for i in ids], [gold[i]["tgt"] for i in ids])
return h, g, 100 * h / g
for lo, hi in ((0, 25), (25, 50), (50, 75), (75, 1e9)):
ids = [i for i in photos if lo <= cer(ocr[i]["target"], ocr[i]["pred"]) < hi]
if not ids:
continue
rc = sum(metrics.cer_parts(ocr[i]["target"], ocr[i]["pred"])[0] for i in ids) / \
sum(metrics.cer_parts(ocr[i]["target"], ocr[i]["pred"])[1] for i in ids) * 100
h, g, ret = retention(ids, lambda i: real[i]["pred"])
b = {"n": len(ids), "real": {"cer": rc, "chrfpp": h, "chrfpp_gold": g, "retention": ret}}
for lv in levels:
if lv == 0.0:
continue
rows = [noise[(ocr[i]["row_id"], lv)] for i in ids]
sc = sum(metrics.cer_parts(r["src_clean"], r["src"])[0] for r in rows) / \
sum(metrics.cer_parts(r["src_clean"], r["src"])[1] for r in rows) * 100
hs, gs, rs = retention(ids, lambda i, lv=lv: noise[(ocr[i]["row_id"], lv)]["pred"])
b[f"synthetic_{lv}"] = {"cer": sc, "chrfpp": hs, "retention": rs}
out["bins"][f"{lo}-{hi if hi < 1e9 else 'inf'}"] = b
# per-photo CER matching
pairs = []
for i in photos:
rc = cer(ocr[i]["target"], ocr[i]["pred"])
best = min((abs(cer(noise[(ocr[i]["row_id"], lv)]["src_clean"], noise[(ocr[i]["row_id"], lv)]["src"]) - rc), lv)
for lv in levels if lv > 0)
if best[0] <= 10:
pairs.append((i, best[1]))
ids = [i for i, _ in pairs]
lv_of = dict(pairs)
syn = {i: noise[(ocr[i]["row_id"], lv_of[i])] for i in ids}
real_cer = 100 * sum(metrics.cer_parts(ocr[i]["target"], ocr[i]["pred"])[0] for i in ids) / \
sum(metrics.cer_parts(ocr[i]["target"], ocr[i]["pred"])[1] for i in ids)
syn_cer = 100 * sum(metrics.cer_parts(syn[i]["src_clean"], syn[i]["src"])[0] for i in ids) / \
sum(metrics.cer_parts(syn[i]["src_clean"], syn[i]["src"])[1] for i in ids)
a = [dict(real[i], id=i) for i in ids]
s = [dict(syn[i], id=i, tgt=real[i]["tgt"], cluster=real[i]["cluster"]) for i in ids]
h_real, g, ret_real = retention(ids, lambda i: real[i]["pred"])
h_syn, _, ret_syn = retention(ids, lambda i: syn[i]["pred"])
out["matched"] = {"n": len(ids), "real_cer": real_cer, "synthetic_cer": syn_cer, "chrfpp_gold": g,
"real_chrfpp": h_real, "synthetic_chrfpp": h_syn, "real_retention": ret_real,
"synthetic_retention": ret_syn, "paired_real_gt_synthetic": metrics.paired_bootstrap_chrf(a, s)}
res[tr] = out
json.dump(res, open(f"{ROOT}/results/matched_noise.json", "w"), indent=1)
for tr, out in res.items():
print(f"=== {tr}: {out['n_photos']} photos")
for k, b in out["bins"].items():
print(f" CER {k:7s} n={b['n']:3d} real CER {b['real']['cer']:.1f} ret {b['real']['retention']:.0f}% | " +
" ".join(f"{lv[10:]}: CER {v['cer']:.1f} ret {v['retention']:.0f}%" for lv, v in b.items() if lv.startswith("synthetic")))
m = out["matched"]
print(f" matched n={m['n']} CER real {m['real_cer']:.1f} syn {m['synthetic_cer']:.1f} | chrF gold {m['chrfpp_gold']:.1f} "
f"real {m['real_chrfpp']:.1f} ({m['real_retention']:.0f}%) syn {m['synthetic_chrfpp']:.1f} ({m['synthetic_retention']:.0f}%) "
f"p={m['paired_real_gt_synthetic']['p']:.3f}")