Instructions to use kesha-humonen/runic-reader-2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use kesha-humonen/runic-reader-2 with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Download code/metrics.py from kesha-humonen/runic-reader-2: direct link, hf CLI and curl.
- Browser
- Download file 5 kB
-
https://huggingface.co/kesha-humonen/runic-reader-2/resolve/main/code/metrics.py
- Command line
-
hf download hf://kesha-humonen/runic-reader-2/code/metrics.py
-
curl -L -o metrics.py https://huggingface.co/kesha-humonen/runic-reader-2/resolve/main/code/metrics.py
5 kB
| """Metrics with 95% percentile-bootstrap confidence intervals. | |
| OCR: CER and WER (corpus-level: total edits / total reference length), exact match | |
| MT : chrF++ (sacrebleu CHRF word_order=2) and BLEU, corpus-level | |
| Paired bootstrap p-values compare two systems on the same items. | |
| Resampling unit: inscription clusters (all items of a cluster are drawn together), so that several images or | |
| pairs of one inscription do not count as independent evidence. BOOT_UNIT=item restores item-level resampling. | |
| Items without a "cluster" field (synthetic images) form their own cluster. | |
| """ | |
| import os | |
| import json | |
| import random | |
| import sys | |
| import sacrebleu | |
| from rapidfuzz.distance import Levenshtein | |
| B = int(os.environ.get("BOOT_B", 1000)) | |
| UNIT = os.environ.get("BOOT_UNIT", "cluster") | |
| def _groups(rows): | |
| """Index groups that are resampled together.""" | |
| if UNIT == "item": | |
| return [[i] for i in range(len(rows))] | |
| by = {} | |
| for i, r in enumerate(rows): | |
| c = r.get("cluster") | |
| key = ("c", c) if c is not None else ("i", i) | |
| by.setdefault(key, []).append(i) | |
| return list(by.values()) | |
| def _draw(groups, rng): | |
| """One bootstrap replicate: as many clusters as there are, drawn with replacement.""" | |
| idx = [] | |
| for _ in range(len(groups)): | |
| idx.extend(groups[rng.randrange(len(groups))]) | |
| return idx | |
| def read(path): | |
| return [json.loads(l) for l in open(path) if l.strip()] | |
| def cer_parts(ref, hyp): | |
| return Levenshtein.distance(ref, hyp), max(len(ref), 1) | |
| def wer_parts(ref, hyp): | |
| r, h = ref.split(), hyp.split() | |
| return Levenshtein.distance(r, h), max(len(r), 1) | |
| def ocr_scores(rows, ref_key="target", hyp_key="pred", seed=0): | |
| cp = [cer_parts(r[ref_key], r[hyp_key]) for r in rows] | |
| wp = [wer_parts(r[ref_key], r[hyp_key]) for r in rows] | |
| em = [float(r[ref_key] == r[hyp_key]) for r in rows] | |
| def agg(idx): | |
| return (100 * sum(cp[i][0] for i in idx) / sum(cp[i][1] for i in idx), | |
| 100 * sum(wp[i][0] for i in idx) / sum(wp[i][1] for i in idx), | |
| 100 * sum(em[i] for i in idx) / len(idx)) | |
| n = len(rows) | |
| point = agg(range(n)) | |
| rng = random.Random(seed) | |
| g = _groups(rows) | |
| boots = [agg(_draw(g, rng)) for _ in range(B)] | |
| ci = [] | |
| for k in range(3): | |
| v = sorted(b[k] for b in boots) | |
| ci.append((v[int(0.025 * B)], v[int(0.975 * B)])) | |
| return dict(n=n, cer=point[0], cer_ci=ci[0], wer=point[1], wer_ci=ci[1], exact=point[2]) | |
| def mt_stats(hyps, refs): | |
| chrf = sacrebleu.CHRF(word_order=2) | |
| bleu = sacrebleu.BLEU() | |
| return chrf, bleu | |
| def mt_scores(rows, hyp_key="pred", ref_key="tgt", seed=0, boot=True): | |
| hyps = [r.get(hyp_key) or "" for r in rows] | |
| refs = [r[ref_key] for r in rows] | |
| chrf = sacrebleu.CHRF(word_order=2) | |
| bleu = sacrebleu.BLEU() | |
| out = dict(n=len(rows), chrfpp=chrf.corpus_score(hyps, [refs]).score, bleu=bleu.corpus_score(hyps, [refs]).score, | |
| empty=sum(1 for h in hyps if not h.strip())) | |
| if boot and len(rows) > 1: | |
| # sentence-level statistics make the bootstrap cheap: resample and re-aggregate | |
| cs = [chrf._extract_corpus_statistics([h], [[r]]) for h, r in zip(hyps, refs)] | |
| bs = [bleu._extract_corpus_statistics([h], [[r]]) for h, r in zip(hyps, refs)] | |
| rng = random.Random(seed) | |
| g = _groups(rows) | |
| vc, vb = [], [] | |
| for _ in range(B): | |
| idx = _draw(g, rng) | |
| vc.append(chrf._compute_score_from_stats([sum(x) for x in zip(*[cs[i][0] for i in idx])]).score) | |
| vb.append(bleu._compute_score_from_stats([sum(x) for x in zip(*[bs[i][0] for i in idx])]).score) | |
| vc.sort() | |
| vb.sort() | |
| out["chrfpp_ci"] = (vc[int(0.025 * B)], vc[int(0.975 * B)]) | |
| out["bleu_ci"] = (vb[int(0.025 * B)], vb[int(0.975 * B)]) | |
| return out | |
| def paired_bootstrap_chrf(rows_a, rows_b, hyp_key="pred", ref_key="tgt", seed=0): | |
| """p-value for H0: system A is not better than B on chrF++ (aligned by id).""" | |
| b_by = {r["id"]: r for r in rows_b} | |
| common = [r for r in rows_a if r["id"] in b_by] | |
| chrf = sacrebleu.CHRF(word_order=2) | |
| sa = [chrf._extract_corpus_statistics([r.get(hyp_key) or ""], [[r[ref_key]]])[0] for r in common] | |
| sb = [chrf._extract_corpus_statistics([b_by[r["id"]].get(hyp_key) or ""], [[r[ref_key]]])[0] for r in common] | |
| n = len(common) | |
| def score(stats, idx): | |
| return chrf._compute_score_from_stats([sum(x) for x in zip(*[stats[i] for i in idx])]).score | |
| delta = score(sa, range(n)) - score(sb, range(n)) | |
| rng = random.Random(seed) | |
| g = _groups(common) | |
| worse = 0 | |
| for _ in range(B): | |
| idx = _draw(g, rng) | |
| if score(sa, idx) - score(sb, idx) <= 0: | |
| worse += 1 | |
| return dict(n=n, delta=delta, p=worse / B, n_clusters=len(g)) | |
| if __name__ == "__main__": | |
| task, path = sys.argv[1], sys.argv[2] | |
| rows = read(path) | |
| print(json.dumps(ocr_scores(rows) if task == "ocr" else mt_scores(rows), indent=1)) | |