File size: 7,758 Bytes
6eed659 | 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 | #!/usr/bin/env python3
"""POOL INTER-MODELES sur le SHONA — transposition de ce qui vient de marcher sur
le lingala (`sub_XC010.csv` = 0.762499646, +0.000126 sur le record).
Un juge CLASSE, il ne PROPOSE pas : tous les candidats shona sortaient du beam de
`sna_ps_best` seul. On ajoute ici les hypotheses d un DONNEUR (autre distribution).
BEAUCOUP plus simple que le lingala : le shona decode en beam PUR (aucun KenLM, le LM
degradait le shona). Le score du beam est donc du CTC acoustique seul => un candidat
externe se score EXACTEMENT sur la meme echelle avec ctc_score(), sans formule de
calibration ni offset. C est precisement la calibration qui avait casse le 1er essai
lingala (-0.0091). Ici ce risque n existe pas.
Mecanisme deja valide dans le pipeline du record : `gen_sna_rescore.py` ajoute deja
le greedy et le decode-batch comme candidats supplementaires, notes par ctc_score.
GARDE-FOU DE LONGUEUR conserve (mots ET caracteres) : sur le lingala, l absence de
borne en CARACTERES suffisait a tout perdre (memes mots, formes 40 % plus longues).
"""
import csv
import json
import os
import sys
import numpy as np
import torch
from multiprocessing import Pool
from pyctcdecode import build_ctcdecoder
sys.path.insert(0, "/root")
from gen_sna_rescore import compute_logits, ctc_score, encode_for, norm # noqa: E402
BASE = os.environ.get("BASE", "/root/sub_XC010.csv") # record 0.762499646
LANGF = os.environ.get("LANGF", "/root/test_lang.json")
SNAM = os.environ.get("SNA_MODEL", "/root/models/sna_ps_best")
J1 = os.environ.get("J1", "/root/models/sna_r2_best")
J2 = os.environ.get("J2", "/scratch/runs/xlsrlong/checkpoint-5600")
J3 = os.environ.get("J3", "/root/models/mmsjoint_best")
DONOR = os.environ.get("DONOR", "/root/models/mmsjoint_best")
W1 = float(os.environ.get("W1", "6.0"))
W2 = float(os.environ.get("W2", "4.0"))
W3 = float(os.environ.get("W3", "2.0"))
MARGINS = [float(x) for x in os.environ.get("MARGINS", "1e9,10,5,0").split(",")]
DON_NBEST = int(os.environ.get("DON_NBEST", "5"))
RLO = float(os.environ.get("RATIO_LO", "0.85"))
RHI = float(os.environ.get("RATIO_HI", "1.15"))
GAMMA = float(os.environ.get("GAMMA", "0.0"))
NBEST = int(os.environ.get("NBEST", "50"))
BW = int(os.environ.get("BW", "256"))
TAG = os.environ.get("TAG", "SX")
AUD = os.environ.get("AUDIO_DIR", "/scratch/p2_16k")
def main():
base = {r["ID"]: r["Target"] for r in csv.DictReader(open(BASE, encoding="utf-8"))}
lang = json.load(open(LANGF))
sna_ids = [k for k in base if lang.get(k) == "sna"]
print("base %d | sna %d | lin intouches %d"
% (len(base), len(sna_ids), len(base) - len(sna_ids)), flush=True)
files = [os.path.join(AUD, k + ".wav") for k in sna_ids]
assert all(os.path.exists(f) for f in files), "audio manquant"
proc, L1 = compute_logits(SNAM, files)
tok = proc.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] = ""
greedy = [norm(tok.decode(l.argmax(-1))) for l in L1]
dec = build_ctcdecoder(lab) # beam PUR, aucun LM
with Pool(8) as p:
allbeams = dec.decode_beams_batch(p, L1, beam_width=BW)
with Pool(8) as p:
db = [norm(x) for x in dec.decode_batch(p, L1, beam_width=BW)]
cands, AC1, NW, ISEXT = [], [], [], []
for i, bs in enumerate(allbeams):
c = [norm(b[0]) for b in bs[:NBEST]]
a = [(b[3] if len(b) > 3 else 0.0) for b in bs[:NBEST]]
for extra in (db[i], greedy[i]):
if extra and extra not in c:
c.append(extra)
a.append(ctc_score(L1[i], encode_for(tok, extra), tok.pad_token_id))
cands.append(c)
AC1.append(list(a))
ISEXT.append([False] * len(c))
print("n-best du modele principal construit", flush=True)
# ---------- candidats DONNEURS ----------
dproc, DL = compute_logits(DONOR, files)
dtok = dproc.tokenizer
dv = dtok.get_vocab()
dlab = [None] * len(dv)
for t, i in dv.items():
dlab[i] = t
dlab[dtok.word_delimiter_token_id] = " "
dlab[dtok.unk_token_id] = "⁇"
dlab[dtok.pad_token_id] = ""
ddec = build_ctcdecoder(dlab)
with Pool(8) as p:
dbeams = ddec.decode_beams_batch(p, DL, beam_width=64, prune_history=True)
del ddec, DL
torch.cuda.empty_cache()
nadd = 0
for i in range(len(sna_ids)):
seen = set(cands[i])
for b in dbeams[i][:DON_NBEST]:
h = norm(b[0])
if h and h not in seen:
seen.add(h)
cands[i].append(h)
# meme echelle EXACTE : le beam shona est du CTC pur, sans LM
AC1[i].append(ctc_score(L1[i], encode_for(tok, h), tok.pad_token_id))
ISEXT[i].append(True)
nadd += 1
del dbeams, L1
print("donneur %s : %d hypotheses NOUVELLES (%.1f/clip)"
% (os.path.basename(DONOR), nadd, nadd / max(len(sna_ids), 1)), flush=True)
AC1 = [np.array(x) for x in AC1]
ISEXT = [np.array(x) for x in ISEXT]
NW = [np.array([float(len(x.split())) for x in c]) for c in cands]
def judge(path):
pr, LG = compute_logits(path, files)
t = pr.tokenizer
R = [np.array([ctc_score(LG[i], encode_for(t, x), t.pad_token_id)
for x in cands[i]]) for i in range(len(sna_ids))]
del LG
torch.cuda.empty_cache()
print("juge %s OK" % os.path.basename(path), flush=True)
return R
R1, R2, R3 = judge(J1), judge(J2), judge(J3)
from huggingface_hub import HfApi
api = HfApi(token=open(os.path.expanduser("~/.cache/huggingface/token")).read().strip())
for mg in MARGINS:
out = dict(base)
nchg = next_ = 0
for i, k in enumerate(sna_ids):
tot = AC1[i] + W1 * R1[i] + W2 * R2[i] + W3 * R3[i] + GAMMA * NW[i]
bm = ~ISEXT[i]
jb = int(np.argmax(np.where(bm, tot, -1e18)))
nref, cref = len(cands[i][jb].split()), len(cands[i][jb])
ct = tot.copy()
ct[ISEXT[i]] -= mg
for j2 in np.flatnonzero(ISEXT[i]):
r = len(cands[i][j2].split()) / max(nref, 1)
rc = len(cands[i][j2]) / max(cref, 1)
if r < RLO or r > RHI or rc < RLO or rc > RHI:
ct[j2] = -1e18
j = int(np.argmax(ct))
if ISEXT[i][j]:
next_ += 1
pick = cands[i][j] or greedy[i] or "a"
if pick != base[k]:
nchg += 1
out[k] = pick
empt = sum(1 for x in out.values() if not str(x).strip())
dlin = sum(1 for k in base if lang.get(k) != "sna" and out[k] != base[k])
tag = "%s%s" % (TAG, ("CTL" if mg > 1e8 else "%03d" % round(mg)))
OUT = "/root/sub_%s.csv" % tag
with open(OUT, "w", newline="", encoding="utf-8") as f:
wr = csv.writer(f)
wr.writerow(["ID", "Target"])
for k in base:
wr.writerow([k, out[k] or "a"])
assert len(out) == 892 and empt == 0 and dlin == 0, "%s INVALIDE" % tag
if mg <= 1e8:
api.upload_file(path_or_fileobj=OUT,
path_in_repo="phase2_corrected/sub_%s.csv" % tag,
repo_id="Pricile/waxal2026-backup", repo_type="model")
flag = " <-- CONTROLE : doit etre 0" if mg > 1e8 else ""
print("%-8s MARGIN=%8.1f | sna modifies %3d/%d | externes retenus %3d | lin intouche%s"
% (tag, mg, nchg, len(sna_ids), next_, flag), flush=True)
print("CROSSPOOL_SNA_DONE", flush=True)
if __name__ == "__main__":
main()
|