Spaces:
Running on Zero
Running on Zero
Download src/data/dataset.py from SAJIDIUBAT/bangla-ser-api: direct link, hf CLI and curl.
- Browser
- Download file 4.94 kB
-
https://huggingface.co/spaces/SAJIDIUBAT/bangla-ser-api/resolve/main/src/data/dataset.py
- Command line
-
hf download hf://spaces/SAJIDIUBAT/bangla-ser-api/src/data/dataset.py
-
curl -L -o dataset.py https://huggingface.co/spaces/SAJIDIUBAT/bangla-ser-api/resolve/main/src/data/dataset.py
4.94 kB
| """PyTorch Dataset and DataLoader with Audio Augmentation for Bangla Speech Emotion Recognition.""" | |
| import os | |
| import random | |
| import torch | |
| from torch.utils.data import Dataset, DataLoader | |
| import numpy as np | |
| import soundfile as sf | |
| import librosa | |
| import pandas as pd | |
| from src.utils.config import ( | |
| METADATA_DIR, | |
| TARGET_SAMPLE_RATE, | |
| MAX_DURATION_SECONDS, | |
| EMOTION_TO_ID, | |
| EMOTION_CLASSES | |
| ) | |
| class AudioAugmenter: | |
| """Acoustic augmentations tailored for speech emotion recognition without distorting emotion.""" | |
| def __init__(self, p: float = 0.5): | |
| self.p = p | |
| def apply(self, y: np.ndarray, sr: int = TARGET_SAMPLE_RATE) -> np.ndarray: | |
| if random.random() > self.p: | |
| return y | |
| aug_type = random.choice(["noise", "gain", "shift", "pitch"]) | |
| if aug_type == "noise": | |
| noise_amp = random.uniform(0.002, 0.015) | |
| noise = np.random.randn(len(y)) * noise_amp | |
| y = y + noise | |
| elif aug_type == "gain": | |
| gain_factor = random.uniform(0.8, 1.2) | |
| y = y * gain_factor | |
| elif aug_type == "shift": | |
| shift_samples = int(random.uniform(-0.15, 0.15) * sr) | |
| y = np.roll(y, shift_samples) | |
| elif aug_type == "pitch": | |
| # Mild pitch variation (-1 to +1 semitone) | |
| n_steps = random.choice([-1.0, -0.5, 0.5, 1.0]) | |
| y = librosa.effects.pitch_shift(y, sr=sr, n_steps=n_steps) | |
| # Clip to prevent digital clipping | |
| y = np.clip(y, -1.0, 1.0) | |
| return y | |
| class BanglaSpeechDataset(Dataset): | |
| """Dataset delivering log Mel-spectrograms or raw waveforms with labels.""" | |
| def __init__( | |
| self, | |
| split: str = "train", | |
| feature_type: str = "melspec", | |
| n_mels: int = 128, | |
| augment: bool = False, | |
| cached_mel_dict: dict = None | |
| ): | |
| self.split = split | |
| self.feature_type = feature_type | |
| self.n_mels = n_mels | |
| self.augment = augment | |
| self.augmenter = AudioAugmenter(p=0.5) if augment else None | |
| self.target_len = int(MAX_DURATION_SECONDS * TARGET_SAMPLE_RATE) # 160,000 samples | |
| meta_path = METADATA_DIR / "metadata.csv" | |
| df = pd.read_csv(meta_path) | |
| self.df = df[df["split"] == split].reset_index(drop=True) | |
| # In-memory cached mel spectrograms if provided | |
| self.cached_mel = cached_mel_dict.get(split) if cached_mel_dict else None | |
| def __len__(self): | |
| return len(self.df) | |
| def __getitem__(self, idx): | |
| row = self.df.iloc[idx] | |
| emotion = row["emotion"] | |
| label_id = EMOTION_TO_ID[emotion] | |
| audio_id = row["audio_id"] | |
| if self.feature_type == "melspec" and self.cached_mel is not None: | |
| # Ultra-fast indexed from RAM | |
| mel = self.cached_mel[idx] | |
| tensor_mel = torch.tensor(mel, dtype=torch.float32).unsqueeze(0) | |
| return tensor_mel, label_id, audio_id | |
| file_path = row["processed_file_path"] | |
| audio, sr = sf.read(file_path, dtype="float32") | |
| if self.augment and self.augmenter: | |
| audio = self.augmenter.apply(audio, sr=sr) | |
| # Pad or truncate to fixed length (10 seconds) | |
| if len(audio) < self.target_len: | |
| audio = np.pad(audio, (0, self.target_len - len(audio)), mode="constant") | |
| else: | |
| audio = audio[:self.target_len] | |
| if self.feature_type == "raw": | |
| return torch.tensor(audio, dtype=torch.float32), label_id, audio_id | |
| elif self.feature_type == "melspec": | |
| # Compute 128 Mel bands | |
| mel_spec = librosa.feature.melspectrogram( | |
| y=audio, sr=sr, n_fft=1024, hop_length=512, n_mels=self.n_mels, fmin=20, fmax=8000 | |
| ) | |
| log_mel_spec = librosa.power_to_db(mel_spec, ref=np.max) | |
| log_mel_spec = (log_mel_spec - np.mean(log_mel_spec)) / (np.std(log_mel_spec) + 1e-6) | |
| tensor_mel = torch.tensor(log_mel_spec, dtype=torch.float32).unsqueeze(0) | |
| return tensor_mel, label_id, audio_id | |
| elif self.feature_type == "mfcc": | |
| mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=40, n_fft=1024, hop_length=512) | |
| mfcc = (mfcc - np.mean(mfcc)) / (np.std(mfcc) + 1e-6) | |
| # Shape: (313, 40) | |
| tensor_mfcc = torch.tensor(mfcc.T, dtype=torch.float32) | |
| return tensor_mfcc, label_id, audio_id | |
| def get_dataloader( | |
| split: str = "train", | |
| feature_type: str = "melspec", | |
| batch_size: int = 16, | |
| augment: bool = False, | |
| num_workers: int = 0, | |
| cached_mel_dict: dict = None | |
| ) -> DataLoader: | |
| dataset = BanglaSpeechDataset( | |
| split=split, | |
| feature_type=feature_type, | |
| augment=augment, | |
| cached_mel_dict=cached_mel_dict | |
| ) | |
| shuffle = (split == "train") | |
| return DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=num_workers) | |