Spaces:
Running on Zero
Running on Zero
File size: 6,611 Bytes
0302f3a bb272bb 69ae8ff bb272bb 69ae8ff bb272bb 69ae8ff bb272bb b0e55bc bb272bb 69ae8ff bb272bb 69ae8ff e4a87ac bb272bb e4a87ac bb272bb e4a87ac bb272bb 0302f3a bb272bb 69ae8ff bb272bb 69ae8ff bb272bb 69ae8ff bb272bb 83719f4 bb272bb e4a87ac bb272bb fda31b9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | import spaces
import torch
import torch.nn as nn
from huggingface_hub import hf_hub_download
import librosa
import numpy as np
import gradio as gr
# Vars
repo_name = "ipetrousov/ser_supcon"
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
weights_dict = "supcon_stage_ft_acc60.pt"
# Classes
class EmotionFeatureExtractorBase(nn.Module):
"""
Core CNN to extract spatial features from Mel-Spectrograms.
Outputs a flat 256-dimensional feature vector.
"""
def __init__(self):
super(EmotionFeatureExtractorBase, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv3 = nn.Sequential(
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.conv4 = nn.Sequential(
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.global_pool = nn.AdaptiveAvgPool2d((1, 1))
self.flatten = nn.Flatten()
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.global_pool(x)
x = self.flatten(x)
return x # Output shape: (batch_size, 256)
class SupConStage2(nn.Module):
""" Model B (Stage 2): Frozen Encoder + Linear Classifier. """
def __init__(self, trained_encoder, num_classes=8):
super(SupConStage2, self).__init__()
# Pass trained encoder from Stage 1
self.encoder = trained_encoder
# Freeze encoder weights
for param in self.encoder.parameters():
param.requires_grad = False
# Linear classifier
self.classifier = nn.Linear(256, num_classes)
def forward(self, x):
# Disable gradient tracking for the encoder to save memory/compute
with torch.no_grad():
features = self.encoder(x)
return self.classifier(features)
# Function
def preprocess_drive_audio(file_path, target_sample_rate=22050, n_mels=128, noise_std=0.0):
"""
Loads audio and generates a normalized Mel-spectrogram using librosa.
Returns a PyTorch tensor shaped (1, 1, Mels, Time).
"""
y, sr = librosa.load(file_path, sr=target_sample_rate, mono=True)
mel_spectrogram = librosa.feature.melspectrogram(
y=y,
sr=target_sample_rate,
n_fft=512,
hop_length=256,
n_mels=n_mels,
fmax=8000
)
mel_spectrogram_db = librosa.power_to_db(mel_spectrogram, ref=1.0)
mean = np.mean(mel_spectrogram_db)
std = np.std(mel_spectrogram_db)
mel_normalized = (mel_spectrogram_db - mean) / (std + 1e-6)
final_tensor = torch.tensor(mel_normalized, dtype=torch.float32)
final_tensor = final_tensor.unsqueeze(0).unsqueeze(0)
# Noise Injection Pipeline
if noise_std > 0.0:
noise_matrix = torch.randn_like(final_tensor)
final_tensor = final_tensor + (noise_matrix * noise_std)
return final_tensor
def run_local_inference(model, file_path, device, noise_std=0.0):
"""
Runs a forward pass
"""
emotion_classes = ["Neutral", "Calm", "Happy", "Sad", "Angry", "Fearful", "Disgust", "Surprised"]
input_tensor = preprocess_drive_audio(file_path, noise_std=noise_std)
input_tensor = input_tensor.to(device)
model.eval()
with torch.no_grad():
logits = model(input_tensor)
# Get the top prediction index
predicted_index = torch.argmax(logits, dim=1).item()
top_class = emotion_classes[predicted_index]
# Apply softmax to convert logits to probabilities between 0 and 1
probabilities = torch.softmax(logits, dim=1).squeeze().tolist()
# Map the probabilities to their respective class names for Gradio
prob_dict = {emotion_classes[i]: probabilities[i] for i in range(len(emotion_classes))}
return top_class, prob_dict
# Load SOTA SupConFN
blank_encoder = EmotionFeatureExtractorBase()
model = SupConStage2(trained_encoder=blank_encoder, num_classes=8)
# Download & Load weights
model_path = hf_hub_download(repo_id=repo_name, filename=weights_dict)
model.load_state_dict(torch.load(model_path, map_location=device))
model.eval()
model = model.to(device)
# Gradio Interface Setup
@spaces.GPU
def predict_emotion(audio_path, noise_std):
if audio_path is None:
return "Upload an audio file."
try:
pred = run_local_inference(model, audio_path, device, noise_std=noise_std)
return pred
except Exception as e:
return f"Error processing audio: {str(e)}"
description_html = """
Upload a short audio clip and test the architecture's resilience. The slider injects mathematically controlled Gaussian noise into the normalized Mel-spectrogram before classification.
<div style="display: flex; gap: 10px; margin-top: 15px;">
<a href="https://github.com/gpetrousov/dl_assignment_demokritos" target="_blank">
<img src="https://img.shields.io/badge/GitHub-View_Repository-181717?style=for-the-badge&logo=github" alt="GitHub Repository" />
</a>
<a href="https://docs.google.com/presentation/d/1ShSmXY2OqCEbQQjYiLYCFc8Psu2bioECoFHhliiDEiQ/edit?usp=drive_link" target="_blank">
<img src="https://img.shields.io/badge/Presentation-View_Slides-F4B400?style=for-the-badge&logo=googleslides&logoColor=white" alt="Presentation Slides" />
</a>
</div>
"""
demo = gr.Interface(
fn=predict_emotion,
inputs=[
gr.Audio(type="filepath", label="Upload Audio (.mp3 or .wav)"),
gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.0, label="Gaussian Noise Injection (STD)")
],
outputs=[
gr.Textbox(label="Final Prediction"),
gr.Label(num_top_classes=8, label="Probability Distribution")
],
title="Robust Speech Emotion Recognition (SupCon)",
description=description_html,
)
if __name__ == "__main__":
demo.launch(ssr_mode=False) |