| |
| """ANALYSE VOCABULAIRE & SEGMENTATION sur les DEUX langues (le §5 n'avait analysé que le lin). |
| Cherche des défauts SYSTÉMATIQUES corrigeables : |
| (1) OOV : mots que nous produisons et qui n'existent pas dans le vocabulaire du train |
| (2) SEGMENTATION : mots collés/séparés à tort (crucial en shona, agglutinant) |
| (3) substitutions les plus coûteuses, et si elles ont un motif (préfixe, accord, emprunt) |
| (4) mots du train jamais produits par nous (trous de vocabulaire) |
| Sur devhard (on a les références) => diagnostic exact, aucune soumission. |
| """ |
| import json, os, re |
| from collections import Counter, defaultdict |
| import difflib |
|
|
| AUD = "/root/devhard_audio" |
|
|
|
|
| def words(t): |
| return t.split() |
|
|
|
|
| def norm(w): |
| return re.sub(r"[^\w'ɛɔ]", "", w.lower()) |
|
|
|
|
| def main(): |
| 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")) |
| MODEL = {"lin": "joint_cont_best", "sna": "sna_ps_best"} |
|
|
| for lg in ("lin", "sna"): |
| sub = [r for r in rows if r["lang"] == lg] |
| refs = [r["text"] for r in sub] |
| H = D[MODEL[lg]] |
| hyps = [H.get(r["id"], "") for r in sub] |
| print("\n" + "=" * 78) |
| print("### %s — %d clips (modele %s)" % (lg.upper(), len(sub), MODEL[lg])) |
| print("=" * 78) |
|
|
| |
| tv = Counter() |
| for l in open("/root/devhard/train_%s_min.jsonl" % lg, encoding="utf-8"): |
| tv.update(norm(w) for w in json.loads(l).get("text", "").split()) |
| tv.pop("", None) |
| print("vocabulaire train : %d types, %d tokens" % (len(tv), sum(tv.values()))) |
|
|
| |
| hv = Counter() |
| for h in hyps: |
| hv.update(norm(w) for w in words(h)) |
| hv.pop("", None) |
| oov = {w: n for w, n in hv.items() if w not in tv} |
| ntok = sum(hv.values()) |
| noov = sum(oov.values()) |
| print("\n(1) OOV : %d tokens sur %d (%.1f%%) ; %d types inconnus" |
| % (noov, ntok, 100.0 * noov / max(ntok, 1), len(oov))) |
| |
| rv = Counter() |
| for t in refs: |
| rv.update(norm(w) for w in t.split()) |
| rv.pop("", None) |
| roov = sum(n for w, n in rv.items() if w not in tv) |
| print(" (references : %.1f%% d'OOV — c'est la borne naturelle)" |
| % (100.0 * roov / max(sum(rv.values()), 1))) |
| print(" top OOV produits :", [w for w, _ in Counter(oov).most_common(12)]) |
|
|
| |
| split_err = Counter() |
| join_err = Counter() |
| sub_err = Counter() |
| del_err = Counter() |
| ins_err = Counter() |
| for r, h in zip(refs, hyps): |
| a = [norm(x) for x in r.split()] |
| b = [norm(x) for x in h.split()] |
| sm = difflib.SequenceMatcher(None, a, b, autojunk=False) |
| for op, i1, i2, j1, j2 in sm.get_opcodes(): |
| if op == "replace": |
| ra, rb = a[i1:i2], b[j1:j2] |
| if len(ra) == 1 and len(rb) == 2 and ra[0] == rb[0] + rb[1]: |
| split_err[ra[0]] += 1 |
| elif len(ra) == 2 and len(rb) == 1 and rb[0] == ra[0] + ra[1]: |
| join_err[" ".join(ra)] += 1 |
| elif len(ra) == 1 and len(rb) == 1: |
| sub_err[(ra[0], rb[0])] += 1 |
| elif op == "delete": |
| for w in a[i1:i2]: |
| del_err[w] += 1 |
| elif op == "insert": |
| for w in b[j1:j2]: |
| ins_err[w] += 1 |
| tot_err = sum(sub_err.values()) + sum(del_err.values()) + sum(ins_err.values()) \ |
| + sum(split_err.values()) + sum(join_err.values()) |
| print("\n(2) SEGMENTATION :") |
| print(" ref COLLEE / nous separons : %d cas %s" |
| % (sum(split_err.values()), split_err.most_common(6))) |
| print(" ref SEPAREE / nous collons : %d cas %s" |
| % (sum(join_err.values()), join_err.most_common(6))) |
| print(" => %.1f%% du budget d'erreur total (%d)" |
| % (100.0 * (sum(split_err.values()) + sum(join_err.values())) / max(tot_err, 1), tot_err)) |
|
|
| |
| print("\n(3) TOP substitutions (ref -> nous) :") |
| for (x, y), n in sub_err.most_common(12): |
| |
| close = "~" if difflib.SequenceMatcher(None, x, y).ratio() > 0.75 else " " |
| print(" %s %-18s -> %-18s x%d" % (close, x, y, n)) |
| near = sum(n for (x, y), n in sub_err.items() |
| if difflib.SequenceMatcher(None, x, y).ratio() > 0.75) |
| print(" dont %.0f%% sont des QUASI-MOTS (ratio>0.75) = erreurs orthographiques," |
| % (100.0 * near / max(sum(sub_err.values()), 1))) |
| print(" le reste = vrais mots differents (erreurs acoustiques).") |
|
|
| print("\n(4) omissions / insertions les plus frequentes :") |
| print(" omis :", del_err.most_common(8)) |
| print(" inseres:", ins_err.most_common(8)) |
|
|
| print("\nVOCAB_ANALYSIS_DONE") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|