Music Auto-Tagging CNN Model Comparison

By Fouzil Ali · Published July 20, 2026

Compares four CNN architectures (FCN, CRNN, SampleCNN, ShortChunkCNN_Res) for music auto-tagging on MagnaTagATune dataset using mel-spectrograms and raw waveforms.

  • music-tagging
  • cnn
  • audio-classification
  • deep-learning
  • benchmark
13 cells4 experiments19 views0 forks

Inside this notebook

# Music Auto-Tagging: CNN Model Comparison on MagnaTagATune This notebook reproduces and compares four CNN-based music auto-tagging models using the **minzwon/sota-music-tagging-models** benchmark as the reference implementation. The models are trained on the MagnaTagATune dataset (top-50 tags) and evaluated with mean ROC-AUC and PR-AUC on a held-out test split. **Models compared:** - **FCN** (Choi et al. 2016) — fully-convolutional baseline, 5-layer Conv2d on mel-spectrograms - **CRNN** (Choi et al. 2017) — CNN + GRU recurrent temporal summarization on mel-spectrograms - **SampleCNN** (Lee et al. 2017) — raw-waveform 1-D Conv, end-to-end learning - **ShortChunkCNN_Res** (Won et al. 2020) — VGG-style residual Conv2d on short mel-spectrogram chunks **Workflow:** Baseline branch → EDA → cache preprocessed audio → fork 4 parallel training branches → converge into comparison. **Tractability note:** Uses ~3000 training clips and 15 epochs per model. Full-scale benchmarks use all 15K training clips × 200 epochs.

# ⚙️ Imports & Configuration
import os, sys, math, time, random, shutil, zipfile, subprocess
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
from tqdm.auto import tqdm

import torch
import torch.nn as nn
import torch.nn.functional as F
import torchaudio

# librosa install
!pip install -q librosa
import librosa

…
Device: cuda
GPU: NVIDIA A10
VRAM: 23.7 GB
Data root: /home/user/data
PyTorch version: 2.7.1+cu128
Torchaudio version: 2.7.1+cu128
librosa version: 0.11.0
# 📥 Download MTAT Annotations + Benchmark Split Files
import requests

print("Downloading benchmark split files and annotations...")

# 1) Clone the benchmark repo
REPO_PATH = BASE_DIR / "sota-music-tagging-models"
if not REPO_PATH.exists():
    !git clone --depth 1 https://github.com/minzwon/sota-music-tagging-models.git {REPO_PATH}
else:
    print("Repo already cloned.")

# Verify split files exist
split_files = list(SPLIT_DIR.glob("*.npy"))
print(f"Split files found: {[f.name for f in split_files]}")

# 2) Download annotations_final.csv from MIRG
ann_path = MTAT_DIR / "annotations_final.csv"
…
Downloading benchmark split files and annotations...
Repo already cloned.
Split files found: ['binary.npy', 'tags.npy', 'test.npy', 'train.npy', 'valid.npy']
Downloading annotations_final.csv...
Downloaded 21.5 MB

Annotations shape: (25863, 190)
Columns: ['clip_id', 'no voice', 'singer', 'duet', 'plucking', 'hard rock', 'world', 'bongos', 'harpsichord', 'female singing']... (190 total, many are tag names)
Sample rows:
   clip_id  no voice  singer  duet  plucking  hard rock  world  bongos  \
0…
# 📦 Download MTAT MP3 Audio Files
# 3 zip parts totalling ~2.7 GB from MIRG
# Using curl -C - to resume partial downloads
import subprocess

mp3_parts = [
    ("mp3.zip.001", "https://mirg.city.ac.uk/datasets/magnatagatune/mp3.zip.001"),
    ("mp3.zip.002", "https://mirg.city.ac.uk/datasets/magnatagatune/mp3.zip.002"),
    ("mp3.zip.003", "https://mirg.city.ac.uk/datasets/magnatagatune/mp3.zip.003"),
]

zip_dir = MTAT_DIR / "zip"
zip_dir.mkdir(parents=True, exist_ok=True)

