Classifies audio from the MUSAN dataset into music / noise / speech using a CNN trained on Mel spectrograms — reaching 98 % validation accuracy.
Three steps from raw audio to a working classifier:
Step 1 — Feature extraction
.wav→ Mel spectrogram
Each audio file is sliced into 3-second chunks. An STFT computes a 2D time × frequency representation, a Mel filterbank compresses it to 128 perceptually-spaced bands, and power-to-dB scaling gives a compact(128 × 94)float32 image per chunk.
Step 2 — Training a CNN
A 4-block CNN (MelCNN) treats each Mel spectrogram as a single-channel image and learns to classify it. Weighted cross-entropy handles class imbalance;ReduceLROnPlateauadapts the learning rate automatically.
Step 3 — Evaluation (three modes)
Script What it does evaluation/eval_model.pyHeld-out evaluation → confusion matrix + per-class confidence analysis evaluation/inspect_model.pyInteractive layer-by-layer viewer: watch each ConvBlock transform the input evaluation/live_classify.pyReal-time microphone classification — new prediction every second
musan.tar.gz
│
▼
training/prepare_musan.py → musan/ + musan_index.json
│
▼
training/build_dataset.py → training_data/
│
▼
training/train.py → best_model.pt + plots/training_curves.png
│
▼
evaluation/eval_model.py → plots/confusion_matrix.png
evaluation/inspect_model.py → interactive layer viewer
evaluation/live_classify.py → real-time mic classification
- Download the archive (~10 GB) from the official mirror:
https://www.openslr.org/17/ - Place
musan.tar.gzin the project root (next totraining/prepare_musan.py). - Run
training/prepare_musan.pyonce — it extracts the archive and createsmusan_index.json.
The dataset folder and the archive are excluded from version control via
.gitignore.
Run once. Extracts the MUSAN archive and builds a JSON index of all .wav files.
python training/prepare_musan.pyPrerequisite: musan.tar.gz must be present in the project folder.
Output:
musan/— extracted dataset directorymusan_index.json— list of all.wavfile paths
Converts the entire MUSAN dataset into Mel spectrograms and saves them as NumPy arrays.
python training/build_dataset.pyWhat it does:
- Each audio file is split into 3-second chunks (no overlap between chunks; any remainder shorter than 3 s is discarded)
- For each chunk a Mel spectrogram is computed (STFT with 50 % window overlap, Mel filterbank, dB scaling)
- Each spectrogram is saved as a
float32array of shape (128 × 94) - The class label is derived automatically from the file path
Parameters:
| Parameter | Value | Description |
|---|---|---|
SAMPLE_RATE |
16 000 Hz | Audio sample rate |
CHUNK_SEC |
3.0 s | Length of one training example |
N_MELS |
128 | Number of Mel frequency bands |
N_FFT |
1024 | FFT window length |
HOP_LENGTH |
512 | Step size (= 50 % overlap) |
FMAX |
8 000 Hz | Upper frequency cutoff |
Output — training_data/:
training_data/
music/ music_0000000.npy …
noise/ noise_0000000.npy …
speech/ speech_0000000.npy …
metadata.csv
metadata.csv contains for each .npy file: path, class name, label ID.
| Class | Label ID |
|---|---|
| music | 0 |
| noise | 1 |
| speech | 2 |
Note on train/test split: Chunks from the same source file should always be assigned to the same partition (train or test) to avoid data leakage.
Trains a CNN classifier (MelCNN) on the generated .npy dataset.
python training/train.pyArchitecture — MelCNN:
Input (1 × 128 × 94)
│
├─ ConvBlock 1: Conv2d → BatchNorm → ReLU → MaxPool → (32 × 64 × 47)
├─ ConvBlock 2: → (64 × 32 × 23)
├─ ConvBlock 3: → (128 × 16 × 11)
├─ ConvBlock 4: → (256 × 8 × 5)
│
├─ AdaptiveAvgPool2d(1,1) → (256 × 1 × 1)
└─ FC: 256 → 128 → 3 (with Dropout 0.5)
Training parameters:
| Parameter | Value | Description |
|---|---|---|
EPOCHS |
50 | Number of full passes through the training data |
BATCH_SIZE |
64 | Samples per gradient update |
LR |
1e-3 | Initial learning rate (Adam) |
VAL_SPLIT |
0.15 | Fraction of data held out for validation |
Class imbalance is handled via weighted cross-entropy loss (inverse-frequency weights).
The learning rate is halved automatically if validation loss does not improve for 3 epochs (ReduceLROnPlateau).
Output:
best_model.pt— model weights with lowest validation losstraining_curves.png— Loss and Accuracy plots over all epochs
Evaluates the trained model on files that were held out during training.
python evaluation/eval_model.pyWhat it does:
- Loads source files not seen during training (reads
train_val_split.json) - For each file: splits into 3-second segments, runs inference, takes the majority-vote prediction
- Prints per-class accuracy and average softmax confidence (correct vs. wrong predictions)
Output — plots/confusion_matrix.png:
Interactive layer-by-layer visualisation of the trained CNN.
python evaluation/inspect_model.pyWhat it does:
Loads a set of validation samples, runs a forward pass with activation hooks, and opens an interactive matplotlib window. Slide through layers and samples to watch each ConvBlock transform the input spectrogram into progressively abstract feature maps.
Controls:
| Key | Action |
|---|---|
← / → |
Switch layer (Input Mel → ConvBlock 1 → … → ConvBlock 4) |
↑ / ↓ |
Switch sample |
| Sliders | Same as arrow keys, mouse-controlled |
Classifies microphone input in real time.
python evaluation/live_classify.pyStop with Ctrl+C.
How it works:
- A ring buffer continuously captures audio from the default microphone at 16 kHz
- Every second, the most recent 3-second window is classified by
MelCNN - Softmax probabilities are smoothed over the last 3 predictions and rendered as ASCII bars:
Live Classification (Ctrl+C to quit)
──────────────────────────────────────────
music [████████████░░░░░░░░] 62% ◄
noise [░░░░░░░░░░░░░░░░░░░░] 3%
speech [██████░░░░░░░░░░░░░░] 35%
A regular Fourier Transform analyses the entire signal at once and tells you which frequencies are present — but not when. The STFT solves this by cutting the signal into short overlapping windows and computing a separate FFT for each one, producing a 2D time × frequency representation.
Each window is N_FFT = 1024 samples long, but the next window starts only HOP_LENGTH = 512 samples later — so consecutive windows overlap by half. This is necessary because a windowing function (e.g. Hann window) tapers the signal to zero at the edges; without overlap, information at those edges would be lost.
The FFT produces frequency bins on a linear scale. Human hearing, however, is logarithmic — we perceive fine differences at low frequencies but not at high ones. The Mel scale models this:
A Mel filterbank is a set of triangular bandpass filters spaced evenly on this scale. With N_MELS = 128, the ~513 FFT bins are compressed to 128 values in a perceptually meaningful way — more compact and more relevant for the CNN than raw FFT bins.
Signal → [Windowing + STFT] → linear spectrogram
│
[Mel filterbank]
│
Mel spectrogram (128 bands)
│
[power_to_db]
│
dB Mel spectrogram → .npy
pip install librosa numpy tqdm matplotlib
pip install torch # CPU-only
# On a CUDA cluster, match the PyTorch build to the driver version:
# conda install pytorch torchvision torchaudio pytorch-cuda=12.1 -c pytorch -c nvidiaThe model reaches 97 % validation accuracy after 50 epochs on the MUSAN dataset.

