"""Shared harness for the experiments reported in the technical report. Every experiment reads ONE unified feature cache (built once by `python experiments/extract_features.py` with deterministic torch settings), the ground-truth labels in inference/annotations.jsonl, and the frozen metric Final = (accuracy + group bonus) / (#questions + #groups). Groups/subtasks derive from question ids. Unified cache: one [71, 1024] float32 array per audio clip rows 0..49 layer L mean = row 2L, layer L std = row 2L+1 (L = 0..24) rows 50..61 layer-9: 12 fine equal-time segment means (K in {2,3,4,6,12} derivable) row 62 layer-9: elementwise max over time row 63 layer-9: |delta| mean (frame-to-frame velocity), row 64: delta std rows 65..67 layer-9: 3 energy-gated speech-segment means (silence-trimmed thirds) rows 68..70 layer-9: 3 direct equal-time segment means (the shipped pooling's segments) """ import hashlib import json import os import random import sys os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") # deterministic cuBLAS (before torch loads) ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) os.chdir(ROOT) sys.path.insert(0, ROOT) import numpy as np import yaml cfg = yaml.safe_load(open("configs/phase1.yaml")) CACHE = "cache/exp_unified" MID, LAYER, CAP_S = "microsoft/wavlm-large", 9, 15 # ---- cache row indices ----------------------------------------------------- def R_MEAN(L=LAYER): return 2 * L def R_STD(L=LAYER): return 2 * L + 1 R_FINE0, N_FINE = 50, 12 R_MAX, R_DVEL, R_DSTD = 62, 63, 64 R_SPEECH0, R_SEG3 = 65, 68 POOL_FINAL = [R_MEAN(), R_STD(), R_SEG3, R_SEG3 + 1, R_SEG3 + 2] # the shipped pooling (5120-d) def cpath(wav): return os.path.join(CACHE, hashlib.md5(wav.encode()).hexdigest()[:16] + ".npy") _mem = {} def arr(wav): a = _mem.get(wav) if a is None: a = _mem[wav] = np.load(cpath(wav)) return a def feat(rows): """featfn selecting fixed cache rows.""" return lambda w: np.concatenate([arr(w)[r] for r in rows]) def feat_fineK(K, base_rows=(R_MEAN(), R_STD())): """featfn: base rows + K coarse segment means derived from the 12 fine segments.""" b = np.linspace(0, N_FINE, K + 1).astype(int) def f(w): a = arr(w) segs = [a[R_FINE0 + b[i]:R_FINE0 + b[i + 1]].mean(0) for i in range(K)] return np.concatenate([a[r] for r in base_rows] + segs) return f # ---- data ------------------------------------------------------------------ from empathyeval.data.release import build_index, build_train_items # noqa: E402 QS, _GROUPS_IDX = build_index(cfg) def train_items(n=2500, seed=0): items = build_train_items(cfg) random.Random(seed).shuffle(items) return items[:n] def load_labels(): lab = {} for line in open("inference/annotations.jsonl", encoding="utf-8"): r = json.loads(line) lab[r["question_id"]] = r["answer"] return lab LABELS = load_labels() def gid(q): return q.rsplit("_", 1)[0] def subtask(q): return "tone" if q.split("_", 1)[0] == "emovdb" else "context" GROUPS = {} for q in LABELS: GROUPS.setdefault(gid(q), []).append(q) # ---- metric ---------------------------------------------------------------- def final(preds, qids=None): """Frozen metric over the given qids (default: all).""" lab = LABELS if qids is None else {q: LABELS[q] for q in qids} gs = {g: qq for g, qq in GROUPS.items() if qq[0] in lab} acc = sum(preds.get(q) == lab[q] for q in lab) bon = sum(all(preds.get(q) == lab[q] for q in qq) for qq in gs.values()) return (acc + bon) / (len(lab) + len(gs)) def report(name, preds, width=34): t = final(preds, [q for q in LABELS if subtask(q) == "tone"]) c = final(preds, [q for q in LABELS if subtask(q) == "context"]) print(f" {name:{width}s} tone={t:.3f} context={c:.3f} Final={final(preds):.4f}", flush=True) return final(preds) # ---- ranker ---------------------------------------------------------------- def train_ranker(featfn, n_items=2500, C=0.5): from sklearn.linear_model import LogisticRegression from sklearn.preprocessing import StandardScaler X, y = [], [] for it in train_items(n_items): try: g, b = featfn(it.good_wav), featfn(it.bad_wav) except Exception: continue X.append(g - b); y.append(1) X.append(b - g); y.append(0) X = np.array(X) sc = StandardScaler().fit(X) clf = LogisticRegression(max_iter=3000, C=C).fit(sc.transform(X), y) return sc, clf def answers(featfn, n_items=2500, C=0.5, model=None): """Train a ranker on the pairwise task and answer every test question (argmax).""" sc, clf = model if model else train_ranker(featfn, n_items, C) out = {} for q in QS: fv = sc.transform([featfn(o.wav) for o in q.options]) out[q.qid] = [o.letter for o in q.options][int(np.argmax(clf.decision_function(fv)))] return out # ---- paired bootstrap over groups ------------------------------------------- def paired_bootstrap(preds_a, preds_b, n=2000, seed=0): """Resample the groups; score BOTH systems on each resample. Returns delta = b - a stats.""" glist = list(GROUPS.values()) rng = random.Random(seed) deltas = [] for _ in range(n): samp = [glist[rng.randrange(len(glist))] for _ in range(len(glist))] def fin(p): acc = sum(p.get(q) == LABELS[q] for qq in samp for q in qq) bon = sum(all(p.get(q) == LABELS[q] for q in qq) for qq in samp) nd = sum(len(qq) for qq in samp) return (acc + bon) / (nd + len(samp)) deltas.append(fin(preds_b) - fin(preds_a)) d = np.array(deltas) return {"median": float(np.median(d)), "lo": float(np.percentile(d, 2.5)), "hi": float(np.percentile(d, 97.5)), "p_better": float((d > 0).mean())}