File size: 11,779 Bytes
0e11779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# -*- coding: utf-8 -*-
"""Audit linguistique comparatif: sorties sub_LMA06 vs corpus train, par langue."""
import csv, json, re, sys, unicodedata
from collections import Counter

OUT = []
def P(*a):
    s = " ".join(str(x) for x in a)
    OUT.append(s)
    print(s)

SUB = "/root/sub_LMA06.csv"
LANG = "/root/test_lang.json"
MAN = "/scratch/prep/manifests/waxal_%s_%s.jsonl"

# ---------- chargement ----------
sub = {}
with open(SUB, encoding="utf-8") as f:
    r = csv.DictReader(f)
    for row in r:
        sub[row["ID"]] = row["Target"]
lang = json.load(open(LANG, encoding="utf-8"))

hyp = {"lin": [], "sna": []}
for k, v in sub.items():
    L = lang.get(k)
    if L in hyp:
        hyp[L].append(v)

train = {"lin": [], "sna": []}
val = {"lin": [], "sna": []}
for L in ("lin", "sna"):
    for split, store in (("train", train), ("validation", val)):
        try:
            with open(MAN % (L, split), encoding="utf-8") as f:
                for line in f:
                    line = line.strip()
                    if line:
                        store[L].append(json.loads(line)["text"])
        except FileNotFoundError:
            pass

P("=== TAILLES ===")
for L in ("lin", "sna"):
    P("%s: hyp=%d clips, train=%d phrases, val=%d phrases" % (L, len(hyp[L]), len(train[L]), len(val[L])))

# ---------- tokenisation ----------
WORD = re.compile(r"[^\W\d_]+", re.UNICODE)   # lettres seulement, pour le vocabulaire
def words(t):
    return WORD.findall(t.lower())
def words_cased(t):
    return WORD.findall(t)
def toks_ws(t):  # tokens WER (espaces), tels que le scoreur les voit
    return t.split()

# ================= 1. OOV =================
P("")
P("=== 1. TAUX DE MOTS HORS-VOCABULAIRE (hyp vs vocab train, minuscules) ===")
oov_detail = {}
for L in ("lin", "sna"):
    vtrain = Counter()
    for t in train[L]:
        vtrain.update(words(t))
    vval = Counter()
    for t in val[L]:
        vval.update(words(t))
    vall = Counter(vtrain); vall.update(vval)

    hw = []
    for t in hyp[L]:
        hw.extend(words(t))
    hc = Counter(hw)
    n_tok = len(hw)
    oov_tok_tr = sum(c for w, c in hc.items() if w not in vtrain)
    oov_typ_tr = sum(1 for w in hc if w not in vtrain)
    oov_tok_all = sum(c for w, c in hc.items() if w not in vall)
    oov_typ_all = sum(1 for w in hc if w not in vall)
    P("%s | tokens hyp=%d, types hyp=%d | vocab train=%d types (+val -> %d)" % (L, n_tok, len(hc), len(vtrain), len(vall)))
    P("%s | OOV vs TRAIN      : tokens %d (%.2f%%), types %d (%.2f%%)" % (L, oov_tok_tr, 100.0*oov_tok_tr/max(n_tok,1), oov_typ_tr, 100.0*oov_typ_tr/max(len(hc),1)))
    P("%s | OOV vs TRAIN+VAL  : tokens %d (%.2f%%), types %d (%.2f%%)" % (L, oov_tok_all, 100.0*oov_tok_all/max(n_tok,1), oov_typ_all, 100.0*oov_typ_all/max(len(hc),1)))
    oov_words = sorted([(c, w) for w, c in hc.items() if w not in vall], reverse=True)[:40]
    P("%s | top OOV (vs train+val): %s" % (L, ", ".join("%s(%d)" % (w, c) for c, w in oov_words)))
    # OOV du train lui-meme mesure en leave-one-out approx: taux de mots du val absents du train
    vw = []
    for t in val[L]:
        vw.extend(words(t))
    vc = Counter(vw)
    oov_val = sum(c for w, c in vc.items() if w not in vtrain)
    P("%s | REFERENCE: OOV du VAL vs train = %.2f%% tokens (borne naturelle)" % (L, 100.0*oov_val/max(len(vw),1)))
    oov_detail[L] = dict(n_tok=n_tok, oov_tok_all=oov_tok_all, rate=100.0*oov_tok_all/max(n_tok,1),
                         val_rate=100.0*oov_val/max(len(vw),1), vtrain=vtrain, vall=vall, hc=hc)

