MSP-Audio / feature_extraction_msp_audio.py
MahmoodAnaam's picture
Training in progress, step 1000
12a8889 verified
Raw
History Blame Contribute Delete
5.74 kB
from pathlib import Path
import numpy as np
import torch
from torchcodec.decoders import AudioDecoder
from transformers.feature_extraction_sequence_utils import SequenceFeatureExtractor
from transformers.feature_extraction_utils import BatchFeature
from transformers.utils import PaddingStrategy, TensorType, logging
logger = logging.get_logger(__name__)
class MSPAudioFeatureExtractor(SequenceFeatureExtractor):
model_input_names = ["input_values", "padding_mask"]
def __init__(
self,
feature_size: int = 1,
sampling_rate: int = 16000,
padding_value: float = 0.0,
return_attention_mask: bool = True,
do_normalize: bool = True,
**kwargs,
):
super().__init__(
feature_size=feature_size,
sampling_rate=sampling_rate,
padding_value=padding_value,
**kwargs,
)
self.return_attention_mask = return_attention_mask
self.do_normalize = do_normalize
@staticmethod
def zero_mean_unit_var_norm(
input_values: list[np.ndarray],
attention_mask: list[np.ndarray] | None,
padding_value: float = 0.0,
) -> list[np.ndarray]:
"""Normalize each sequence to zero mean and unit variance."""
if attention_mask is not None:
attention_mask = np.array(attention_mask, dtype=np.int32)
normed = []
for vec, length in zip(input_values, attention_mask.sum(-1)):
normed_slice = (vec - vec[:length].mean()) / np.sqrt(
vec[:length].var() + 1e-7
)
if length < normed_slice.shape[0]:
normed_slice[length:] = padding_value
normed.append(normed_slice)
else:
normed = [(x - x.mean()) / np.sqrt(x.var() + 1e-7) for x in input_values]
return normed
def _load_audio(
self,
src: str | Path | bytes | torch.Tensor,
start_seconds: float = 0.0,
stop_seconds: float | None = None,
) -> np.ndarray:
"""Load audio waveform from file path or bytes as a 1-D numpy array."""
audio_decoder = AudioDecoder(source=src, sample_rate=self.sampling_rate)
if stop_seconds is None:
stop_seconds = audio_decoder.metadata.duration_seconds_from_header
waveform = audio_decoder.get_samples_played_in_range(
start_seconds, stop_seconds
).data.numpy()
return waveform.squeeze() # shape: (T,)
def __call__(
self,
raw_speech: (
str
| Path
| bytes
| np.ndarray
| list[str]
| list[Path]
| list[bytes]
| list[float]
| list[np.ndarray]
| list[list[float]]
),
padding: bool | str | PaddingStrategy = False,
max_length: int | None = None,
truncation: bool = False,
pad_to_multiple_of: int | None = None,
return_attention_mask: bool | None = None,
return_tensors: str | TensorType | None = None,
sampling_rate: int | None = None,
**kwargs,
) -> BatchFeature:
"""
Featurize and pad one or several audio sequences.
"""
if sampling_rate is not None and sampling_rate != self.sampling_rate:
raise ValueError(
f"Sampling rate mismatch: expected {self.sampling_rate}, "
f"got {sampling_rate}."
)
is_batched_numpy = isinstance(raw_speech, np.ndarray) and raw_speech.ndim > 1
if is_batched_numpy and raw_speech.ndim > 2:
raise ValueError("Only mono-channel audio is supported.")
is_batched = is_batched_numpy or (
isinstance(raw_speech, (list, tuple))
and isinstance(raw_speech[0], (str, Path, bytes, np.ndarray, list, tuple))
)
if not is_batched:
raw_speech = [raw_speech]
# Load from file paths or bytes
if isinstance(raw_speech[0], (str, Path, bytes)):
raw_speech = [self._load_audio(src) for src in raw_speech]
encoded = BatchFeature({"input_values": raw_speech})
padded = self.pad(
encoded,
padding=padding,
max_length=max_length,
truncation=truncation,
pad_to_multiple_of=pad_to_multiple_of,
return_attention_mask=return_attention_mask,
)
# Ensure float32
vals = padded["input_values"]
if not isinstance(vals[0], np.ndarray):
padded["input_values"] = [np.asarray(a, dtype=np.float32) for a in vals]
elif isinstance(vals[0], np.ndarray) and vals[0].dtype == np.float64:
padded["input_values"] = [a.astype(np.float32) for a in vals]
# Normalize
attn = padded.get("attention_mask")
if attn is not None:
padded["attention_mask"] = [np.asarray(a, dtype=np.int32) for a in attn]
if self.do_normalize:
norm_attn = (
attn
if self._get_padding_strategies(padding, max_length=max_length)
is not PaddingStrategy.DO_NOT_PAD
else None
)
padded["input_values"] = self.zero_mean_unit_var_norm(
padded["input_values"],
attention_mask=norm_attn,
padding_value=self.padding_value,
)
# Rename attention_mask -> padding_mask
if "attention_mask" in padded:
padded["padding_mask"] = padded.pop("attention_mask")
if return_tensors is not None:
padded = padded.convert_to_tensors(return_tensors)
return padded