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
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 ---
…