waxal2026-backup / code /build_nbest.py
Pricile's picture
compactage apres suppression luganda
6eed659
Raw
History Blame Contribute Delete
6.09 kB
#!/usr/bin/env python3
"""Etape 1/2 : produit les listes N-BEST des deux langues, avec leurs scores
actuels, pour pouvoir les re-classer ensuite par un LM MASQUE BIDIRECTIONNEL.
Pourquoi cette piste : arXiv 2606.23306 teste 11 methodes INTERNES au CTC pour
recuperer l'ecart a l'oracle (MBR sur posterieurs, MC-dropout, decodage
contrastif, rescoreurs entraines) -- aucune n'est significative. La seule qui
marche est un posterieur issu d'un MLM bidirectionnel EXTERNE. Cela explique nos
propres refutations (moyennage de logits, ensembles medoide, LM neuronal causal).
Et notre plus gros ecart a l'oracle est cote shona (oracle 10-best 0.0949 vs
0.1281 retenu) : le goulot y est la SELECTION, exactement ce qu'un rescoreur corrige.
lin : beam KenLM alpha=0.6 beta=1.0 lsb=True beam=64 (config du RECORD)
sna : beam CTC pur 256 + rescorage sna_r2 w=4.0 (config du RECORD)
Les scores sauves sont EXACTEMENT ceux qui produisent le record, pour que
lambda=0 reproduise le record bit pour bit (controle de non-regression).
"""
import csv, json, os, pickle, sys
import numpy as np, soundfile as sf, torch
from multiprocessing import Pool
from pyctcdecode import build_ctcdecoder
from transformers import AutoModelForCTC, AutoProcessor
sys.path.insert(0, "/root")
from gen_sna_rescore import ctc_score, encode_for, norm
BASE = "/root/sub_LMA06.csv"
NBEST = 24
OUT = "/scratch/nbest_all.pkl"
base = {r["ID"]: r["Target"] for r in csv.DictReader(open(BASE, encoding="utf-8"))}
lang = json.load(open("/root/test_lang.json"))
lin_ids = [k for k in base if lang.get(k) == "lin"]
sna_ids = [k for k in base if lang.get(k) == "sna"]
print("lin %d | sna %d" % (len(lin_ids), len(sna_ids)), flush=True)
store = {}
# ---------------------------------------------------------------- LINGALA
with open("/scratch/lm/logits_test_lin.pkl", "rb") as f:
keys, logs, greedy = pickle.load(f)
tokL = AutoProcessor.from_pretrained("/root/models/joint_cont_best").tokenizer
v = tokL.get_vocab()
labL = [None] * len(v)
for t, i in v.items():
labL[i] = t
labL[tokL.word_delimiter_token_id] = " "
labL[tokL.unk_token_id] = "⁇"
labL[tokL.pad_token_id] = ""
dec = build_ctcdecoder(labL, kenlm_model_path="/scratch/lm/lin_5g.arpa",
alpha=0.6, beta=1.0, lm_score_boundary=True)
with Pool(8) as p:
beams = dec.decode_beams_batch(p, logs, beam_width=64, prune_history=True) # comme decode(), sinon le top ne reproduit pas le record
gmap = dict(zip(keys, greedy))
lin = {}
for k, bs in zip(keys, beams):
cands, scores = [], []
for b in bs[:NBEST]:
h = norm(b[0])
g = gmap[k]
if h and g:
h = g[:1] + h[1:] # casse du 1er caractere, comme dans le record
if h and h not in cands:
cands.append(h); scores.append(float(b[3])) # batch : b[3] = score combine (acoustique + alpha.LM + beta.mots)
lin[k] = (cands, scores)
n_ok = sum(1 for k in lin if lin[k][0] and lin[k][0][int(np.argmax(lin[k][1]))] == base[k])
print("lin n-best OK | argmax reproduit le record sur %d/%d clips" % (n_ok, len(lin)), flush=True)
store["lin"] = lin
del logs, beams, dec
# ---------------------------------------------------------------- SHONA
files = [os.path.join("/scratch/p2_16k", k + ".wav") for k in sna_ids]
proc = AutoProcessor.from_pretrained("/root/models/sna_ps_best")
m = AutoModelForCTC.from_pretrained("/root/models/sna_ps_best", dtype=torch.float32).cuda().eval()
L1 = []
with torch.inference_mode():
for i in range(0, len(files), 4):
au = [sf.read(f, dtype="float32")[0] for f in files[i:i + 4]]
x = proc(au, sampling_rate=16000, return_tensors="pt", padding=True)
x = {kk: vv.cuda() for kk, vv in x.items()}
lg = m(**x).logits.log_softmax(-1).float().cpu().numpy()
for j in range(len(au)):
L1.append(lg[j])
del m; torch.cuda.empty_cache()
tokS = proc.tokenizer
v = tokS.get_vocab()
labS = [None] * len(v)
for t, i in v.items():
labS[i] = t
labS[tokS.word_delimiter_token_id] = " "
labS[tokS.unk_token_id] = "⁇"
labS[tokS.pad_token_id] = ""
greedyS = [norm(tokS.decode(l.argmax(-1))) for l in L1]
decS = build_ctcdecoder(labS) # CTC pur, aucun LM (config du record)
with Pool(8) as p:
allb = decS.decode_beams_batch(p, L1, beam_width=256)
with Pool(8) as p:
dbS = [norm(x) for x in decS.decode_batch(p, L1, beam_width=256)]
print("beam sna OK", flush=True)
procR = AutoProcessor.from_pretrained("/scratch/restore/sna_r2_best")
mR = AutoModelForCTC.from_pretrained("/scratch/restore/sna_r2_best", dtype=torch.float32).cuda().eval()
LG = []
with torch.inference_mode():
for i in range(0, len(files), 4):
au = [sf.read(f, dtype="float32")[0] for f in files[i:i + 4]]
x = procR(au, sampling_rate=16000, return_tensors="pt", padding=True)
x = {kk: vv.cuda() for kk, vv in x.items()}
lg = mR(**x).logits.log_softmax(-1).float().cpu().numpy()
for j in range(len(au)):
LG.append(lg[j])
del mR; torch.cuda.empty_cache()
t2 = procR.tokenizer
print("rescoreur sna OK", flush=True)
sna = {}
for i, k in enumerate(sna_ids):
bs = allb[i]
c = [norm(b[0]) for b in bs[:50]]
a = [(b[2] if len(b) > 3 else 0.0) for b in bs[:50]] # sna sans LM : b[2] = acoustique
for extra in (dbS[i], greedyS[i]):
if extra and extra not in c:
c.append(extra)
a.append(ctc_score(L1[i], encode_for(tokS, extra), tokS.pad_token_id))
resc = np.array([ctc_score(LG[i], encode_for(t2, x), t2.pad_token_id) for x in c])
tot = np.array(a) + 4.0 * resc
order = np.argsort(-tot)[:NBEST]
sna[k] = ([c[j] for j in order], [float(tot[j]) for j in order])
n_ok = sum(1 for k in sna if sna[k][0] and sna[k][0][0] == base[k])
print("sna n-best OK | argmax reproduit le record sur %d/%d clips" % (n_ok, len(sna)), flush=True)
store["sna"] = sna
with open(OUT, "wb") as f:
pickle.dump(store, f, protocol=4)
tot_h = sum(len(v[0]) for L in store.values() for v in L.values())
print("NBEST_DONE %s | %d hypotheses au total" % (OUT, tot_h), flush=True)