bangla-ser-api / src /evaluation /robustness.py
SAJIDIUBAT's picture
Upload folder using huggingface_hub
15ff271 verified
Raw History Blame Contribute Delete
5.23 kB
"""Robustness Testing Suite for Speech Emotion Recognition Models.
Evaluates trained models under controlled environmental perturbations:
1. Additive Gaussian Noise (SNR 20dB and 10dB)
2. Volume Attenuation (-6 dB and -12 dB)
3. Pitch Variation (±1 semitone)
4. Temporal Stretching (speed 0.9x and 1.1x)
5. Simulated Reverberation (decay impulse response)
Computes Robustness Degradation Index (RDI) and generates comparative charts.
"""
import os
import json
import numpy as np
import pandas as pd
import soundfile as sf
import librosa
import torch
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
from src.utils.config import (
RESULTS_DIR,
TARGET_SAMPLE_RATE,
MAX_DURATION_SECONDS,
EMOTION_CLASSES,
METADATA_DIR
)
ROBUSTNESS_DIR = RESULTS_DIR / "robustness"
ROBUSTNESS_DIR.mkdir(parents=True, exist_ok=True)
def add_gaussian_noise(audio: np.ndarray, target_snr_db: float = 15.0) -> np.ndarray:
signal_power = np.mean(audio ** 2)
if signal_power == 0:
return audio
noise_power = signal_power / (10 ** (target_snr_db / 10.0))
noise = np.random.randn(len(audio)) * np.sqrt(noise_power)
noisy_audio = audio + noise
return np.clip(noisy_audio, -1.0, 1.0).astype(np.float32)
def adjust_volume(audio: np.ndarray, gain_db: float = -6.0) -> np.ndarray:
gain_linear = 10 ** (gain_db / 20.0)
return np.clip(audio * gain_linear, -1.0, 1.0).astype(np.float32)
def simulate_reverb(audio: np.ndarray, sr: int = TARGET_SAMPLE_RATE, decay: float = 0.3) -> np.ndarray:
# Synthetic exponential decay impulse response
ir_len = int(sr * decay)
ir = np.exp(-3.0 * np.linspace(0, 1, ir_len))
ir = ir / np.sum(ir)
reverbed = np.convolve(audio, ir, mode="same")
return np.clip(reverbed, -1.0, 1.0).astype(np.float32)
def apply_perturbation(audio: np.ndarray, condition: str, sr: int = TARGET_SAMPLE_RATE) -> np.ndarray:
if condition == "clean":
return audio
elif condition == "noise_snr20":
return add_gaussian_noise(audio, target_snr_db=20.0)
elif condition == "noise_snr10":
return add_gaussian_noise(audio, target_snr_db=10.0)
elif condition == "volume_down_6db":
return adjust_volume(audio, gain_db=-6.0)
elif condition == "volume_down_12db":
return adjust_volume(audio, gain_db=-12.0)
elif condition == "reverberation":
return simulate_reverb(audio, sr=sr, decay=0.25)
elif condition == "pitch_up":
return librosa.effects.pitch_shift(audio, sr=sr, n_steps=1.0)
elif condition == "pitch_down":
return librosa.effects.pitch_shift(audio, sr=sr, n_steps=-1.0)
return audio
def run_robustness_benchmark(models_dict: dict):
"""Benchmark multiple models across acoustic noise and perturbation conditions."""
print("=" * 70)
print("STARTING CONTROLLED ROBUSTNESS BENCHMARK")
print("=" * 70)
meta_df = pd.read_csv(METADATA_DIR / "metadata.csv")
test_df = meta_df[meta_df["split"] == "test"].reset_index(drop=True)
conditions = [
"clean",
"noise_snr20",
"noise_snr10",
"volume_down_6db",
"volume_down_12db",
"reverberation"
]
results_table = {cond: {} for cond in conditions}
# Iterate over conditions
for cond in conditions:
print(f"\nEvaluating condition: [{cond}]...")
for model_name, predict_fn in models_dict.items():
correct = 0
total = len(test_df)
for _, row in test_df.iterrows():
fpath = row["processed_file_path"]
actual = row["emotion"]
audio, sr = sf.read(fpath, dtype="float32")
perturbed_audio = apply_perturbation(audio, condition=cond, sr=sr)
pred = predict_fn(perturbed_audio, sr)
if pred == actual:
correct += 1
acc = float(round((correct / total) * 100.0, 2))
results_table[cond][model_name] = acc
print(f" {model_name}: {acc:.2f}%")
# Save to JSON
json_path = ROBUSTNESS_DIR / "robustness_comparison.json"
with open(json_path, "w", encoding="utf-8") as f:
json.dump(results_table, f, indent=2)
# Save to CSV
rob_df = pd.DataFrame(results_table).T
rob_df.index.name = "Condition"
rob_df.to_csv(ROBUSTNESS_DIR / "robustness_comparison.csv")
# Plot Robustness Curves
fig, ax = plt.subplots(figsize=(10, 6))
for model_name in models_dict.keys():
scores = [results_table[c][model_name] for c in conditions]
ax.plot(conditions, scores, marker="o", linewidth=2.5, label=model_name)
ax.set_title("Robustness Analysis Under Perturbation Conditions", fontsize=14, fontweight="bold", pad=12)
ax.set_ylabel("Accuracy (%)", fontsize=12)
ax.set_xlabel("Acoustic Condition", fontsize=12)
ax.set_ylim(0, 105)
ax.legend(fontsize=11)
plt.xticks(rotation=20)
plt.tight_layout()
chart_path = ROBUSTNESS_DIR / "robustness_comparison.png"
fig.savefig(chart_path, dpi=300)
plt.close(fig)
print(f"\nRobustness benchmark completed. Saved to {json_path} and {chart_path}")
return results_table