tiny-kws / common.py
priyadeepjaiswal9c's picture
demo app
682e4cc verified
Raw
History Blame Contribute Delete
3.12 kB
"""Shared constants and the audio feature frontend for tiny-kws.
Everything that must be identical between training, evaluation and the live
demo lives here: the label order, the feature parameters, and the log-mel
transform itself. If train and inference disagree on any of these, accuracy
silently collapses, so they are defined exactly once.
"""
import json
from pathlib import Path
import torch
import torch.nn as nn
import torchaudio
# ---------------------------------------------------------------------------
# Task definition: the standard 12-class Speech Commands benchmark
# ---------------------------------------------------------------------------
KEYWORDS = ["yes", "no", "up", "down", "left", "right", "on", "off", "stop", "go"]
LABELS = ["silence", "unknown"] + KEYWORDS # index 0 = silence, 1 = unknown
LABEL_TO_IDX = {l: i for i, l in enumerate(LABELS)}
# The 25 non-keyword words in Speech Commands v2; clips from these become "unknown".
UNKNOWN_WORDS = [
"backward", "bed", "bird", "cat", "dog", "eight", "five", "follow",
"forward", "four", "happy", "house", "learn", "marvin", "nine", "one",
"seven", "sheila", "six", "three", "tree", "two",
"visual", "wow", "zero",
]
# ---------------------------------------------------------------------------
# Audio / feature parameters
# ---------------------------------------------------------------------------
SAMPLE_RATE = 16_000 # Hz; dataset native rate
CLIP_SAMPLES = 16_000 # exactly 1 second per clip
N_FFT = 400 # 25 ms analysis window
HOP_LENGTH = 160 # 10 ms hop -> 101 frames per 1 s clip
N_MELS = 64
F_MIN, F_MAX = 20.0, 7600.0 # stay below Nyquist (8 kHz)
N_FRAMES = CLIP_SAMPLES // HOP_LENGTH + 1 # 101 (torch STFT uses center padding)
LOG_EPS = 1e-6
class LogMel(nn.Module):
"""Waveform (B, 16000) -> log-mel spectrogram (B, 1, 64, 101).
Kept as an nn.Module so it can be moved to CUDA for fast feature
extraction on Colab; on Apple Silicon it should stay on CPU because
torchaudio's spectrogram ops have gaps on MPS.
"""
def __init__(self):
super().__init__()
self.mel = torchaudio.transforms.MelSpectrogram(
sample_rate=SAMPLE_RATE,
n_fft=N_FFT,
hop_length=HOP_LENGTH,
n_mels=N_MELS,
f_min=F_MIN,
f_max=F_MAX,
power=2.0,
)
def forward(self, wav: torch.Tensor) -> torch.Tensor:
if wav.dim() == 1:
wav = wav.unsqueeze(0)
feats = torch.log(self.mel(wav) + LOG_EPS)
return feats.unsqueeze(1) # add channel dim
def normalize(feats: torch.Tensor, stats: dict) -> torch.Tensor:
"""Apply train-set global mean/std normalization."""
return (feats - stats["mean"]) / stats["std"]
def load_stats(path: Path) -> dict:
with open(path) as f:
return json.load(f)
def pick_device() -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
if torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")