#!/usr/bin/env python3 """Soumission avec RESCORING N-BEST sur le lingala : lin : beams KenLM de joint_cont (+ hypothese decode_batch) reordonnes par score = ac_cont + lm_kenlm + LAMBDA * ac_cont2 (LAMBDA par env, defaut 1.5) sna : sna_ps greedy Casse du 1er caractere copiee du greedy. Routage par LANGF. """ import csv import glob import json import os import numpy as np import soundfile as sf import torch from multiprocessing import Pool from pyctcdecode import build_ctcdecoder from transformers import AutoModelForCTC, AutoProcessor SR = 16000 CACHE = "/scratch/p2_16k" M1 = "/root/models/joint_cont_best" M2 = "/root/models/joint_cont2_best" ARPA = os.environ.get("ARPA", "/scratch/lm/lin_5g.arpa") LAMBDA = float(os.environ.get("LAMBDA", "1.5")) GAMMA = float(os.environ.get("GAMMA", "2.0")) # bonus par mot (compense le biais des sommes de log-probs) NBEST = 10 LANGF = os.environ.get("LANGF", "/root/test_lang_gpulid.json") OUT = os.environ.get("OUT", "/root/sub_rescore.csv") def norm(s): return " ".join(str(s).replace("|", " ").split()) def batches(sel, budget): d = {f: sf.info(f).duration for f in sel} sel = sorted(sel, key=lambda f: -d[f]) bs, cur, acc = [], [], 0.0 for f in sel: if cur and acc + d[f] > budget: bs.append(cur) cur, acc = [], 0.0 cur.append(f) acc += d[f] if cur: bs.append(cur) return bs def logits_for(model_dir, files, dtype=torch.float32): proc = AutoProcessor.from_pretrained(model_dir) m = AutoModelForCTC.from_pretrained(model_dir, dtype=dtype).cuda().eval() res = {} with torch.inference_mode(): for b in batches(files, 90): au = [sf.read(f, dtype="float32")[0] for f in b] x = proc(au, sampling_rate=SR, return_tensors="pt", padding=True) x = {k: v.cuda() for k, v in x.items()} lg = m(**x).logits.log_softmax(-1).float().cpu().numpy() for j, f in enumerate(b): res[f] = lg[j] del m torch.cuda.empty_cache() return proc, res def ctc_scores(logp, texts, tok): T = logp.shape[0] lp = torch.from_numpy(logp).unsqueeze(1) out = [] for t in texts: ids = [i for i in tok(t.replace(" ", "|")).input_ids if i != tok.pad_token_id] if t else [] if not ids or len(ids) > T: out.append(-1e9) continue loss = torch.nn.functional.ctc_loss( lp, torch.tensor(ids).unsqueeze(0), torch.tensor([T]), torch.tensor([len(ids)]), blank=tok.pad_token_id, reduction="sum", zero_infinity=True) out.append(-float(loss)) return out def main(): lang = json.load(open(LANGF)) files = sorted(glob.glob(os.path.join(CACHE, "*.wav"))) ids = [os.path.splitext(os.path.basename(f))[0] for f in files] lin = [f for f in files if lang[os.path.splitext(os.path.basename(f))[0]] == "lin"] sna = [f for f in files if lang[os.path.splitext(os.path.basename(f))[0]] == "sna"] print("lin=%d (rescoring lambda=%.1f gamma=%.1f) | sna=%d (sna_ps greedy)" % (len(lin), LAMBDA, GAMMA, len(sna)), flush=True) out = {} proc1, LG1 = logits_for(M1, lin) print("logits joint_cont OK", flush=True) _, LG2 = logits_for(M2, lin) print("logits joint_cont2 OK", flush=True) tok = proc1.tokenizer v = tok.get_vocab() lab = [None] * len(v) for t, i in v.items(): lab[i] = t lab[tok.word_delimiter_token_id] = " " lab[tok.unk_token_id] = "⁇" lab[tok.pad_token_id] = "" dec = build_ctcdecoder(lab, kenlm_model_path=ARPA, alpha=0.5, beta=0.5, lm_score_boundary=False) order = lin L1 = [LG1[f] for f in order] with Pool(8) as p: allbeams = dec.decode_beams_batch(p, L1, beam_width=64) with Pool(8) as p: db = [" ".join(x.split()) for x in dec.decode_batch(p, L1, beam_width=64)] for i, f in enumerate(order): g = norm(tok.decode(L1[i].argmax(-1))) bs = allbeams[i] cands = [" ".join(b[0].split()) for b in bs[:NBEST]] ac1 = [(b[3] if len(b) > 3 else 0.0) for b in bs[:NBEST]] lm = [((b[4] - b[3]) if len(b) > 4 else 0.0) for b in bs[:NBEST]] if db[i] not in cands: cands.append(db[i]) ac1.append(ctc_scores(L1[i], [db[i]], tok)[0]) lm.append(float(np.mean(lm)) if lm else 0.0) ac2 = ctc_scores(LG2[f], cands, tok) nw = np.array([float(len(x.split())) for x in cands]) tot = np.array(ac1) + np.array(lm) + LAMBDA * np.array(ac2) + GAMMA * nw h = norm(cands[int(np.argmax(tot))]) if h and g: h = g[:1] + h[1:] out[os.path.splitext(os.path.basename(f))[0]] = h if (i + 1) % 150 == 0: print(" rescore %d/%d" % (i + 1, len(order)), flush=True) print("lin OK", flush=True) proc2 = AutoProcessor.from_pretrained("/root/models/sna_ps_best") m2 = AutoModelForCTC.from_pretrained("/root/models/sna_ps_best", dtype=torch.bfloat16).cuda().eval() with torch.inference_mode(): for b in batches(sna, 140): au = [sf.read(f, dtype="float32")[0] for f in b] x = proc2(au, sampling_rate=SR, return_tensors="pt", padding=True) x = {k: v.to("cuda", dtype=torch.bfloat16 if v.dtype == torch.float32 else v.dtype) for k, v in x.items()} pid = m2(**x).logits.float().argmax(-1).cpu().numpy() for f, s in zip(b, proc2.batch_decode(pid)): out[os.path.splitext(os.path.basename(f))[0]] = norm(s) del m2 torch.cuda.empty_cache() print("sna OK", flush=True) fb = {} ref = "/root/sub_p2_KENLM.csv" if os.path.exists(ref): fb = {r["ID"]: r["Target"] for r in csv.DictReader(open(ref, encoding="utf-8"))} filled = 0 for i in ids: if not out.get(i, "").strip() and fb.get(i, "").strip(): out[i] = fb[i] filled += 1 with open(OUT, "w", newline="", encoding="utf-8") as fo: w = csv.writer(fo) w.writerow(["ID", "Target"]) for i in ids: w.writerow([i, out.get(i) or "a"]) print("RESCORE_GEN_DONE %s | %d IDs | vides=%d | combles=%d" % (OUT, len(ids), sum(1 for i in ids if not out.get(i, "").strip()), filled), flush=True) if __name__ == "__main__": main()