| """One-time feature extraction for ALL experiments (run once; every exp_*.py reads this cache). |
| |
| python experiments/extract_features.py |
| |
| Extracts, per audio clip, one [71,1024] array (layout in common.py) from a frozen WavLM-large: |
| all 25 layers' mean+std, plus layer-9 fine-segment / max / velocity / speech-segment / 3-segment rows. |
| Covers the good+bad audio of all 4,892 training triples, all test candidates, and the utterance audio |
| needed by the utterance-conditioning experiment (~13.8k clips; ~4 GB cache; ~1 h on an 8 GB GPU). |
| |
| Deterministic settings (cuDNN deterministic, no benchmark autotuning, deterministic algorithms where |
| available) so a re-run on the same machine reproduces the cache — and therefore every experiment number. |
| Resumable: existing clips are skipped, so an interrupted run just continues. |
| """ |
| import sys |
| import os |
| sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) |
| import numpy as np |
| from experiments import common as C |
| from empathyeval.data.release import build_train_items, build_index |
| from empathyeval.data.audio import cached_load |
|
|
| os.makedirs(C.CACHE, exist_ok=True) |
| _M = {} |
|
|
|
|
| def _model(): |
| if not _M: |
| import torch |
| torch.backends.cudnn.deterministic = True |
| torch.backends.cudnn.benchmark = False |
| try: |
| torch.use_deterministic_algorithms(True, warn_only=True) |
| except TypeError: |
| pass |
| from transformers import AutoFeatureExtractor, WavLMModel |
| dev = "cuda" if torch.cuda.is_available() else "cpu" |
| _M["torch"] = torch |
| _M["fe"] = AutoFeatureExtractor.from_pretrained(C.MID) |
| _M["m"] = WavLMModel.from_pretrained(C.MID).to(dev).eval() |
| _M["dev"] = dev |
| print(f"loaded {C.MID} on {dev} (deterministic mode)", flush=True) |
| return _M |
|
|
|
|
| def seg_means(h, K): |
| T = h.shape[0] |
| idx = np.linspace(0, T, K + 1).astype(int) |
| return [h[idx[i]:max(idx[i] + 1, idx[i + 1])].mean(0) for i in range(K)] |
|
|
|
|
| def ensure(wav): |
| if os.path.exists(C.cpath(wav)): |
| return False |
| y = cached_load(wav, C.cfg)[:16000 * C.CAP_S] |
| m = _model() |
| inp = m["fe"](y, sampling_rate=16000, return_tensors="pt").input_values.to(m["dev"]) |
| with m["torch"].no_grad(): |
| hs = m["m"](inp, output_hidden_states=True).hidden_states |
| rows = [] |
| for h in hs: |
| h = h[0] |
| rows += [h.mean(0).cpu().numpy(), h.std(0).cpu().numpy()] |
| h9 = hs[C.LAYER][0].cpu().numpy() |
| T = h9.shape[0] |
| rows += seg_means(h9, C.N_FINE) |
| rows.append(h9.max(0)) |
| d = h9[1:] - h9[:-1] if T > 1 else h9 * 0.0 |
| rows += [np.abs(d).mean(0), d.std(0)] |
| e = np.array([np.sqrt((y[i * len(y) // T:(i + 1) * len(y) // T] ** 2).mean() + 1e-9) for i in range(T)]) |
| sp = h9[e > 0.12 * e.max()] |
| rows += seg_means(sp if sp.shape[0] >= 3 else h9, 3) |
| s3 = [h9[:T // 3], h9[T // 3:2 * T // 3], h9[2 * T // 3:]] if T >= 3 else [h9, h9, h9] |
| rows += [s.mean(0) for s in s3] |
| np.save(C.cpath(wav), np.stack(rows).astype(np.float32)) |
| return True |
|
|
|
|
| def all_wavs(): |
| items = build_train_items(C.cfg) |
| qs, _ = build_index(C.cfg) |
| wavs = [] |
| for it in items: |
| wavs += [it.good_wav, it.bad_wav] |
| for q in qs: |
| wavs += [o.wav for o in q.options] |
| for it in C.train_items(2500): |
| wavs.append(it.utterance_wav) |
| for q in qs: |
| wavs.append(q.utterance_wav) |
| return list(dict.fromkeys(wavs)) |
|
|
|
|
| def main(): |
| wavs = all_wavs() |
| done = 0 |
| for n, w in enumerate(wavs, 1): |
| try: |
| done += ensure(w) |
| except Exception as e: |
| print(f" skip {w}: {e}", flush=True) |
| if n % 300 == 0: |
| print(f" {n}/{len(wavs)} scanned, {done} new", flush=True) |
| missing = sum(0 if os.path.exists(C.cpath(w)) else 1 for w in wavs) |
| print(f"extracted {done}; {missing} missing of {len(wavs)}") |
| if missing == 0: |
| print("ALL-CACHED") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|