Audio Classification
Transformers
Safetensors
English
Vietnamese
smad_crnn
feature-extraction
audio
music
speech
singing-voice-detection
speech-music-discrimination
custom-code
custom_code
Eval Results (legacy)
Instructions to use duclvQ/smad with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use duclvQ/smad with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("audio-classification", model="duclvQ/smad", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("duclvQ/smad", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add Transformers load-by-id model files
Browse files- README.md +61 -0
- config.json +38 -0
- configuration_smad.py +46 -0
- feature_extraction_smad.py +66 -0
- model.safetensors +3 -0
- modeling_smad.py +110 -0
- preprocessor_config.json +12 -0
README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: transformers
|
| 3 |
+
pipeline_tag: audio-classification
|
| 4 |
+
tags:
|
| 5 |
+
- audio
|
| 6 |
+
- music
|
| 7 |
+
- speech
|
| 8 |
+
- custom-code
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# SMAD CRNN
|
| 13 |
+
|
| 14 |
+
SMAD classifies a 4-second mono audio clip into one of four labels:
|
| 15 |
+
`speech_noise`, `speech_music`, `singing_music`, or `none`.
|
| 16 |
+
|
| 17 |
+
The model is a small CNN + BiGRU trained from scratch on 80-bin log-mel
|
| 18 |
+
spectrograms. It has 834k parameters and reached 87.95% on the synthetic
|
| 19 |
+
held-out test split used by the project.
|
| 20 |
+
|
| 21 |
+
## Load by model id
|
| 22 |
+
|
| 23 |
+
```python
|
| 24 |
+
import torch
|
| 25 |
+
from transformers import AutoFeatureExtractor, AutoModelForAudioClassification
|
| 26 |
+
|
| 27 |
+
model_id = "duclvQ/smad"
|
| 28 |
+
|
| 29 |
+
feature_extractor = AutoFeatureExtractor.from_pretrained(
|
| 30 |
+
model_id,
|
| 31 |
+
trust_remote_code=True,
|
| 32 |
+
)
|
| 33 |
+
model = AutoModelForAudioClassification.from_pretrained(
|
| 34 |
+
model_id,
|
| 35 |
+
trust_remote_code=True,
|
| 36 |
+
).eval()
|
| 37 |
+
|
| 38 |
+
# `audio` must be mono float32 audio sampled at 16 kHz. For longer files, run
|
| 39 |
+
# this over 4-second windows.
|
| 40 |
+
inputs = feature_extractor(audio, sampling_rate=16000, return_tensors="pt")
|
| 41 |
+
with torch.no_grad():
|
| 42 |
+
logits = model(**inputs).logits
|
| 43 |
+
probs = torch.softmax(logits / model.config.temperature, dim=-1)
|
| 44 |
+
|
| 45 |
+
label_id = int(probs.argmax(-1)[0])
|
| 46 |
+
label = model.config.id2label[label_id]
|
| 47 |
+
confidence = float(probs[0, label_id])
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
For arbitrary file paths, load/resample with `librosa`:
|
| 51 |
+
|
| 52 |
+
```python
|
| 53 |
+
import librosa
|
| 54 |
+
|
| 55 |
+
audio, _ = librosa.load("clip.mp3", sr=16000, mono=True)
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## Caveat
|
| 59 |
+
|
| 60 |
+
The reported score is an upper bound on real-world performance: train and test
|
| 61 |
+
clips come from the same synthetic mixer, so they share its biases.
|
config.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"SmadForAudioClassification"
|
| 4 |
+
],
|
| 5 |
+
"auto_map": {
|
| 6 |
+
"AutoConfig": "configuration_smad.SmadConfig",
|
| 7 |
+
"AutoModel": "modeling_smad.SmadForAudioClassification",
|
| 8 |
+
"AutoModelForAudioClassification": "modeling_smad.SmadForAudioClassification"
|
| 9 |
+
},
|
| 10 |
+
"channels": [
|
| 11 |
+
32,
|
| 12 |
+
64,
|
| 13 |
+
128,
|
| 14 |
+
128
|
| 15 |
+
],
|
| 16 |
+
"dropout": 0.2,
|
| 17 |
+
"id2label": {
|
| 18 |
+
"0": "speech_noise",
|
| 19 |
+
"1": "speech_music",
|
| 20 |
+
"2": "singing_music",
|
| 21 |
+
"3": "none"
|
| 22 |
+
},
|
| 23 |
+
"label2id": {
|
| 24 |
+
"speech_noise": 0,
|
| 25 |
+
"speech_music": 1,
|
| 26 |
+
"singing_music": 2,
|
| 27 |
+
"none": 3
|
| 28 |
+
},
|
| 29 |
+
"model_type": "smad_crnn",
|
| 30 |
+
"n_fft": 400,
|
| 31 |
+
"num_labels": 4,
|
| 32 |
+
"num_mels": 80,
|
| 33 |
+
"rnn_hidden": 128,
|
| 34 |
+
"rnn_type": "gru",
|
| 35 |
+
"sample_rate": 16000,
|
| 36 |
+
"segment_seconds": 4.0,
|
| 37 |
+
"temperature": 0.709507268312107
|
| 38 |
+
}
|
configuration_smad.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import PretrainedConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class SmadConfig(PretrainedConfig):
|
| 5 |
+
model_type = "smad_crnn"
|
| 6 |
+
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
num_mels=80,
|
| 10 |
+
channels=(32, 64, 128, 128),
|
| 11 |
+
rnn_hidden=128,
|
| 12 |
+
dropout=0.2,
|
| 13 |
+
num_labels=4,
|
| 14 |
+
rnn_type="gru",
|
| 15 |
+
temperature=1.0,
|
| 16 |
+
sample_rate=16000,
|
| 17 |
+
segment_seconds=4.0,
|
| 18 |
+
n_fft=400,
|
| 19 |
+
hop_length=160,
|
| 20 |
+
label2id=None,
|
| 21 |
+
id2label=None,
|
| 22 |
+
**kwargs,
|
| 23 |
+
):
|
| 24 |
+
label2id = label2id or {
|
| 25 |
+
"speech_noise": 0,
|
| 26 |
+
"speech_music": 1,
|
| 27 |
+
"singing_music": 2,
|
| 28 |
+
"none": 3,
|
| 29 |
+
}
|
| 30 |
+
id2label = id2label or {str(v): k for k, v in label2id.items()}
|
| 31 |
+
super().__init__(
|
| 32 |
+
num_labels=num_labels,
|
| 33 |
+
label2id=label2id,
|
| 34 |
+
id2label=id2label,
|
| 35 |
+
**kwargs,
|
| 36 |
+
)
|
| 37 |
+
self.num_mels = num_mels
|
| 38 |
+
self.channels = list(channels)
|
| 39 |
+
self.rnn_hidden = rnn_hidden
|
| 40 |
+
self.dropout = dropout
|
| 41 |
+
self.rnn_type = rnn_type
|
| 42 |
+
self.temperature = temperature
|
| 43 |
+
self.sample_rate = sample_rate
|
| 44 |
+
self.segment_seconds = segment_seconds
|
| 45 |
+
self.n_fft = n_fft
|
| 46 |
+
self.hop_length = hop_length
|
feature_extraction_smad.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from transformers import BatchFeature, SequenceFeatureExtractor
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SmadFeatureExtractor(SequenceFeatureExtractor):
|
| 6 |
+
model_input_names = ["input_features"]
|
| 7 |
+
|
| 8 |
+
def __init__(
|
| 9 |
+
self,
|
| 10 |
+
feature_size=80,
|
| 11 |
+
sampling_rate=16000,
|
| 12 |
+
padding_value=0.0,
|
| 13 |
+
segment_seconds=4.0,
|
| 14 |
+
n_fft=400,
|
| 15 |
+
hop_length=160,
|
| 16 |
+
**kwargs,
|
| 17 |
+
):
|
| 18 |
+
super().__init__(
|
| 19 |
+
feature_size=feature_size,
|
| 20 |
+
sampling_rate=sampling_rate,
|
| 21 |
+
padding_value=padding_value,
|
| 22 |
+
**kwargs,
|
| 23 |
+
)
|
| 24 |
+
self.segment_seconds = segment_seconds
|
| 25 |
+
self.n_fft = n_fft
|
| 26 |
+
self.hop_length = hop_length
|
| 27 |
+
|
| 28 |
+
def waveform_to_mel(self, waveform, sampling_rate=None):
|
| 29 |
+
import librosa
|
| 30 |
+
|
| 31 |
+
sampling_rate = sampling_rate or self.sampling_rate
|
| 32 |
+
mel = librosa.feature.melspectrogram(
|
| 33 |
+
y=np.asarray(waveform, dtype=np.float32),
|
| 34 |
+
sr=sampling_rate,
|
| 35 |
+
n_fft=self.n_fft,
|
| 36 |
+
hop_length=self.hop_length,
|
| 37 |
+
n_mels=self.feature_size,
|
| 38 |
+
power=2.0,
|
| 39 |
+
)
|
| 40 |
+
return librosa.power_to_db(mel).T.astype(np.float32)
|
| 41 |
+
|
| 42 |
+
def __call__(self, raw_speech, sampling_rate=None, return_tensors=None, **kwargs):
|
| 43 |
+
sampling_rate = sampling_rate or self.sampling_rate
|
| 44 |
+
if sampling_rate != self.sampling_rate:
|
| 45 |
+
raise ValueError(
|
| 46 |
+
f"Expected {self.sampling_rate} Hz audio. Resample before calling "
|
| 47 |
+
f"the feature extractor; received {sampling_rate} Hz."
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
if isinstance(raw_speech, np.ndarray) and raw_speech.ndim == 1:
|
| 51 |
+
waves = [raw_speech]
|
| 52 |
+
else:
|
| 53 |
+
waves = [np.asarray(w, dtype=np.float32) for w in raw_speech]
|
| 54 |
+
|
| 55 |
+
target_len = int(round(self.segment_seconds * self.sampling_rate))
|
| 56 |
+
features = []
|
| 57 |
+
for wave in waves:
|
| 58 |
+
if wave.ndim != 1:
|
| 59 |
+
raise ValueError("Expected mono audio arrays with shape `(samples,)`.")
|
| 60 |
+
if wave.shape[0] < target_len:
|
| 61 |
+
wave = np.pad(wave, (0, target_len - wave.shape[0]))
|
| 62 |
+
elif wave.shape[0] > target_len:
|
| 63 |
+
wave = wave[:target_len]
|
| 64 |
+
features.append(self.waveform_to_mel(wave, sampling_rate=sampling_rate))
|
| 65 |
+
|
| 66 |
+
return BatchFeature({"input_features": features}, tensor_type=return_tensors)
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:63818291e401ca27616cb29ba3a224b780296ac4af87e6d6b70edb9daa40f5bc
|
| 3 |
+
size 3342832
|
modeling_smad.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch import nn
|
| 3 |
+
from transformers import PreTrainedModel
|
| 4 |
+
from transformers.modeling_outputs import SequenceClassifierOutput
|
| 5 |
+
|
| 6 |
+
from .configuration_smad import SmadConfig
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class ConvBlock(nn.Module):
|
| 10 |
+
def __init__(self, in_ch, out_ch, pool):
|
| 11 |
+
super().__init__()
|
| 12 |
+
self.block = nn.Sequential(
|
| 13 |
+
nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1, bias=False),
|
| 14 |
+
nn.BatchNorm2d(out_ch),
|
| 15 |
+
nn.ReLU(inplace=True),
|
| 16 |
+
nn.MaxPool2d(pool),
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
def forward(self, x):
|
| 20 |
+
return self.block(x)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TinyAudioCRNN(nn.Module):
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
n_mels=80,
|
| 27 |
+
channels=(32, 64, 128, 128),
|
| 28 |
+
rnn_hidden=128,
|
| 29 |
+
dropout=0.2,
|
| 30 |
+
num_classes=4,
|
| 31 |
+
rnn_type="gru",
|
| 32 |
+
):
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.register_buffer("feat_mean", torch.zeros(n_mels))
|
| 35 |
+
self.register_buffer("feat_std", torch.ones(n_mels))
|
| 36 |
+
|
| 37 |
+
pools = [(2, 2)] * (len(channels) - 1) + [(2, 1)]
|
| 38 |
+
blocks, in_ch = [], 1
|
| 39 |
+
for out_ch, pool in zip(channels, pools):
|
| 40 |
+
blocks.append(ConvBlock(in_ch, out_ch, pool))
|
| 41 |
+
in_ch = out_ch
|
| 42 |
+
self.conv = nn.Sequential(*blocks)
|
| 43 |
+
|
| 44 |
+
freq_out = n_mels
|
| 45 |
+
for freq_pool, _ in pools:
|
| 46 |
+
freq_out //= freq_pool
|
| 47 |
+
if freq_out < 1:
|
| 48 |
+
raise ValueError(f"{len(channels)} conv blocks pool {n_mels} mel bins down to nothing")
|
| 49 |
+
rnn_in = channels[-1] * freq_out
|
| 50 |
+
|
| 51 |
+
self.dropout = nn.Dropout(dropout)
|
| 52 |
+
rnn_cls = {"gru": nn.GRU, "lstm": nn.LSTM}[rnn_type.lower()]
|
| 53 |
+
self.rnn = rnn_cls(
|
| 54 |
+
rnn_in,
|
| 55 |
+
rnn_hidden,
|
| 56 |
+
num_layers=1,
|
| 57 |
+
batch_first=True,
|
| 58 |
+
bidirectional=True,
|
| 59 |
+
)
|
| 60 |
+
self.classifier = nn.Linear(rnn_hidden * 2 * 2, num_classes)
|
| 61 |
+
|
| 62 |
+
def forward(self, x):
|
| 63 |
+
x = (x - self.feat_mean) / self.feat_std
|
| 64 |
+
x = x.transpose(1, 2).unsqueeze(1)
|
| 65 |
+
x = self.conv(x)
|
| 66 |
+
b, c, f, t = x.shape
|
| 67 |
+
x = x.permute(0, 3, 1, 2).reshape(b, t, c * f)
|
| 68 |
+
x = self.dropout(x)
|
| 69 |
+
x, _ = self.rnn(x)
|
| 70 |
+
pooled = torch.cat([x.mean(dim=1), x.max(dim=1).values], dim=-1)
|
| 71 |
+
return self.classifier(self.dropout(pooled))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class SmadForAudioClassification(PreTrainedModel):
|
| 75 |
+
config_class = SmadConfig
|
| 76 |
+
base_model_prefix = "smad"
|
| 77 |
+
main_input_name = "input_features"
|
| 78 |
+
all_tied_weights_keys = {}
|
| 79 |
+
|
| 80 |
+
def __init__(self, config):
|
| 81 |
+
super().__init__(config)
|
| 82 |
+
self.smad = TinyAudioCRNN(
|
| 83 |
+
n_mels=config.num_mels,
|
| 84 |
+
channels=tuple(config.channels),
|
| 85 |
+
rnn_hidden=config.rnn_hidden,
|
| 86 |
+
dropout=config.dropout,
|
| 87 |
+
num_classes=config.num_labels,
|
| 88 |
+
rnn_type=config.rnn_type,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
def forward(self, input_features=None, labels=None, return_dict=None, **kwargs):
|
| 92 |
+
if input_features is None:
|
| 93 |
+
input_features = kwargs.pop("inputs", None)
|
| 94 |
+
if input_features is None:
|
| 95 |
+
raise ValueError("Pass log-mel features as `input_features`.")
|
| 96 |
+
|
| 97 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 98 |
+
logits = self.smad(input_features)
|
| 99 |
+
loss = None
|
| 100 |
+
if labels is not None:
|
| 101 |
+
loss = nn.functional.cross_entropy(logits, labels)
|
| 102 |
+
if not return_dict:
|
| 103 |
+
output = (logits,)
|
| 104 |
+
return ((loss,) + output) if loss is not None else output
|
| 105 |
+
return SequenceClassifierOutput(loss=loss, logits=logits)
|
| 106 |
+
|
| 107 |
+
@torch.no_grad()
|
| 108 |
+
def predict_proba(self, input_features):
|
| 109 |
+
logits = self(input_features=input_features).logits
|
| 110 |
+
return torch.softmax(logits / float(self.config.temperature), dim=-1)
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoFeatureExtractor": "feature_extraction_smad.SmadFeatureExtractor"
|
| 4 |
+
},
|
| 5 |
+
"feature_extractor_type": "SmadFeatureExtractor",
|
| 6 |
+
"feature_size": 80,
|
| 7 |
+
"hop_length": 160,
|
| 8 |
+
"n_fft": 400,
|
| 9 |
+
"padding_value": 0.0,
|
| 10 |
+
"sampling_rate": 16000,
|
| 11 |
+
"segment_seconds": 4.0
|
| 12 |
+
}
|