# ============ 2. LONGUEURS DE MOTS ============
P("")
P("=== 2. DISTRIBUTION DES LONGUEURS DE MOTS (caracteres) ===")
def lenstats(texts):
    ws = []
    for t in texts:
        ws.extend(words(t))
    if not ws:
        return None
    ls = [len(w) for w in ws]
    ls.sort()
    n = len(ls)
    mean = sum(ls)/n
    med = ls[n//2]
    p90 = ls[int(0.9*n)]
    return dict(n=n, mean=mean, med=med, p90=p90, dist=Counter(ls))
for L in ("lin", "sna"):
    a = lenstats(hyp[L]); b = lenstats(train[L])
    P("%s | HYP   : n=%d moy=%.3f med=%d p90=%d" % (L, a["n"], a["mean"], a["med"], a["p90"]))
    P("%s | TRAIN : n=%d moy=%.3f med=%d p90=%d" % (L, b["n"], b["mean"], b["med"], b["p90"]))
    P("%s | ECART moy hyp-train = %+.3f car (%.2f%%)" % (L, a["mean"]-b["mean"], 100.0*(a["mean"]-b["mean"])/b["mean"]))
    for src, d in (("HYP", a), ("TRAIN", b)):
        tot = sum(d["dist"].values())
        row = " ".join("%d:%.1f%%" % (k, 100.0*d["dist"].get(k,0)/tot) for k in range(1, 13))
        P("   %s %s len>=13:%.1f%%" % (src, row, 100.0*sum(v for k,v in d["dist"].items() if k>=13)/tot))
    # mots par clip / par seconde
    wpc_h = a["n"]/len(hyp[L])
    wpc_t = b["n"]/len(train[L])
    P("%s | mots/clip HYP=%.2f  TRAIN=%.2f (train clips potentiellement + longs)" % (L, wpc_h, wpc_t))

# ============ 3. TOP-40 MOTS FREQUENTS ============
P("")
P("=== 3. TOP-40 MOTS DU TRAIN : frequence relative train vs hyp ===")
for L in ("lin", "sna"):
    tw = []
    for t in train[L]:
        tw.extend(words(t))
    tc = Counter(tw); Nt = len(tw)
    hc = oov_detail[L]["hc"]; Nh = oov_detail[L]["n_tok"]
    P("--- %s ---" % L)
    P("   %-16s %8s %8s %8s %9s" % ("mot", "f_train", "f_hyp", "ratio", "delta_tok"))
    rows = []
    for w, c in tc.most_common(40):
        ft = 100.0*c/Nt
        fh = 100.0*hc.get(w, 0)/Nh
        ratio = fh/ft if ft > 0 else float("nan")
        # nombre de tokens hyp en trop/manque si on alignait la frequence
        delta = hc.get(w, 0) - ft/100.0*Nh
        rows.append((w, ft, fh, ratio, delta))
    for w, ft, fh, ratio, delta in rows:
        flag = ""
        if ratio < 0.72: flag = "  <-- SOUS-PRODUIT"
        elif ratio > 1.38: flag = "  <-- SUR-PRODUIT"
        P("   %-16s %7.3f%% %7.3f%%  %6.2f  %+8.1f%s" % (w, ft, fh, ratio, delta, flag))

# ============ 4. LIN vs SNA : agglutination ============
P("")
P("=== 4. LIN vs SNA : morphologie comparee (sur le TRAIN) ===")
for L in ("lin", "sna"):
    tw = []
    for t in train[L]:
        tw.extend(words(t))
    tc = Counter(tw); N = len(tw); V = len(tc)
    hapax = sum(1 for w, c in tc.items() if c == 1)
    dis = sum(1 for w, c in tc.items() if c == 2)
    mean_len = sum(len(w) for w in tw)/N
    # couverture: % de tokens couverts par les 1000 mots les plus frequents
    top = tc.most_common(1000)
    cov1k = 100.0*sum(c for _, c in top)/N
    cov100 = 100.0*sum(c for _, c in tc.most_common(100))/N
    # TTR normalise (sur 200k tokens echantillonnes deterministe)
    sub_tw = tw[:200000]
    ttr = 100.0*len(set(sub_tw))/max(len(sub_tw),1)
    P("%s | tokens=%d types=%d  TTR(200k)=%.2f%%  moy_len=%.3f" % (L, N, V, ttr, mean_len))
    P("%s | hapax=%d (%.2f%% des types, %.3f%% des tokens)  dis-legomena=%d (%.2f%% types)" % (L, hapax, 100.0*hapax/V, 100.0*hapax/N, dis, 100.0*dis/V))
    P("%s | couverture top-100=%.2f%% top-1000=%.2f%% des tokens" % (L, cov100, cov1k))
    # caracteres par phrase / mots par phrase
    nch = sum(len(t) for t in train[L]); nph = len(train[L])
    P("%s | phrase moy: %.1f car, %.2f mots" % (L, nch/nph, N/nph))

# ============ 5. CARACTERES / PONCTUATION / CASSE ============
P("")
P("=== 5. INVENTAIRE CARACTERES, PONCTUATION, CASSE ===")
for L in ("lin", "sna"):
    ch_t = Counter(); ch_h = Counter()
    for t in train[L]: ch_t.update(t)
    for t in hyp[L]:   ch_h.update(t)
    Nt = sum(ch_t.values()); Nh = sum(ch_h.values())
    P("--- %s --- chars train=%d hyp=%d" % (L, Nt, Nh))
    keys = set(ch_t) | set(ch_h)
    punct = sorted([c for c in keys if not c.isalnum() and not c.isspace()])
    P("   ponctuation/symboles (train ‰ | hyp ‰ | ratio):")
    for c in punct:
        a = 1000.0*ch_t.get(c,0)/Nt; b = 1000.0*ch_h.get(c,0)/Nh
        r = (b/a) if a > 0 else float("inf")
        name = unicodedata.name(c, "?")
        P("      %-3s U+%04X %-28s %7.3f | %7.3f | %s" % (repr(c)[1:-1], ord(c), name[:28], a, b, ("%.2f" % r) if a>0 else "INF"))
    # lettres presentes dans train mais jamais/rarement en hyp
    alpha_t = {c: n for c, n in ch_t.items() if c.isalpha()}
    alpha_h = {c: n for c, n in ch_h.items() if c.isalpha()}
    miss = []
    for c, n in sorted(alpha_t.items(), key=lambda x: -x[1]):
        a = 1000.0*n/Nt; b = 1000.0*alpha_h.get(c,0)/Nh
        if a >= 0.02 and (b == 0 or b/a < 0.5 or b/a > 2.0):
            miss.append((c, a, b))
    P("   lettres desequilibrees (>=0.02 pour-mille dans train, ratio hors [0.5,2]):")
    for c, a, b in miss[:30]:
        P("      %-3s U+%04X %7.3f | %7.3f | %s" % (repr(c)[1:-1], ord(c), a, b, ("%.2f" % (b/a)) if a>0 else "-"))
    extra = [(c, 1000.0*n/Nh) for c, n in alpha_h.items() if c not in alpha_t]
    P("   lettres en HYP absentes du TRAIN: %s" % (", ".join("%s(%.3f‰)" % (c, v) for c, v in sorted(extra, key=lambda x:-x[1])) or "aucune"))

    # casse
    def case_stats(texts):
        n = len(texts); up1 = 0; endpt = Counter(); capmid = 0; wtot = 0; allcaps = 0
        for t in texts:
            t2 = t.strip()
            if not t2: continue
            if t2[0].isupper(): up1 += 1
            endpt[t2[-1]] += 1
            ws = t2.split()
            for i, w in enumerate(ws):
                wtot += 1
                core = WORD.findall(w)
                if not core: continue
                c0 = core[0]
                if i > 0 and c0[0].isupper(): capmid += 1
                if len(c0) > 1 and c0.isupper(): allcaps += 1
        return n, up1, endpt, capmid, wtot, allcaps
    for src, texts in (("TRAIN", train[L]), ("HYP", hyp[L])):
        n, up1, endpt, capmid, wtot, allcaps = case_stats(texts)
        P("   %s casse: 1re lettre majuscule %.2f%% | mots capitalises hors-initiale %.2f%% | ALLCAPS %.2f%%" % (src, 100.0*up1/n, 100.0*capmid/max(wtot,1), 100.0*allcaps/max(wtot,1)))
        top_end = ", ".join("%r:%.1f%%" % (c, 100.0*v/n) for c, v in endpt.most_common(6))
        P("   %s dernier caractere: %s" % (src, top_end))
    # ponctuation par mot
    for src, texts in (("TRAIN", train[L]), ("HYP", hyp[L])):
        nw = sum(len(t.split()) for t in texts)
        ncomma = sum(t.count(",") for t in texts)
        nper = sum(t.count(".") for t in texts)
        P("   %s: virgules/100mots=%.2f  points/100mots=%.2f" % (src, 100.0*ncomma/nw, 100.0*nper/nw))

# ============ 6. n'  (Shona velar nasal) et digrammes ============
P("")
P("=== 6. MOTIFS ORTHOGRAPHIQUES CIBLES ===")
for L in ("lin", "sna"):
    def pat(texts, rx):
        c = 0
        for t in texts:
            c += len(re.findall(rx, t))
        return c
    ntw = sum(len(t.split()) for t in train[L])
    nhw = sum(len(t.split()) for t in hyp[L])
    for label, rx in (("n' (nasal velaire)", r"n['’]"), ("apostrophe droite '", r"'"), ("apostrophe typo ’", r"’"), ("trait d'union", r"-"), ("chiffres", r"\d")):
        a = 1000.0*pat(train[L], rx)/ntw
        b = 1000.0*pat(hyp[L], rx)/nhw
        P("%s | %-22s train=%.3f/1000mots  hyp=%.3f/1000mots  ratio=%s" % (L, label, a, b, ("%.2f" % (b/a)) if a > 0 else "n/a"))

# ============ 7. longueur des phrases hyp vs attendu ============
P("")
P("=== 7. LONGUEUR DES SORTIES ===")
for L in ("lin", "sna"):
    hl = sorted(len(t.split()) for t in hyp[L])
    tl = sorted(len(t.split()) for t in train[L])
    P("%s | HYP mots/phrase: moy=%.2f med=%d min=%d max=%d" % (L, sum(hl)/len(hl), hl[len(hl)//2], hl[0], hl[-1]))
    P("%s | TRAIN mots/phrase: moy=%.2f med=%d min=%d max=%d" % (L, sum(tl)/len(tl), tl[len(tl)//2], tl[0], tl[-1]))
    empt = sum(1 for t in hyp[L] if not t.strip())
    P("%s | sorties vides: %d ; sorties <3 mots: %d" % (L, empt, sum(1 for x in hl if x < 3)))

with open("/root/audit_STATSLING_out.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(OUT))
print("\n[OK] ecrit /root/audit_STATSLING_out.txt")