ipetrousov commited on
Commit
bb272bb
·
verified ·
1 Parent(s): 68535a8

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +176 -0
app.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from huggingface_hub import hf_hub_download
4
+ import librosa
5
+ import numpy as np
6
+ import gradio as gr
7
+
8
+
9
+ # Vars
10
+ repo_name = "ipetrousov/ser_supcon"
11
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
12
+ weights_dict = "supcon_stage_ft_acc60.pt"
13
+
14
+ # Classes
15
+ class EmotionFeatureExtractorBase(nn.Module):
16
+ """
17
+ Core CNN to extract spatial features from Mel-Spectrograms.
18
+ Outputs a flat 256-dimensional feature vector.
19
+ """
20
+
21
+ def __init__(self):
22
+ super(EmotionFeatureExtractorBase, self).__init__()
23
+
24
+ self.conv1 = nn.Sequential(
25
+ nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1, padding=1),
26
+ nn.BatchNorm2d(32),
27
+ nn.ReLU(),
28
+ nn.MaxPool2d(kernel_size=2, stride=2)
29
+ )
30
+
31
+ self.conv2 = nn.Sequential(
32
+ nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1),
33
+ nn.BatchNorm2d(64),
34
+ nn.ReLU(),
35
+ nn.MaxPool2d(kernel_size=2, stride=2)
36
+ )
37
+
38
+ self.conv3 = nn.Sequential(
39
+ nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
40
+ nn.BatchNorm2d(128),
41
+ nn.ReLU(),
42
+ nn.MaxPool2d(kernel_size=2, stride=2)
43
+ )
44
+
45
+ self.conv4 = nn.Sequential(
46
+ nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
47
+ nn.BatchNorm2d(256),
48
+ nn.ReLU(),
49
+ nn.MaxPool2d(kernel_size=2, stride=2)
50
+ )
51
+
52
+ self.global_pool = nn.AdaptiveAvgPool2d((1, 1))
53
+ self.flatten = nn.Flatten()
54
+
55
+ def forward(self, x):
56
+ x = self.conv1(x)
57
+ x = self.conv2(x)
58
+ x = self.conv3(x)
59
+ x = self.conv4(x)
60
+ x = self.global_pool(x)
61
+ x = self.flatten(x)
62
+ return x # Output shape: (batch_size, 256)
63
+
64
+
65
+ class SupConStage2(nn.Module):
66
+ """ Model B (Stage 2): Frozen Encoder + Linear Classifier. """
67
+
68
+ def __init__(self, trained_encoder, num_classes=8):
69
+ super(SupConStage2, self).__init__()
70
+
71
+ # Pass trained encoder from Stage 1
72
+ self.encoder = trained_encoder
73
+
74
+ # Freeze encoder weights
75
+ for param in self.encoder.parameters():
76
+ param.requires_grad = False
77
+
78
+ # Linear classifier
79
+ self.classifier = nn.Linear(256, num_classes)
80
+
81
+ def forward(self, x):
82
+ # Disable gradient tracking for the encoder to save memory/compute
83
+ with torch.no_grad():
84
+ features = self.encoder(x)
85
+
86
+ return self.classifier(features)
87
+
88
+
89
+ # Function
90
+ def preprocess_drive_audio(file_path, target_sample_rate=22050, n_mels=128, noise_std=0.0):
91
+ """
92
+ Loads audio and generates a normalized Mel-spectrogram using librosa.
93
+ Returns a PyTorch tensor shaped (1, 1, Mels, Time).
94
+ """
95
+
96
+ y, sr = librosa.load(file_path, sr=target_sample_rate, mono=True)
97
+
98
+ mel_spectrogram = librosa.feature.melspectrogram(
99
+ y=y,
100
+ sr=target_sample_rate,
101
+ n_fft=512,
102
+ hop_length=256,
103
+ n_mels=n_mels,
104
+ fmax=8000
105
+ )
106
+
107
+ mel_spectrogram_db = librosa.power_to_db(mel_spectrogram, ref=1.0)
108
+
109
+ mean = np.mean(mel_spectrogram_db)
110
+ std = np.std(mel_spectrogram_db)
111
+ mel_normalized = (mel_spectrogram_db - mean) / (std + 1e-6)
112
+
113
+ final_tensor = torch.tensor(mel_normalized, dtype=torch.float32)
114
+ final_tensor = final_tensor.unsqueeze(0).unsqueeze(0)
115
+
116
+ # Noise Injection Pipeline
117
+ if noise_std > 0.0:
118
+ noise_matrix = torch.randn_like(final_tensor)
119
+ final_tensor = final_tensor + (noise_matrix * noise_std)
120
+
121
+ return final_tensor
122
+
123
+
124
+ def run_local_inference(model, file_path, device, noise_std=0.0):
125
+ """
126
+ Runs a forward pass
127
+ """
128
+
129
+ emotion_classes = ["Neutral", "Calm", "Happy", "Sad", "Angry", "Fearful", "Disgust", "Surprised"]
130
+
131
+ input_tensor = preprocess_drive_audio(file_path, noise_std=noise_std)
132
+ input_tensor = input_tensor.to(device)
133
+
134
+ model.eval()
135
+ with torch.no_grad():
136
+ logits = model(input_tensor)
137
+ predicted_index = torch.argmax(logits, dim=1).item()
138
+
139
+ return emotion_classes[predicted_index]
140
+
141
+
142
+ # Load SOTA SupConFN
143
+ blank_encoder = EmotionFeatureExtractorBase()
144
+ model = SupConStage2(trained_encoder=blank_encoder, num_classes=8)
145
+
146
+ # Download & Load weights
147
+ model_path = hf_hub_download(repo_id=repo_name, filename=weights_dict)
148
+ model.load_state_dict(torch.load(model_path, map_location=device))
149
+ model.eval()
150
+ model = model.to(device)
151
+
152
+
153
+ # Gradio Interface Setup
154
+ def predict_emotion(audio_path, noise_std):
155
+ if audio_path is None:
156
+ return "Upload an audio file."
157
+
158
+ try:
159
+ pred = run_local_inference(model, audio_path, device, noise_std=noise_std)
160
+ return pred
161
+ except Exception as e:
162
+ return f"Error processing audio: {str(e)}"
163
+
164
+ demo = gr.Interface(
165
+ fn=predict_emotion,
166
+ inputs=[
167
+ gr.Audio(type="filepath", label="Upload Audio (.mp3 or .wav)"),
168
+ gr.Slider(minimum=0.0, maximum=1.0, step=0.05, value=0.0, label="Gaussian Noise Injection (STD)")
169
+ ],
170
+ outputs=gr.Textbox(label="Predicted Emotion"),
171
+ title="Robust Speech Emotion Recognition (SupCon)",
172
+ description="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."
173
+ )
174
+
175
+ if __name__ == "__main__":
176
+ demo.launch()