neuralninja110 commited on
Commit
cd65ca4
Β·
verified Β·
1 Parent(s): a5fc814

Upload folder using huggingface_hub

Browse files
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Music Genre Classification β€” 5-Model Ensemble
3
+ Hugging Face Spaces Deployment (Gradio)
4
+ Public F1 Score: 0.801
5
+ """
6
+ import os, torch, numpy as np, librosa
7
+ from scipy.ndimage import zoom as scipyzoom
8
+ import torch.nn as nn
9
+ import gradio as gr
10
+
11
+ # ── Constants ──────────────────────────────────────────────────────────
12
+ NCLASSES = 10
13
+ MAXFRAMES = 256
14
+ SR = 22050
15
+ NMELS = 128
16
+ NFFT = 2048
17
+ HOP = 512
18
+ GENRES = ["blues", "classical", "country", "disco", "hiphop",
19
+ "jazz", "metal", "pop", "reggae", "rock"]
20
+
21
+ VAL_F1 = [0.8255, 0.8391, 0.7601, 0.7966, 0.7872]
22
+ _raw = np.exp(np.array(VAL_F1, dtype=np.float32) * 10)
23
+ ENS_WEIGHTS = torch.FloatTensor(_raw / _raw.sum()).view(-1, 1, 1)
24
+ device = torch.device("cpu")
25
+
26
+ # ── Building Blocks ───────────────────────────────────────────────────
27
+
28
+ class ResBlock(nn.Module):
29
+ def __init__(self, c_in, c_out, stride=1):
30
+ super().__init__()
31
+ self.body = nn.Sequential(
32
+ nn.Conv2d(c_in, c_out, 3, stride=stride, padding=1, bias=False),
33
+ nn.BatchNorm2d(c_out), nn.ReLU(True),
34
+ nn.Conv2d(c_out, c_out, 3, padding=1, bias=False),
35
+ nn.BatchNorm2d(c_out))
36
+ self.skip = (nn.Sequential(
37
+ nn.Conv2d(c_in, c_out, 1, stride=stride, bias=False),
38
+ nn.BatchNorm2d(c_out))
39
+ if stride != 1 or c_in != c_out else nn.Identity())
40
+ self.act = nn.ReLU(True)
41
+ def forward(self, x):
42
+ return self.act(self.body(x) + self.skip(x))
43
+
44
+ class SEBlock(nn.Module):
45
+ def __init__(self, c, ratio=4):
46
+ super().__init__()
47
+ h = max(4, c // ratio)
48
+ self.pool = nn.AdaptiveAvgPool2d(1)
49
+ self.fc = nn.Sequential(
50
+ nn.Flatten(), nn.Linear(c, h), nn.ReLU(True),
51
+ nn.Linear(h, c), nn.Sigmoid())
52
+ def forward(self, x):
53
+ return x * self.fc(self.pool(x)).view(x.size(0), -1, 1, 1)
54
+
55
+ class SEResBlock(nn.Module):
56
+ def __init__(self, c_in, c_out, stride=1, ratio=4):
57
+ super().__init__()
58
+ self.body = nn.Sequential(
59
+ nn.Conv2d(c_in, c_out, 3, stride=stride, padding=1, bias=False),
60
+ nn.BatchNorm2d(c_out), nn.ReLU(True),
61
+ nn.Conv2d(c_out, c_out, 3, padding=1, bias=False),
62
+ nn.BatchNorm2d(c_out))
63
+ self.se = SEBlock(c_out, ratio)
64
+ self.skip = (nn.Sequential(
65
+ nn.Conv2d(c_in, c_out, 1, stride=stride, bias=False),
66
+ nn.BatchNorm2d(c_out))
67
+ if stride != 1 or c_in != c_out else nn.Identity())
68
+ self.act = nn.ReLU(True)
69
+ def forward(self, x):
70
+ return self.act(self.se(self.body(x)) + self.skip(x))
71
+
72
+ # ── Model Definitions ────────────────────────────────────────────────
73
+
74
+ class FreqFirstNet(nn.Module):
75
+ def __init__(self):
76
+ super().__init__()
77
+ self.freqcnn = nn.Sequential(
78
+ nn.Conv2d(3, 32, (8,1), stride=(4,1), padding=(2,0), bias=False),
79
+ nn.BatchNorm2d(32), nn.ReLU(True),
80
+ nn.Conv2d(32, 64, (8,1), stride=(4,1), padding=(2,0), bias=False),
81
+ nn.BatchNorm2d(64), nn.ReLU(True),
82
+ nn.AdaptiveAvgPool2d((1, MAXFRAMES)))
83
+ self.tempcnn = nn.Sequential(
84
+ nn.Conv1d(64, 64, 7, padding=3, bias=False),
85
+ nn.BatchNorm1d(64), nn.ReLU(True), nn.MaxPool1d(4),
86
+ nn.Conv1d(64, 128, 5, padding=2, bias=False),
87
+ nn.BatchNorm1d(128), nn.ReLU(True),
88
+ nn.AdaptiveAvgPool1d(1), nn.Flatten())
89
+ self.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(128, NCLASSES))
90
+ def forward(self, x):
91
+ return self.fc(self.tempcnn(self.freqcnn(x).squeeze(2)))
92
+
93
+ class FreqFirstNetV2(nn.Module):
94
+ def __init__(self):
95
+ super().__init__()
96
+ self.freqcnn = nn.Sequential(
97
+ nn.Conv2d(3, 48, (8,1), stride=(4,1), padding=(2,0), bias=False),
98
+ nn.BatchNorm2d(48), nn.ReLU(True),
99
+ nn.Conv2d(48, 96, (8,1), stride=(4,1), padding=(2,0), bias=False),
100
+ nn.BatchNorm2d(96), nn.ReLU(True),
101
+ nn.AdaptiveAvgPool2d((1, MAXFRAMES)))
102
+ self.tempcnn = nn.Sequential(
103
+ nn.Conv1d(96, 96, 5, padding=2, bias=False),
104
+ nn.BatchNorm1d(96), nn.ReLU(True), nn.MaxPool1d(4),
105
+ nn.Conv1d(96, 192, 3, padding=1, bias=False),
106
+ nn.BatchNorm1d(192), nn.ReLU(True),
107
+ nn.AdaptiveAvgPool1d(1), nn.Flatten())
108
+ self.fc = nn.Sequential(nn.Dropout(0.5), nn.Linear(192, NCLASSES))
109
+ def forward(self, x):
110
+ return self.fc(self.tempcnn(self.freqcnn(x).squeeze(2)))
111
+
112
+ class DeepResNet(nn.Module):
113
+ def __init__(self):
114
+ super().__init__()
115
+ self.stem = nn.Sequential(
116
+ nn.Conv2d(3, 32, 5, stride=2, padding=2, bias=False),
117
+ nn.BatchNorm2d(32), nn.ReLU(True), nn.MaxPool2d(2, 2))
118
+ self.stage1 = nn.Sequential(ResBlock(32, 32), ResBlock(32, 32))
119
+ self.stage2 = nn.Sequential(ResBlock(32, 48, stride=2), ResBlock(48, 48))
120
+ self.stage3 = nn.Sequential(ResBlock(48, 64, stride=2), ResBlock(64, 64))
121
+ self.head = nn.Sequential(
122
+ nn.AdaptiveAvgPool2d(1), nn.Flatten(),
123
+ nn.Dropout(0.4), nn.Linear(64, NCLASSES))
124
+ def forward(self, x):
125
+ return self.head(self.stage3(self.stage2(self.stage1(self.stem(x)))))
126
+
127
+ class TinyResNet(nn.Module):
128
+ def __init__(self):
129
+ super().__init__()
130
+ self.stem = nn.Sequential(
131
+ nn.Conv2d(3, 32, 5, stride=2, padding=2, bias=False),
132
+ nn.BatchNorm2d(32), nn.ReLU(True), nn.MaxPool2d(2, 2))
133
+ self.stage1 = nn.Sequential(ResBlock(32, 32), ResBlock(32, 32))
134
+ self.stage2 = nn.Sequential(ResBlock(32, 64, stride=2), ResBlock(64, 64))
135
+ self.head = nn.Sequential(
136
+ nn.AdaptiveAvgPool2d(1), nn.Flatten(),
137
+ nn.Dropout(0.5), nn.Linear(64, NCLASSES))
138
+ def forward(self, x):
139
+ return self.head(self.stage2(self.stage1(self.stem(x))))
140
+
141
+ class TinySENet(nn.Module):
142
+ def __init__(self):
143
+ super().__init__()
144
+ self.stem = nn.Sequential(
145
+ nn.Conv2d(3, 32, 5, stride=2, padding=2, bias=False),
146
+ nn.BatchNorm2d(32), nn.ReLU(True), nn.MaxPool2d(2, 2))
147
+ self.stage1 = nn.Sequential(SEResBlock(32, 32), SEResBlock(32, 32))
148
+ self.stage2 = nn.Sequential(SEResBlock(32, 64, stride=2), SEResBlock(64, 64))
149
+ self.head = nn.Sequential(
150
+ nn.AdaptiveAvgPool2d(1), nn.Flatten(),
151
+ nn.Dropout(0.5), nn.Linear(64, NCLASSES))
152
+ def forward(self, x):
153
+ return self.head(self.stage2(self.stage1(self.stem(x))))
154
+
155
+ # ── Load Models ──────────────────────────────────────────────────────
156
+ MODEL_DIR = os.path.dirname(os.path.abspath(__file__))
157
+ MODELS_INFO = [
158
+ ("FreqFirstNet", FreqFirstNet, "freqfirstnet_best.pth"),
159
+ ("FreqFirstNetV2", FreqFirstNetV2, "freqfirstnetv2_best.pth"),
160
+ ("DeepResNet", DeepResNet, "deepresnet_best.pth"),
161
+ ("TinyResNet", TinyResNet, "tinyresnet_best.pth"),
162
+ ("TinySENet", TinySENet, "tinysenet_best.pth"),
163
+ ]
164
+
165
+ models = []
166
+ for name, cls, ckpt in MODELS_INFO:
167
+ model = cls().to(device)
168
+ ckpt_path = os.path.join(MODEL_DIR, ckpt)
169
+ state = torch.load(ckpt_path, map_location=device, weights_only=True)
170
+ model.load_state_dict(state)
171
+ model.eval()
172
+ models.append(model)
173
+ print(f"Loaded {name}")
174
+ print("All 5 models loaded.")
175
+
176
+ # ── Feature Extraction ──────────────────────────────────────────────
177
+
178
+ def extract3ch(audio, rate=SR):
179
+ mel = librosa.feature.melspectrogram(
180
+ y=audio, sr=rate, n_mels=NMELS, n_fft=NFFT, hop_length=HOP)
181
+ meldb = librosa.power_to_db(mel, ref=np.max)
182
+ if meldb.shape[1] != MAXFRAMES:
183
+ meldb = scipyzoom(meldb, (1, MAXFRAMES / meldb.shape[1]), order=1)
184
+ if meldb.shape[1] > MAXFRAMES:
185
+ meldb = meldb[:, :MAXFRAMES]
186
+ elif meldb.shape[1] < MAXFRAMES:
187
+ meldb = np.pad(meldb, ((0, 0), (0, MAXFRAMES - meldb.shape[1])),
188
+ constant_values=meldb.min())
189
+ d1 = librosa.feature.delta(meldb, order=1)
190
+ d2 = librosa.feature.delta(meldb, order=2)
191
+ return np.stack([meldb, d1, d2], axis=0).astype(np.float32)
192
+
193
+ # ── Prediction Function ─────────────────────────────────────────────
194
+
195
+ def predict(audio_input):
196
+ if audio_input is None:
197
+ return {g: 0.0 for g in GENRES}
198
+
199
+ audio, _ = librosa.load(audio_input, sr=SR)
200
+ spec = extract3ch(audio)
201
+
202
+ spec_t = torch.FloatTensor(spec)
203
+ for c in range(spec_t.shape[0]):
204
+ mu = spec_t[c].mean()
205
+ sg = spec_t[c].std() + 1e-8
206
+ spec_t[c] = (spec_t[c] - mu) / sg
207
+ spec_t = spec_t.unsqueeze(0)
208
+
209
+ logit_list = []
210
+ with torch.no_grad():
211
+ for model in models:
212
+ logit_list.append(model(spec_t))
213
+
214
+ stacked = torch.stack(logit_list, 0)
215
+ ensemble = (stacked * ENS_WEIGHTS).sum(0)
216
+ probs = torch.softmax(ensemble, dim=1)[0]
217
+
218
+ return {genre: float(prob) for genre, prob in zip(GENRES, probs)}
219
+
220
+ # ── Gradio Interface ─────────────────────────────────────────────────
221
+
222
+ demo = gr.Interface(
223
+ fn=predict,
224
+ inputs=gr.Audio(type="filepath", label="Upload Audio File (.wav, .mp3, etc.)"),
225
+ outputs=gr.Label(num_top_classes=10, label="Genre Prediction"),
226
+ title="Music Genre Classifier",
227
+ description=(
228
+ "Upload an audio file to classify its music genre using a "
229
+ "**5-model ensemble** (FreqFirstNet, FreqFirstNetV2, DeepResNet, "
230
+ "TinyResNet, TinySENet).\n\n"
231
+ "**Features:** 3-channel mel spectrogram (mel + delta + delta-squared)\n\n"
232
+ "**Genres:** blues, classical, country, disco, hiphop, jazz, metal, "
233
+ "pop, reggae, rock\n\n"
234
+ "**Public F1 Score:** 0.801\n\n"
235
+ "Use the **API** tab at the bottom of this page for programmatic access."
236
+ ),
237
+ api_name="predict",
238
+ )
239
+
240
+ if __name__ == "__main__":
241
+ demo.launch()
deepresnet_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:601a4e5f665ada7b9a2b82c88ad2c9d464e967405967b1afddcc11bdc485245a
3
+ size 1077855
freqfirstnet_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43213a886675241a5a0ccffe23859ffaa365c101ba9b24622f1c761e9348642f
3
+ size 365973
freqfirstnetv2_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bd78f9f4bbb4e93659e301a37f2a352de94c7c5f906aa12887d5d11d97ed2d86
3
+ size 581333
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cpu
2
+ torch
3
+ librosa
4
+ scipy
5
+ numpy
6
+ soundfile
tinyresnet_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e9650f15cb4f9140469e8bfb41800767264dc0213f564c81da74263ad9370ee3
3
+ size 712841
tinysenet_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c905cd73a0abb937354db3346b86062bc541b53b452a15cf451218456b7f200
3
+ size 740229