| |
| """Fouille d'erreurs sur les hypotheses champion (banc 4235 clips, refs connues). |
| Chaque piste n'est retenue que si son gain NET sur la validation complete est positif : |
| A. normalisation apostrophes/typographie B. paires de substitution frequentes |
| C. majuscules systematiques (noms propres) D. chiffres vs lettres E. hygiene CSV.""" |
| import csv |
| import json |
| import unicodedata |
| from collections import Counter |
|
|
| import jiwer |
|
|
| MAP = json.load(open("/scratch/rehearsal/mapping.json", encoding="utf-8")) |
| HYPS = {r["ID"]: r["Target"] for r in csv.DictReader(open("/root/subs/sub_rehearsal_final.csv", encoding="utf-8"))} |
| LANGS = ["lin", "lug", "sna"] |
|
|
|
|
| def norm(t): |
| return " ".join(str(t).replace("|", " ").split()) |
|
|
|
|
| def pairs_for(lang): |
| rs, hs = [], [] |
| for anon, info in MAP.items(): |
| if info["true_lang"] == lang and info["text"].strip(): |
| rs.append(norm(info["text"])) |
| hs.append(norm(HYPS.get(anon, ""))) |
| return rs, hs |
|
|
|
|
| def sc(rs, hs): |
| return 0.5 * jiwer.wer(rs, hs) + 0.5 * jiwer.cer(rs, hs) |
|
|
|
|
| def apply_map(hs, table): |
| out = [] |
| for h in hs: |
| out.append(" ".join(table.get(w, w) for w in h.split())) |
| return out |
|
|
|
|
| print("=== E. hygiene CSV ===", flush=True) |
| for name in ("sub_public.csv", "sub_phase2.csv", "sub_rehearsal_final.csv"): |
| rows = list(csv.DictReader(open(f"/root/subs/{name}", encoding="utf-8"))) |
| bad = sum(1 for r in rows if "�" in r["Target"] or "Ã" in r["Target"]) |
| empt = sum(1 for r in rows if not r["Target"].strip()) |
| lens = sorted(len(r["Target"].split()) for r in rows) |
| print(f"{name}: {len(rows)} lignes | mojibake {bad} | vides {empt} | mots p50/p95 {lens[len(lens)//2]}/{lens[int(len(lens)*0.95)]}", flush=True) |
|
|
| CHAR_VARIANTS = [("’", "'"), ("‘", "'"), ("`", "'"), ("“", '"'), ("”", '"')] |
| for lang in LANGS: |
| rs, hs = pairs_for(lang) |
| base = sc(rs, hs) |
| print(f"\n===== {lang} | baseline banc {base:.4f} ({len(rs)} clips) =====", flush=True) |
|
|
| print("--- A. typographie ---", flush=True) |
| rjoin, hjoin = " ".join(rs), " ".join(hs) |
| for ch, repl in CHAR_VARIANTS: |
| cr, chh = rjoin.count(ch), hjoin.count(ch) |
| if cr or chh: |
| print(f" car {ch!r}: refs {cr} vs hyps {chh}", flush=True) |
| hs2 = [h.translate(str.maketrans({"’": "'", "‘": "'", "`": "'"})) for h in hs] |
| s2 = sc(rs, hs2) |
| if s2 < base: |
| print(f" normalisation apostrophes: {base:.4f} -> {s2:.4f} <== MIEUX", flush=True) |
| else: |
| print(f" normalisation apostrophes: {s2 - base:+.5f} (neutre/negatif)", flush=True) |
|
|
| print("--- B. paires de substitution ---", flush=True) |
| sub = Counter() |
| for r, h in zip(rs, hs): |
| rw, hw = r.split(), h.split() |
| ops = jiwer.process_words([r], [h]) |
| for al in ops.alignments[0]: |
| if al.type == "substitute": |
| for i, j in zip(range(al.ref_start_idx, al.ref_end_idx), range(al.hyp_start_idx, al.hyp_end_idx)): |
| sub[(hw[j], rw[i])] += 1 |
| cands = [(h, r, n) for (h, r), n in sub.most_common(60) if n >= 8 and h.lower() != r.lower()] |
| print(f" {len(cands)} paires candidates (>=8 occ.)", flush=True) |
| kept = {} |
| cur = list(hs) |
| curs = base |
| for h, r, n in cands: |
| trial = apply_map(cur, {h: r}) |
| st = sc(rs, trial) |
| if st < curs - 1e-6: |
| kept[h] = r |
| cur, curs = trial, st |
| print(f" paires retenues: {len(kept)} | banc {base:.4f} -> {curs:.4f} ({curs - base:+.4f})", flush=True) |
| if kept: |
| print(" ex:", dict(list(kept.items())[:8]), flush=True) |
|
|
| print("--- C. majuscules systematiques ---", flush=True) |
| wc, wcap = Counter(), Counter() |
| for l in open(f"/scratch/prep/manifests/waxal_{lang}_train.jsonl", encoding="utf-8"): |
| for w in norm(json.loads(l)["text"]).split(): |
| core = w.strip(".,;:!?\"'") |
| if len(core) >= 3: |
| wc[core.lower()] += 1 |
| if core[:1].isupper(): |
| wcap[core.lower()] += 1 |
| always_cap = {w for w in wc if wc[w] >= 5 and wcap[w] / wc[w] >= 0.95} |
| table = {} |
| for h in hs: |
| for w in h.split(): |
| core = w.strip(".,;:!?\"'") |
| if core.lower() in always_cap and core[:1].islower(): |
| table[w] = w.replace(core, core[:1].upper() + core[1:], 1) |
| hs3 = apply_map(hs, table) |
| s3 = sc(rs, hs3) |
| print(f" {len(always_cap)} mots toujours-capitalises train | {len(table)} formes corrigees | {base:.4f} -> {s3:.4f} ({s3 - base:+.4f})", flush=True) |
|
|
| print("--- D. chiffres ---", flush=True) |
| rdig = sum(any(c.isdigit() for c in r) for r in rs) |
| hdig = sum(any(c.isdigit() for c in h) for h in hs) |
| print(f" refs avec chiffres: {rdig}/{len(rs)} | hyps avec chiffres: {hdig}", flush=True) |
|
|
| print("\nMINE_DONE", flush=True) |
|
|