# -*- coding: utf-8 -*- """Audit 4: precision des SEULES regles JOIN qui declenchent ; restauration d'accents ; point final restreint au lingala ; traits d'union / chiffres.""" import csv, json, os, re, unicodedata from collections import Counter OUT = [] def P(*a): s = " ".join(str(x) for x in a); OUT.append(s); print(s) sub = {} with open("/root/sub_LMA06.csv", encoding="utf-8") as f: for row in csv.DictReader(f): sub[row["ID"]] = row["Target"] lang = json.load(open("/root/test_lang.json", encoding="utf-8")) hyp = {"lin": [], "sna": []} for k, v in sub.items(): if lang.get(k) in hyp: hyp[lang[k]].append(v) train, val = {"lin": [], "sna": []}, {"lin": [], "sna": []} for L in ("lin", "sna"): for sp, st in (("train", train), ("validation", val)): with open("/scratch/prep/manifests/waxal_%s_%s.jsonl" % (L, sp), encoding="utf-8") as f: for line in f: if line.strip(): st[L].append(json.loads(line)["text"]) tot_w = sum(len(t.split()) for L in hyp for t in hyp[L]) tot_c = sum(len(t) for L in hyp for t in hyp[L]) W_ERR, C_ERR = 0.5/tot_w, 0.5/tot_c PUNCT = ".,;:!?\"()«»" def toks(t): return [x for x in (w.strip(PUNCT) for w in t.split()) if x] # ===== 1. JOIN : precision des seules regles declenchees ===== P("=== 1. JOIN : precision des SEULES regles qui declenchent sur le test (anti biais de selection) ===") for L in ("lin", "sna"): uni = Counter(); bi = Counter() for t in train[L]: ws=[w.lower() for w in toks(t)]; uni.update(ws); bi.update(zip(ws, ws[1:])) uni_v = Counter(); bi_v = Counter() for t in val[L]: ws=[w.lower() for w in toks(t)]; uni_v.update(ws); bi_v.update(zip(ws, ws[1:])) hyp_bi = Counter() for t in hyp[L]: ws=[w.lower() for w in toks(t)]; hyp_bi.update(zip(ws, ws[1:])) for mr in (0.10, 0.25, 0.50): rules = {} for j, cj in uni.items(): if cj < 10: continue for i in range(2, len(j)-1): a, b = j[:i], j[i:] if uni.get(a,0) < 20 or uni.get(b,0) < 20: continue cb = bi.get((a,b),0) if cb <= mr*cj: rules[(a,b)] = (cj, cb) fired = {p: c for p, c in hyp_bi.items() if p in rules} n = sum(fired.values()) # precision restreinte aux regles declenchees, mesuree en held-out VAL tp = sum(uni_v.get(a+b,0) for (a,b) in fired) fp = sum(bi_v.get((a,b),0) for (a,b) in fired) prec_v = tp/max(tp+fp,1) # precision sur le TRAIN (grand echantillon) pour les memes regles tpt = sum(uni.get(a+b,0) for (a,b) in fired) fpt = sum(bi.get((a,b),0) for (a,b) in fired) prec_t = tpt/max(tpt+fpt,1) g = n*2*(2*prec_v-1) P("%s | maxRatio=%.2f : %d regles declenchent, %d occurrences" % (L, mr, len(fired), n)) P("%s | precision VAL (regles declenchees seulement) = %.2f%% (%d/%d)" % (L, 100*prec_v, tp, tp+fp)) P("%s | precision TRAIN (memes regles) = %.2f%% (%d/%d)" % (L, 100*prec_t, tpt, tpt+fpt)) P("%s | GAIN = %.1f err-mot = %+.5f score" % (L, g, g*W_ERR)) det = sorted(fired.items(), key=lambda x: -x[1]) P("%s | detail: %s" % (L, ", ".join("%s+%s x%d [val %d/%d]" % (a,b,c,uni_v.get(a+b,0),bi_v.get((a,b),0)) for (a,b),c in det[:25]))) # ===== 2. ACCENTS ===== P("") P("=== 2. RESTAURATION D'ACCENTS (mot nu de hyp dont la forme accentuee domine le train) ===") def deacc(s): return "".join(c for c in unicodedata.normalize("NFD", s) if unicodedata.category(c) != "Mn") for L in ("lin", "sna"): uni = Counter() for t in train[L]+val[L]: uni.update(w.lower() for w in toks(t)) groups = {} for w, c in uni.items(): groups.setdefault(deacc(w), Counter())[w] = c opp = Counter(); n = 0; risky = 0 for t in hyp[L]: for w in toks(t): wl = w.lower() if deacc(wl) != wl: continue # deja accentue g = groups.get(wl) if not g or len(g) < 2: continue bare = g.get(wl, 0) acc_best, acc_c = max(((k, v) for k, v in g.items() if k != wl), key=lambda x: x[1]) if acc_c >= 3 and acc_c > 3*bare: opp[(wl, acc_best, bare, acc_c)] += 1; n += 1 elif acc_c >= 3: risky += 1 P("%s | %d occurrences ou la forme ACCENTUEE domine (>3x) la forme nue dans le train" % (L, n)) P("%s | %d occurrences ambigues (ecartees)" % (L, risky)) for (wl, acc, b, c), k in opp.most_common(20): P(" %-18s -> %-18s x%d [train nu=%d, accentue=%d]" % (wl, acc, k, b, c)) # precision = part accentuee dans le train pour ces paires if n: tp = sum(c for (_,_,b,c) in opp); fp = sum(b for (_,_,b,c) in opp) p = tp/max(tp+fp,1) g = n*(2*p-1) P("%s | precision attendue=%.1f%% -> %.1f err-mot + %.1f err-car = %+.5f score" % (L, 100*p, g, g, g*W_ERR+g*C_ERR)) # ===== 3. POINT FINAL restreint au lingala (devhard) ===== P("") P("=== 3. POINT FINAL : devhard restreint par langue ===") allh = json.load(open("/root/devhard_allhyps.json", encoding="utf-8")) refs = {} for fn in os.listdir("/root/devhard"): with open("/root/devhard/"+fn, encoding="utf-8") as f: for line in f: if line.strip(): d = json.loads(line); refs[d["id"]] = d.get("text","") for mdl in ("joint_cont_best", "sna_ps_best"): hyps = allh[mdl] for pref, name in (("lin_", "lingala"), ("sna_", "shona")): pairs = [(h.strip().endswith("."), refs[i].strip().endswith(".")) for i, h in hyps.items() if i.startswith(pref) and i in refs and refs[i].strip()] if len(pairs) < 30: continue n = len(pairs) ph = sum(1 for a,b in pairs if a)/n; pr = sum(1 for a,b in pairs if b)/n acc = sum(1 for a,b in pairs if a==b)/n P("%-16s %-8s n=%4d | hyp '.'=%.3f ref '.'=%.3f | exact=%.3f TOUJOURS='.'->%.3f gain=%+.3f" % (mdl, name, n, ph, pr, acc, pr, pr-acc)) # extrapolation sur 446 clips d = (pr-acc)*446 P("%-16s %-8s -> %+.1f clips sur 446 = %+.5f score" % (mdl, name, d, d*(W_ERR+C_ERR))) # ===== 4. TRAITS D'UNION / CHIFFRES / APOSTROPHES ===== P("") P("=== 4. TRAITS D'UNION, CHIFFRES, APOSTROPHES : opportunites concretes ===") for L in ("lin", "sna"): uni = Counter() for t in train[L]+val[L]: uni.update(w.lower() for w in toks(t)) hyp_bi = Counter(); hyp_uni = Counter() for t in hyp[L]: ws = [w.lower() for w in toks(t)]; hyp_uni.update(ws); hyp_bi.update(zip(ws, ws[1:])) # bigram (a,b) dont "a-b" est frequent dans le train n = 0; det = Counter() for (a, b), c in hyp_bi.items(): h = a + "-" + b if uni.get(h, 0) >= 3: det[(a, b, uni[h])] += c; n += c P("%s | traits d'union: %d occurrences ou 'a-b' existe dans le train (>=3)" % (L, n)) for (a,b,c), k in det.most_common(10): P(" %s-%s x%d [train %d]" % (a,b,k,c)) # apostrophes internes n2 = 0; det2 = Counter() for w, c in hyp_uni.items(): for i in range(1, len(w)): cand = w[:i] + "'" + w[i:] if uni.get(cand, 0) >= 3 and uni.get(w, 0) < uni[cand]: det2[(w, cand, uni.get(w,0), uni[cand])] += c; n2 += c; break P("%s | apostrophes internes: %d occurrences ou la forme avec ' domine" % (L, n2)) for (w,cand,a,b), k in det2.most_common(10): P(" %s -> %s x%d [nu=%d avec'=%d]" % (w,cand,k,a,b)) P("%s | GAIN combine max = %.1f err-mot = %+.5f score" % (L, (n+n2)*1.0, (n+n2)*(W_ERR+C_ERR))) with open("/root/audit_STATSLING4_out.txt","w",encoding="utf-8") as f: f.write("\n".join(OUT)) print("\n[OK] /root/audit_STATSLING4_out.txt")