#!/usr/bin/env python3 """Gate du pari Whisper : décode devhard-lin (439 clips, locuteurs held-out) avec le modèle fine-tuné et compare aux références de la campagne. Cibles à battre : 0.3534 (joint_cont greedy) et surtout **0.3457** = la config DU RECORD (joint_cont + KenLM beam a=0.5 b=1.0 lsb=True + casse copiée du greedy). Mesure aussi le taux de virgules produit (le déficit mesuré coûte +0.0144). """ import json, os import jiwer, soundfile as sf, torch from transformers import WhisperForConditionalGeneration, WhisperProcessor MODEL = os.environ.get("MODEL", "/scratch/runs/whisper_lin/final") LANG = os.environ.get("LANG_ASR", "lin") WLANG = {"lin": "ln", "sna": "sn"}[LANG] BEAMS = int(os.environ.get("BEAMS", "5")) NOREP = int(os.environ.get("NOREP", "0")) REPPEN = float(os.environ.get("REPPEN", "1.0")) AUD = "/root/devhard_audio" SR = 16000 def comb(refs, hyps): pr = [(r, h) for r, h in zip(refs, hyps) if r.strip()] a = [x for x, _ in pr]; b = [y for _, y in pr] w = jiwer.wer(a, b); c = jiwer.cer(a, b) return w, c, 0.5 * w + 0.5 * c def main(): rows = [json.loads(l) for l in open("/root/devhard/devhard_linsna.jsonl", encoding="utf-8")] sub = [r for r in rows if r["lang"] == LANG] for r in sub: r["audio"] = os.path.join(AUD, os.path.basename(r["audio"])) sub = [r for r in sub if os.path.exists(r["audio"])] refs = [r["text"] for r in sub] print("devhard-%s : %d clips | modele %s | beams %d" % (LANG, len(sub), MODEL, BEAMS), flush=True) proc = WhisperProcessor.from_pretrained(MODEL, language=WLANG, task="transcribe") m = WhisperForConditionalGeneration.from_pretrained(MODEL, dtype=torch.bfloat16).cuda().eval() m.generation_config.language = WLANG m.generation_config.task = "transcribe" m.generation_config.forced_decoder_ids = None hyps = [] with torch.inference_mode(): for i in range(0, len(sub), 8): b = sub[i:i + 8] au = [sf.read(r["audio"], dtype="float32")[0] for r in b] au = [a.mean(1) if a.ndim > 1 else a for a in au] x = proc(au, sampling_rate=SR, return_tensors="pt").input_features.cuda().to(torch.bfloat16) gk = dict(num_beams=BEAMS, max_new_tokens=200, language=WLANG, task="transcribe") if NOREP: gk["no_repeat_ngram_size"] = NOREP if REPPEN != 1.0: gk["repetition_penalty"] = REPPEN g = m.generate(x, **gk) hyps += [" ".join(t.strip().split()) for t in proc.batch_decode(g, skip_special_tokens=True)] if (i + 8) % 80 == 0: print(" %d/%d" % (min(i + 8, len(sub)), len(sub)), flush=True) w, c, k = comb(refs, hyps) print("\n=== WHISPER-FT %s ===" % LANG, flush=True) print("WER %.4f | CER %.4f | COMBINE %.4f" % (w, c, k), flush=True) for name, ref in (("joint_cont greedy", 0.3534), ("CONFIG DU RECORD (KenLM beam)", 0.3457)): print(" vs %-32s %.4f : %+.4f %s" % (name, ref, k - ref, "✅ MIEUX" if k < ref else "❌"), flush=True) nr = sum(t.count(",") for t in refs); nh = sum(t.count(",") for t in hyps) print("virgules : refs %d | whisper %d (notre CTC en produisait 20)" % (nr, nh), flush=True) print("\nexemples :", flush=True) for i in range(3): print(" REF : %s" % refs[i][:95], flush=True) print(" HYP : %s" % hyps[i][:95], flush=True) json.dump({"wer": w, "cer": c, "combine": k, "commas": nh}, open("/root/whisper_eval_%s.json" % LANG, "w")) print("WHISPER_EVAL_DONE", flush=True) if __name__ == "__main__": main()