EmpathyEval / experiments /extract_features.py
cyanwingsbird's picture
Upload folder using huggingface_hub
a6205d4 verified
Raw
History Blame Contribute Delete
4.57 kB
"""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 # 25 x [1,T,1024]
rows = []
for h in hs: # rows 0..49: per-layer mean+std
h = h[0]
rows += [h.mean(0).cpu().numpy(), h.std(0).cpu().numpy()]
h9 = hs[C.LAYER][0].cpu().numpy() # layer-9 rows
T = h9.shape[0]
rows += seg_means(h9, C.N_FINE) # 50..61 fine segments
rows.append(h9.max(0)) # 62 max
d = h9[1:] - h9[:-1] if T > 1 else h9 * 0.0
rows += [np.abs(d).mean(0), d.std(0)] # 63,64 velocity stats
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()] # 65..67 speech segments
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] # 68..70 direct 3 segments
np.save(C.cpath(wav), np.stack(rows).astype(np.float32))
return True
def all_wavs():
items = build_train_items(C.cfg) # ALL 4,892 (train-size sweep needs them)
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): # utterances (conditioning experiment)
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()