#!/usr/bin/env python3 """PISTE 3 - Ensemble de modeles DECORRELES : moyenne des log-probs de modeles entraines differemment (standard / robuste / pseudo), pas de la meme lignee. Evalue chaque combinaison sur la VALIDATION COMPLETE.""" import itertools import json import os import jiwer import numpy as np import soundfile as sf import torch from transformers import Wav2Vec2BertForCTC, Wav2Vec2BertProcessor SR = 16000 MANIF = "/scratch/prep/manifests" POOL = { "lin": ["lin_s4_best", "lin_r_best", "lin_r2_best"], "lug": ["lug_r_best", "lug_ps_best", "lug_r2_best", "lug_s2_best"], "sna": ["sna_ps_best", "sna_r_best", "sna_r2_best"], } def read(p): rows = [] for l in open(p, encoding="utf-8"): r = json.loads(l) r["text"] = " ".join(r.get("text", "").replace("|", " ").split()) rows.append(r) return [r for r in rows if r["text"]] @torch.inference_mode() def logits_of(path, rows): proc = Wav2Vec2BertProcessor.from_pretrained(path) m = Wav2Vec2BertForCTC.from_pretrained(path, torch_dtype=torch.bfloat16).cuda().eval() rows_s = sorted(rows, key=lambda r: -r["duration"]) bs, cur, bud = [], [], 0.0 for r in rows_s: if cur and bud + r["duration"] > 120: bs.append(cur); cur, bud = [], 0.0 cur.append(r); bud += r["duration"] if cur: bs.append(cur) out = {} for b in bs: au = [sf.read(r["audio"], dtype="float32")[0] for r in b] f = proc.feature_extractor(au, sampling_rate=SR, return_tensors="pt", padding=True) f = {k: v.to("cuda", dtype=torch.bfloat16 if v.dtype == torch.float32 else v.dtype) for k, v in f.items()} lg = torch.log_softmax(m(**f).logits.float(), -1).cpu().numpy() att = f.get("attention_mask") for j, r in enumerate(b): T = lg.shape[1] if att is not None: T = max(1, int(round(lg.shape[1] * att[j].sum().item() / att.shape[1]))) out[r["id"]] = lg[j, :T].astype(np.float32) del m torch.cuda.empty_cache() return out, proc def main(): print("=== PISTE 3 : ensembles decorreles (validation complete) ===", flush=True) for lang, names in POOL.items(): names = [n for n in names if os.path.isdir(f"/root/models/{n}")] if len(names) < 2: print(f"{lang}: <2 modeles dispo, saute"); continue val = read(f"{MANIF}/waxal_{lang}_validation.jsonl") refs = [r["text"] for r in val] order = [r["id"] for r in val] cache, proc = {}, None for n in names: cache[n], proc = logits_of(f"/root/models/{n}", val) hy = [" ".join(proc.tokenizer.decode(np.argmax(cache[n][i], -1)).replace("|", " ").split()) for i in order] s = 0.5 * jiwer.wer(refs, hy) + 0.5 * jiwer.cer(refs, hy) print(f" {lang}/{n} seul: {s:.4f}", flush=True) best = None for k in (2, 3): for combo in itertools.combinations(names, k): hy = [] for i in order: L = min(cache[n][i].shape[0] for n in combo) avg = np.mean([cache[n][i][:L] for n in combo], axis=0) hy.append(" ".join(proc.tokenizer.decode(np.argmax(avg, -1)).replace("|", " ").split())) s = 0.5 * jiwer.wer(refs, hy) + 0.5 * jiwer.cer(refs, hy) print(f" {lang}/ENS[{'+'.join(c.replace('_best','') for c in combo)}]: {s:.4f}", flush=True) if best is None or s < best[1]: best = (combo, s) print(f"{lang} -> MEILLEUR ENSEMBLE: {best[0]} = {best[1]:.4f}", flush=True) cache.clear() torch.cuda.empty_cache() print("ENSEMBLE_DONE", flush=True) if __name__ == "__main__": main()