expected_sizes = {
    "mp3.zip.001": 1_000_000_000,
    "mp3.zip.002": 1_000_000_000,
    "mp3.zip.003": 737_000_000,
…
✓ mp3.zip.001 already complete (1100 MB)
↻ Resuming mp3.zip.002 (735 MB already downloaded)...
  → 1100 MB
↻ Resuming mp3.zip.003 (495 MB already downloaded)...
  → 773 MB

All zip parts in /home/user/data/mtat/zip:
total 2.8G
-rw-r--r-- 1 root root 1.1G Jul 20 14:58 mp3.zip.001
-rw-r--r-- 1 root root 1.1G Jul 20 15:20 mp3.zip.002
-rw-r--r-- 1 root root 737M Jul 20 15:20 mp3.zip.003
# 📂 Extract MTAT MP3 files from multi-part zip
# Combine parts and extract into mp3/ directory
import zipfile

zip_dir = MTAT_DIR / "zip"
mp3_dir = MTAT_DIR / "mp3"
mp3_dir.mkdir(parents=True, exist_ok=True)

# Check if already extracted
existing_mp3s = list(mp3_dir.glob("**/*.mp3"))
if len(existing_mp3s) > 5000:
    print(f"Already extracted ({len(existing_mp3s)} mp3 files). Skipping.")
else:
    # Concatenate parts into single zip and extract
    combined_zip = zip_dir / "mp3.zip"
    if not combined_zip.exists():
        print("Combining zip parts...")
        with open(combined_zip, 'wb') as out:
…
Combining zip parts...
  Appending mp3.zip.001 (1100 MB)...
  Appending mp3.zip.002 (1100 MB)...
  Appending mp3.zip.003 (773 MB)...
Combined zip: 2.97 GB
Extracting mp3 files...
Extracted 25863 mp3 files to /home/user/data/mtat/mp3
# 📊 EDA: Top-50 Tag Distribution, Class Imbalance & Clip Lengths
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
from pathlib import Path

# Load annotations (tab-separated!)
ann = pd.read_csv(MTAT_DIR / "annotations_final.csv", sep='\t')
print(f"Annotations shape: {ann.shape}")
print(f"Columns: {list(ann.columns[:10])}... ({ann.shape[1]} total)")
print(f"Total clips: {ann['clip_id'].nunique()}")

# Build binary matrix from benchmark split
binary = np.load(SPLIT_DIR / "binary.npy")
tags = np.load(SPLIT_DIR / "tags.npy", allow_pickle=True)
train_idx = np.load(SPLIT_DIR / "train.npy", allow_pickle=True)
valid_idx = np.load(SPLIT_DIR / "valid.npy", allow_pickle=True)
…
Annotations shape: (25863, 190)
Columns: ['clip_id', 'no voice', 'singer', 'duet', 'plucking', 'hard rock', 'world', 'bongos', 'harpsichord', 'female singing']... (190 total)
Total clips: 25863

Top-50 tags: [np.str_('guitar'), np.str_('classical'), np.str_('slow'), np.str_('techno'), np.str_('strings'), np.str_('drums'), np.str_('electronic'), np.str_('rock'), np.str_('fast'), np.str_('piano'), np.str_('ambient'), np.str_('beat'), np.str_('violin'), np.str_('vocal'), np.str_('synth'), np.str_('…
# 🔄 Preprocess Audio: MP3 → .npy (subset for tractability)
# Caches ~4000 clips as 16kHz mono float32 waveforms
# Naming matches the benchmark's expected format: <basename>.npy (no dir prefix)
import librosa, glob, time
from tqdm.auto import tqdm

FS = 16000          # target sample rate (benchmark standard)

mp3_dir = MTAT_DIR / "mp3"
npy_dir = MTAT_DIR / "npy"
npy_dir.mkdir(parents=True, exist_ok=True)

# Load full splits
train_all = np.load(SPLIT_DIR / "train.npy", allow_pickle=True)
valid_all = np.load(SPLIT_DIR / "valid.npy", allow_pickle=True)
test_all  = np.load(SPLIT_DIR / "test.npy", allow_pickle=True)

# --- Subset to tractable size ---
…