File size: 5,652 Bytes
6eed659 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | #!/usr/bin/env python3
"""(A) VÉRIFIE que le cache d'hypothèses shona n'est pas tronqué (asymétrie omissions suspecte).
(B) Construit une table de règles de SEGMENTATION lingala à partir du corpus TRAIN (la spec) :
la segmentation est 22.5% du budget d'erreur lin. Pour chaque forme collée que nous
produisons, on compare dans le TRAIN la fréquence de la forme COLLÉE vs SÉPARÉE.
On ne garde que les cas tranchés (ratio >= RATIO et effectif suffisant) => règle sûre.
(C) Applique et mesure sur devhard-lin. Aucune soumission.
"""
import json, os, re
from collections import Counter
import difflib, jiwer
def comb(refs, hyps):
pr = [(r, h) for r, h in zip(refs, hyps) if r.strip()]
a = [x for x, _ in pr]; b = [y for _, y in pr]
w = jiwer.wer(a, b); c = jiwer.cer(a, b)
return w, c, 0.5 * w + 0.5 * c
def norm(w):
return re.sub(r"[^\w'ɛɔ]", "", w.lower())
rows = [json.loads(l) for l in open("/root/devhard/devhard_linsna.jsonl", encoding="utf-8")]
D = json.load(open("/root/devhard_allhyps.json", encoding="utf-8"))
# ---------- (A) sanity du cache shona ----------
print("=== (A) VERIFICATION DU CACHE D'HYPOTHESES ===")
for lg, mdl in (("lin", "joint_cont_best"), ("sna", "sna_ps_best")):
sub = [r for r in rows if r["lang"] == lg]
refs = [r["text"] for r in sub]
hyps = [D[mdl].get(r["id"], "") for r in sub]
nr = sum(len(t.split()) for t in refs); nh = sum(len(t.split()) for t in hyps)
m = comb(refs, hyps)
print(" %s : mots refs %d | mots hyps %d (ratio %.2f) | combine %.4f"
% (lg, nr, nh, nh / max(nr, 1), m[2]))
if nh < 0.8 * nr:
print(" ⚠️ HYPOTHESES TRONQUEES — ce cache n'est pas exploitable pour %s" % lg)
# ---------- (B) table de règles de segmentation lin ----------
print("\n=== (B) REGLES DE SEGMENTATION (corpus train = la spec) ===")
LG = "lin"
sub = [r for r in rows if r["lang"] == LG]
refs = [r["text"] for r in sub]
hyps = [D["joint_cont_best"].get(r["id"], "") for r in sub]
base = comb(refs, hyps)
print("baseline lin : combine %.4f" % base[2])
# corpus train : fréquences des formes collées et des bigrammes
uni = Counter(); big = Counter()
for l in open("/root/devhard/train_%s_min.jsonl" % LG, encoding="utf-8"):
w = [norm(x) for x in json.loads(l).get("text", "").split()]
w = [x for x in w if x]
uni.update(w)
big.update(zip(w, w[1:]))
# candidats : formes collées que NOUS produisons et qui se découpent en 2 mots connus
hv = Counter()
for h in hyps:
hv.update(norm(x) for x in h.split())
hv.pop("", None)
RATIO = float(os.environ.get("RATIO", "5"))
MINC = int(os.environ.get("MINC", "10"))
split_rules = {}
for w, n in hv.items():
if len(w) < 5 or uni.get(w, 0) > 0 and uni[w] >= MINC:
pass
best = None
for k in range(2, len(w) - 1):
a, b = w[:k], w[k:]
pa = big.get((a, b), 0)
if pa >= MINC and uni.get(a, 0) >= MINC and uni.get(b, 0) >= MINC:
if pa > (best[2] if best else 0):
best = (a, b, pa)
if best:
joined = uni.get(w, 0)
if best[2] >= RATIO * max(joined, 1):
split_rules[w] = (best[0] + " " + best[1], joined, best[2])
print("regles de SEPARATION retenues (ratio>=%g, effectif>=%d) : %d" % (RATIO, MINC, len(split_rules)))
for w, (s, j, p) in sorted(split_rules.items(), key=lambda x: -x[1][2])[:15]:
print(" %-16s -> %-20s (train : colle %d | separe %d)" % (w, s, j, p))
# règles de FUSION : bigrammes que nous produisons mais que le train colle
join_rules = {}
for h in hyps:
w = [norm(x) for x in h.split()]
for a, b in zip(w, w[1:]):
if not a or not b:
continue
j = a + b
if uni.get(j, 0) >= MINC and uni[j] >= RATIO * max(big.get((a, b), 0), 1):
join_rules[(a, b)] = (j, big.get((a, b), 0), uni[j])
print("\nregles de FUSION retenues : %d" % len(join_rules))
for (a, b), (j, s, c) in sorted(join_rules.items(), key=lambda x: -x[1][2])[:10]:
print(" %-20s -> %-16s (train : separe %d | colle %d)" % (a + " " + b, j, s, c))
def apply_rules(t, do_split=True, do_join=True):
out = []
for tok in t.split():
k = norm(tok)
if do_split and k in split_rules:
rep = split_rules[k][0]
# conserver la casse initiale et la ponctuation finale
tail = tok[len(tok.rstrip(".,!?")):]
if tok[:1].isupper():
rep = rep[:1].upper() + rep[1:]
out.append(rep + tail)
else:
out.append(tok)
if do_join:
res = []
i = 0
toks = out
while i < len(toks):
if i + 1 < len(toks):
a, b = norm(toks[i]), norm(toks[i + 1])
if (a, b) in join_rules:
tail = toks[i + 1][len(toks[i + 1].rstrip(".,!?")):]
m = join_rules[(a, b)][0]
if toks[i][:1].isupper():
m = m[:1].upper() + m[1:]
res.append(m + tail); i += 2; continue
res.append(toks[i]); i += 1
out = res
return " ".join(out)
print("\n=== (C) EFFET SUR devhard-lin ===")
for lbl, ds, dj in (("separation seule", True, False), ("fusion seule", False, True),
("les deux", True, True)):
o = [apply_rules(h, ds, dj) for h in hyps]
m = comb(refs, o)
nch = sum(1 for a, b in zip(hyps, o) if a != b)
print(" %-18s WER %.4f CER %.4f combine %.4f (%+.4f) %d clips modifies%s"
% (lbl, m[0], m[1], m[2], m[2] - base[2], nch,
" <-- GAIN" if m[2] < base[2] else ""))
print("\nSEG_RULES_DONE")
|