Kong, Cao, Iqbal, Wang, Wang, Plumbley (CVSSP, University of Surrey) — IEEE/ACM TASLP 2020

PANNs: Pretrained Audio Neural Networks

The "ImageNet moment" for audio. A family of CNNs trained on AudioSet's 1.9M weakly-labelled YouTube clips that learns one general-purpose audio embedding — CNN14 reaches mAP 0.439, and the Wavegram-Logmel hybrid learns its own frequency front end straight from the waveform.

Prerequisites: Convolution & CNNs + Softmax / sigmoid + What a spectrogram is. We build the rest from zero.
10
Chapters
9+
Simulations
2
Code Labs

Chapter 0: The Audio ImageNet Gap

You're building an app that flags the sound of a smoke alarm, a baby crying, or breaking glass anywhere in a house. You have maybe 2,000 labelled clips of each — a few hours of audio. You train a small CNN on your log-mel spectrograms and it works on your validation set, then falls apart the moment a real kitchen extractor fan or a TV in the background enters the mic. Your model never heard enough of the world's sounds to know what a smoke alarm is not.

In computer vision, nobody trains an object detector from 2,000 images anymore. They download a ResNet that was pretrained on ImageNet's 1.2M images, lop off its classification head, and fine-tune. The pretrained backbone has already learned edges, textures, and parts — a reusable visual vocabulary. That is the "ImageNet moment," and by 2018 vision had it and audio did not.

The reason audio lagged was data. There was no labelled audio corpus at ImageNet scale, and even when Google released AudioSet (2.1M ten-second YouTube clips, 527 sound classes) in 2017, training a strong general model on it was an open engineering problem. Most groups trained on the released embeddings (precomputed by a frozen VGG-ish network) rather than the raw audio, which capped how good the features could get.

PANNs — Pretrained Audio Neural Networks — closed that gap. Kong et al. trained a family of CNNs end-to-end on AudioSet's raw waveforms, systematically compared architectures, and shipped the weights. The best single model, CNN14, reached a mean average precision (mAP) of 0.439 on AudioSet tagging — a large jump over the previous best — and, crucially, transferred to six downstream tasks where it set new states of the art. PANNs became to audio what ImageNet-pretrained ResNets are to vision: the default backbone you reach for.

Scratch vs Pretrained: the data wall

Schematic downstream accuracy as a function of how many labelled clips you have. A model trained from scratch needs a lot of data to do anything. A PANN, pretrained on 1.9M AudioSet clips, starts high even with a few hundred labels. Drag to change the downstream dataset size.

Downstream labels ~500

The contribution is not a single clever trick. It's a careful, large-scale study answering three engineering questions audio researchers actually had:

QuestionPANNs answer
What input — waveform or spectrogram?Log-mel wins overall; a learned Wavegram added to log-mel wins by a hair more
What architecture scales best on AudioSet?A VGG-style 14-layer CNN (CNN14) with global pooling — depth and width matter, but with diminishing returns
Do these features transfer?Yes — fine-tuning CNN14 set new SOTA on ESC-50, DCASE, GTZAN, and more
The bet PANNs makes: the same backbone that learns to recognise 527 coarse YouTube sound classes from messy, weak labels has, in the process, learned a general notion of "what sounds are made of." If that's true, the embedding it produces should be a good starting point for any audio task — even tasks with classes AudioSet never contained. The whole paper is the experiment that confirms this.

We'll build PANNs the way you'd implement it: first the front end that turns a waveform into a picture (the log-mel spectrogram), then the CNN14 backbone that processes that picture, then the audio-tagging head — a multi-label sigmoid with clipwise aggregation — that is the part you most often reuse. Then the Wavegram twist, the results, and how to transfer it. Let's start with the data that made it possible.

A note on the word "tagging." Audio tagging means: given a clip, output which of the 527 classes are present anywhere in it. It is multi-label (a clip can be "Speech" AND "Dog" AND "Music" at once) and clip-level (you don't have to say when each sound happened). That last distinction — clip vs frame — drives the entire head design in Chapter 4.

What core problem in audio learning did PANNs address, and how?

Chapter 1: AudioSet & Weak Labels

Everything in PANNs is downstream of one dataset, so we have to understand its shape. AudioSet is ~2.1M ten-second clips drawn from YouTube, each tagged with one or more of 527 sound classes organised in an ontology (Speech, Music, Animal → Dog → Bark, Vehicle → Car, Engine, Glass, and so on). After excluding clips that had been removed from YouTube, the authors trained on roughly 1.9M clips.

Two properties of these labels shape the whole architecture:

Weak (clip-level)
A label says the sound occurs somewhere in the 10 s — not which frame. There are no time boundaries.
↓ implies
Multi-label
A clip can carry several tags at once. "Music + Speech + Applause" is one clip, not three.
↓ implies
Imbalanced
"Speech" and "Music" appear in hundreds of thousands of clips; rare classes have only a few hundred.

Why "weak" forces global pooling

Because labels are clip-level, the network must produce one prediction per clip, even though internally it sees a sequence of time frames. The standard fix — and the one PANNs uses — is to let the CNN produce a per-frame feature map and then pool over time to collapse it to a single clip vector. The label supervises only the pooled output. The network never gets told which frame contained the dog bark; it has to figure that out by getting the clip-level answer right. This is a multiple-instance learning setup, and the pooling operator is the heart of it (Chapter 4).

The audio resampling pipeline (real numbers)

Before any learning, every clip is normalised to a fixed shape so it can be batched. PANNs uses:

StageValueShape after
Resample32 kHz mono10 s → 320,000 samples
STFT window1024 samples (Hann), hop 320~1000 frames × 513 freq bins
Mel filterbank64 mel bands, 50 Hz–14 kHz~1000 × 64
Log + batch-normlog(mel + ε)[1, 1000, 64] log-mel "image"

So a clip becomes a single-channel image of shape roughly [1000, 64] — time down the rows, mel-frequency across the columns. That is exactly what a CNN wants. (Hop 320 at 32 kHz is 10 ms per frame, which is why ~10 s gives ~1000 frames.)

The AudioSet ontology, weakly labelled

A 10 s clip's true timeline (top) has events at specific moments. The weak label (bottom) erases the timing — it only records WHICH classes occurred. Click "New clip" to resample events; notice the label set is the same regardless of when things happen.

3 events → multi-label

Worked example: how big is the training matrix?

Take 1.9M clips, each [1000, 64] in log-mel space. That's 1.9e6 × 1000 × 64 ≈ 1.2e11 floats. At 4 bytes each that's ~486 GB if you precomputed and stored all spectrograms. PANNs instead computes the STFT and mel filterbank on the GPU, on the fly, from the raw 16-bit waveform (which is ~640 KB/clip, ~1.2 TB total). Storing waveforms and transforming live is both smaller and lets the mel front end be part of the network — which is what makes the learnable Wavegram in Chapter 5 possible.

Key insight: putting the spectrogram transform inside the model (as a fixed but differentiable layer) is not just an optimisation. It means gradients can flow through the front end. PANNs keeps the log-mel transform fixed, but the same plumbing lets the Wavegram branch learn a front end. The decision "is the front end in the data pipeline or in the network?" quietly enables a whole research direction.
Common misconception: people assume AudioSet gives you onset/offset times like a transcription. It does not. The labels are deliberately weak — clip-level only — because labelling 2M clips frame-by-frame is infeasible. Every architectural choice in PANNs (global pooling, the clipwise head, multiple-instance learning) exists to cope with that weakness. If you forget the labels are weak, the head design looks arbitrary.
AudioSet labels are "weak." What does that mean for the model design?

Chapter 2: The Log-Mel Front End

This is the layer that turns sound into a picture, and it's the component you most need to understand to use PANNs. We'll derive each step with real numbers.

Step 1: Short-Time Fourier Transform (STFT)

A raw waveform is just amplitude over time — 320,000 numbers for our clip. The ear, though, hears frequency. The STFT slides a window over the signal and computes a Fourier transform inside each window, giving "how much of each frequency is present in this 32 ms slice."

X[f, t] = ∑n x[n + t·H] · w[n] · e−j 2π f n / N

Here N = 1024 is the window length, H = 320 is the hop, and w is a Hann window (a smooth bell that tapers the edges so we don't get spectral leakage). With N = 1024 the FFT returns N/2 + 1 = 513 frequency bins per frame. With hop 320 over 320,000 samples we get ~1000 frames. We keep the power |X[f,t]|2 and throw away the phase.

Step 2: The mel filterbank

513 linear-frequency bins is too many and the wrong scale — human pitch perception is roughly logarithmic. The mel scale warps frequency so that equal mel steps sound like equal pitch steps. We compress the 513 power bins down to 64 mel bands using a fixed matrix of overlapping triangular filters M of shape [64, 513]:

mel[m, t] = ∑f M[m, f] · |X[f, t]|2

Each row M[m] is a triangle: zero everywhere except over a small band of FFT bins, rising to 1 at the band's centre and falling back to 0. Low mel bands are narrow (fine resolution in the bass, where the ear is picky) and high mel bands are wide (coarse in the treble). The mel-to-Hz mapping is:

mel(f) = 2595 · log10(1 + f / 700)

We place 64 + 2 mel points evenly in mel space between 50 Hz and 14 kHz, convert back to Hz, snap to the nearest FFT bins, and build a triangle between each triple of points. That triple-of-points construction is exactly what you'll implement in Code Lab 1.

Step 3: Log compression

Sound energy spans an enormous dynamic range — a whisper and a jet differ by a factor of ~1012 in power. A linear network would be swamped by the loud bins. We take the log:

L[m, t] = log(mel[m, t] + ε)

The small ε (e.g. 10−10) prevents log(0) = −∞ on silent bins. Log compression mirrors the ear's roughly logarithmic loudness perception (decibels) and turns multiplicative gain changes into additive shifts — so making the whole clip louder just adds a constant, which the following batch-norm absorbs.

Why this exact pipeline? STFT → mel → log is not arbitrary — each step injects a piece of human auditory science: time-localised frequency (STFT), perceptual pitch warping (mel), perceptual loudness (log). The network is handed an input that already lives in roughly the coordinates the ear uses, so it spends its capacity on patterns rather than rediscovering psychoacoustics.

Worked example: one mel band by hand

Suppose mel band m = 5 covers FFT bins 8, 9, 10, 11, 12 with triangle weights M[5] = [0, 0.5, 1.0, 0.5, 0] over those bins. Let the power at those bins in frame t be |X|2 = [4, 4, 9, 16, 1]. Then:

mel[5, t] = 0·4 + 0.5·4 + 1.0·9 + 0.5·16 + 0·1 = 2 + 9 + 8 = 19

And L[5, t] = log(19 + 10−10) ≈ 2.944. The centre bin (10) dominates because its weight is 1.0; the wings contribute half. Repeat for all 64 bands and all ~1000 frames and you have the log-mel image. That is the entire front end.

From waveform to log-mel

Top: a synthetic waveform (a chirp sweeping up in pitch plus a transient click). Middle: its power spectrogram (513 linear bins). Bottom: after the 64-band mel filterbank + log. Watch the rising chirp become a diagonal line and the click a vertical streak. Drag to sweep the chirp's speed.

Chirp rate 4
python
# The PANNs front end, conceptually (torchlibrosa does this on-GPU)
import torch, torchaudio

wav, sr = torchaudio.load("clip.wav")        # [1, 320000] at 32 kHz

spec = torchaudio.transforms.Spectrogram(
    n_fft=1024, hop_length=320, power=2.0)(wav)  # [1, 513, ~1000]

melfb = torchaudio.transforms.MelScale(
    n_mels=64, sample_rate=32000, n_stft=513,
    f_min=50, f_max=14000)                       # fixed [64, 513] matrix
mel = melfb(spec)                              # [1, 64, ~1000]

logmel = torch.log(mel + 1e-10)               # compress dynamic range
logmel = logmel.transpose(1, 2)               # [1, ~1000, 64]: time, mel
# a BatchNorm over the 64 mel bins follows, learned during training
Why does the mel filterbank use narrow triangles at low frequency and wide triangles at high frequency?

Chapter 3: The CNN14 Backbone

With the log-mel "image" [1, 1000, 64] in hand, we need a network to read it. PANNs evaluates a ladder of CNNs (CNN6, CNN10, CNN14) and a few variants; CNN14 is the workhorse. It is a deliberately plain VGG-style stack — the paper's point is partly that a clean, well-trained vanilla CNN beats the fancier prior approaches.

The convolutional block

CNN14 is built from six conv blocks. Each block is two 3×3 convolutions, each followed by batch-norm and ReLU, then a 2×2 average-pool that halves both time and frequency:

Conv 3×3 → BN → ReLU
Learns local time-frequency patterns (an onset, a harmonic, a formant)
Conv 3×3 → BN → ReLU
Composes them into richer features
AvgPool 2×2
Halves T and F; doubles the receptive field; controls cost

The channel widths grow as the maps shrink — the classic CNN bargain of trading spatial resolution for semantic depth:

BlockChannelsFeature map (T × F)
Input11000 × 64
Block 164500 × 32
Block 2128250 × 16
Block 3256125 × 8
Block 451262 × 4
Block 5102431 × 2
Block 6204815 × 1

After block 6 we have a [2048, 15, 1] feature map: 2048 channels, 15 time steps, 1 frequency. The frequency axis has been pooled away entirely — each channel now answers "how strongly is my learned pattern present at each of 15 time steps?"

Why 3×3 convolutions?

A 3×3 kernel looks at a tiny patch — 3 frames × 3 mel bands. Stacking two of them per block gives an effective 5×5 receptive field with fewer parameters and an extra nonlinearity. After six blocks of pooling, a single late-layer neuron sees the whole clip in time and the whole spectrum in frequency. Depth buys global context cheaply — the same reason VGG used stacks of 3×3 in vision.

Worked example: parameter count of one conv

Block 3's first conv maps 128 input channels to 256 output channels with a 3×3 kernel. Its weight tensor is [256, 128, 3, 3], so it has 256 × 128 × 9 = 294,912 weights plus 256 biases ≈ 295K parameters in that single layer. CNN14 totals ~81M parameters — most of them concentrated in the wide late blocks (block 6's convs alone hold tens of millions because 1024→2048 channels × 3×3 is huge). This is why the "make it deeper/wider" ablation hits diminishing returns: you pay quadratically in the late blocks.

CNN14 feature-map pyramid

Each conv block halves time and frequency while doubling channels. Click "Step block" to push the [1000×64] log-mel through CNN14 and watch the map shrink spatially and grow in depth, until frequency is fully pooled away at [2048, 15, 1].

Input: 1 × 1000 × 64
python
import torch.nn as nn

class ConvBlock(nn.Module):
    def __init__(self, c_in, c_out):
        super().__init__()
        self.conv1 = nn.Conv2d(c_in,  c_out, 3, padding=1, bias=False)
        self.conv2 = nn.Conv2d(c_out, c_out, 3, padding=1, bias=False)
        self.bn1, self.bn2 = nn.BatchNorm2d(c_out), nn.BatchNorm2d(c_out)

    def forward(self, x):
        x = torch.relu(self.bn1(self.conv1(x)))
        x = torch.relu(self.bn2(self.conv2(x)))
        return nn.functional.avg_pool2d(x, 2)   # halve T and F

# CNN14: 6 conv blocks, widths 64..2048
widths = [64, 128, 256, 512, 1024, 2048]
blocks = nn.ModuleList(
    [ConvBlock(c_in, c_out)
     for c_in, c_out in zip([1] + widths[:-1], widths)])
# x: [B, 1, 1000, 64] -> ... -> [B, 2048, 15, 1]
Common misconception: CNN14's "14" does NOT mean 14 conv blocks. It counts ~13 conv layers (two per block over six blocks ≈ 12, plus the head) — VGG-style numbering. There are six pooling stages, which is what sets the final temporal resolution. If you assume 14 blocks you'll halve the time axis far too many times and end up with a 1-frame map, breaking the temporal pooling the head depends on.
After CNN14's six conv blocks, the feature map is [2048, 15, 1]. What does the "1" in the frequency axis mean?

Chapter 4: The Tagging Head & Loss

This is the part of PANNs you reuse most often, and it's a direct consequence of the weak labels from Chapter 1. We have a [2048, 15, 1] map. We need a single 527-vector of probabilities, with weak supervision. Three operations get us there.

Step 1: Embedding (squeeze frequency, keep time)

The frequency axis is already 1. A 1×1 conv (or a linear layer) maps the 2048 channels to a 2048-d embedding per time step, giving a sequence of 15 frame-embeddings, each 2048-d. This sequence is the reusable representation — the thing you transfer to other tasks.

Step 2: Framewise logits → sigmoid

A linear layer W of shape [527, 2048] projects each frame-embedding to 527 logits, and a sigmoid (not softmax!) turns each logit into an independent probability:

pframe[c, t] = σ( Wc · et + bc )    σ(z) = 1 / (1 + e−z)

Why sigmoid? Softmax forces the 527 probabilities to sum to 1 — that's correct for mutually exclusive classes, but a clip can be "Speech" AND "Music" AND "Applause" simultaneously. Sigmoid treats each class as an independent yes/no, which is exactly what multi-label means. Each pframe[c, t] is "how strongly does class c fire at frame t?"

Step 3: Clipwise aggregation (pool over time)

The labels are clip-level, so we collapse the 15 frame-probabilities of each class into one clip-probability. PANNs' default is average pooling:

pclip[c] = (1/T) ∑t=1T pframe[c, t]

Average pooling says "a class is present to the degree it fires across the whole clip." Max pooling ("present if it fires in any frame") is an alternative, but tends to be noisy on weak labels. PANNs also explores attention pooling, where learned weights decide how much each frame contributes — a soft, learned interpolation between average and max. The clipwise vector pclip is the final 527-d prediction.

The loss: binary cross-entropy, summed over classes

Because each class is an independent Bernoulli, the loss is binary cross-entropy (BCE) on every class, summed (or averaged) over the 527:

L = − ∑c=1527 [ yc log pclip[c] + (1 − yc) log(1 − pclip[c]) ]

where yc ∈ {0, 1} is the weak label for class c. Note the gradient only ever touches the clip-level prediction — the network must learn on its own which frames to fire on to make pclip right, since no frame-level supervision exists. That is multiple-instance learning in one equation.

Worked example: BCE on three classes by hand

Suppose for one clip the true labels are y = [1, 0, 1] (Speech yes, Dog no, Music yes) and the clip predictions are p = [0.8, 0.3, 0.6]. Then:

L = −[ log 0.8 + log(1−0.3) + log 0.6 ] = −[ −0.223 + −0.357 + −0.511 ] = 1.091

If the model improves Music to p = 0.9, the Music term becomes −log 0.9 = 0.105 and L drops to 0.685. The loss rewards pushing present-class probabilities up and absent-class probabilities down — independently per class. There's no competition between Speech and Music, unlike softmax.

Framewise → clipwise: sigmoid + pooling

Three classes' framewise probabilities over 15 frames (top). The clipwise aggregation (bottom bars) pools each class over time. Toggle the pooling operator and watch how avg vs max changes the clip prediction. Drag to sharpen or flatten the frame patterns.

Sharpness 4
python
import torch, torch.nn as nn

class TaggingHead(nn.Module):
    def __init__(self, d=2048, n_classes=527):
        super().__init__()
        self.fc = nn.Linear(d, n_classes)

    def forward(self, feat):           # feat: [B, 2048, T]  (T=15)
        e = feat.transpose(1, 2)        # [B, T, 2048]
        frame_logits = self.fc(e)       # [B, T, 527]
        frame_prob  = torch.sigmoid(frame_logits)   # multi-label!
        clip_prob   = frame_prob.mean(dim=1)         # avg over time -> [B, 527]
        return clip_prob, frame_prob

# weak clip-level targets y in {0,1}^527
loss = nn.functional.binary_cross_entropy(clip_prob, y)
The reusable artifact: the 2048-d embedding (before the final 527-class linear layer) is what people mean by "the PANNs embedding." You keep the backbone + embedding, throw away the 527-way head, and bolt on a new linear layer for your task. Chapter 7 is exactly this move.
Why does the PANNs tagging head use a sigmoid + BCE per class instead of a softmax + categorical cross-entropy?

Chapter 5: Wavegram-Logmel-CNN

Log-mel is a fixed, hand-designed front end. It bakes in human psychoacoustics — which is mostly good, but it also throws away information (it discards phase, and the mel warping and band limits are guesses). What if the network could learn its own time-frequency representation directly from the raw waveform, and we used it alongside log-mel? That is the Wavegram-Logmel-CNN, the paper's best-performing model.

The Wavegram: a learned spectrogram from 1-D convs

The Wavegram replaces the STFT+mel pipeline with a small stack of 1-D convolutions over the raw waveform. The first conv has a large stride that downsamples the 320,000 samples to a frame rate matching the log-mel (~100 frames/s); subsequent 1-D convs deepen the per-frame features. The output channels are then reshaped to act as a "frequency" axis, producing a learned 2-D map of shape [channels, frames] — a spectrogram the network designed for itself.

Raw waveform [320000]
No transform applied yet
↓ strided Conv1d (downsample to frame rate)
1-D conv stack
Learns filters — effectively a learnable, possibly phase-aware filterbank
↓ reshape channels → freq axis
Wavegram [C, frames]
A learned 2-D time-frequency image, same shape role as log-mel

Fusing the two front ends

The Wavegram and the log-mel spectrogram are both [·, frames]-shaped images. PANNs concatenates them along the channel axis and feeds the stacked input into the same CNN14 backbone:

input = concat( Wavegram , LogMel ) → CNN14 → tagging head

So the backbone sees both a representation tuned to human hearing (log-mel) and one the data chose (Wavegram). The two are complementary: log-mel is a strong prior that stabilises early training, while the Wavegram can recover detail the mel warping discarded. Fused, they edge out either alone.

Why not Wavegram alone? Pure learned-from-waveform front ends were tried (and PANNs reports a Wavegram-CNN). They work, but log-mel is a remarkably good inductive bias — decades of psychoacoustics for free. Dropping it entirely makes the model relearn pitch perception from scratch, which wastes capacity. The winning recipe keeps the prior and adds the learnable branch. Concept + realization: the choice "augment a strong prior" beats both "trust the prior blindly" and "throw the prior away."

Worked example: the downsampling stride

To match log-mel's ~10 ms hop (hop 320 at 32 kHz → 100 frames/s), the Wavegram's first Conv1d needs an effective stride that turns 320,000 samples into ~1000 frames — i.e. a total stride of ~320. PANNs achieves this with a strided first conv plus pooling (e.g. stride 4 conv, then more downsampling), so the Wavegram's time axis lines up frame-for-frame with the log-mel's. Without that alignment you couldn't concatenate them — the frames wouldn't correspond to the same moments.

Wavegram + Log-mel fusion

Left: the fixed log-mel of a signal. Middle: a (schematic) learned Wavegram of the same signal — note it captures structure the mel bands smear. Right: the two concatenated along channels into CNN14. Toggle which branches are active to see the fusion.

Fused input → CNN14
python
import torch, torch.nn as nn

class Wavegram(nn.Module):
    """Learned time-frequency front end from raw waveform."""
    def __init__(self):
        super().__init__()
        # strided 1-D conv downsamples 320k samples toward the frame rate
        self.pre = nn.Conv1d(1, 64, kernel_size=11, stride=5, padding=5)
        self.blocks = nn.Sequential(   # further 1-D convs + pooling
            ConvBlock1d(64, 64), ConvBlock1d(64, 128), ConvBlock1d(128, 64))

    def forward(self, wav):              # wav: [B, 1, 320000]
        x = self.blocks(self.pre(wav))   # [B, 64, ~1000]
        # reshape the 64 channels into a learned "frequency" axis
        return x.unsqueeze(1)             # [B, 1, 64, ~1000]  (acts like a spectrogram)

# Wavegram-Logmel-CNN: stack the two front ends on the channel axis
wavegram = Wavegram()(wav)            # [B, 1, 64, T]
logmel   = logmel_frontend(wav)        # [B, 1, 64, T]
x = torch.cat([wavegram, logmel], dim=1)  # [B, 2, 64, T] -> CNN14
Common misconception: the Wavegram does NOT replace log-mel in the best model — it is concatenated with it. "Wavegram-Logmel-CNN" means both front ends feed one CNN. People who read it as a pure waveform model miss why it wins: it keeps the psychoacoustic prior and merely adds a learnable channel. A pure-waveform Wavegram-CNN exists and is good, but it is not the top result.
What is the Wavegram, and how does the best PANNs model use it?

Chapter 6: Results & Ablations

A method is only as good as the number it produces, and on AudioSet the number is mAP. Let's understand it, then read the results.

What mAP measures

For each of the 527 classes you rank all test clips by their predicted probability for that class, then compute average precision (AP) — the area under that class's precision-recall curve. Because classes are wildly imbalanced and multi-label, AP (which is threshold-free and per-class) is far more honest than accuracy. mAP is the mean of the 527 per-class APs. A perfect ranker scores 1.0; random scores roughly the class prevalence.

APc = ∑k ( Rk − Rk−1 ) · Pk     mAP = (1/527) ∑c APc

Here Pk and Rk are precision and recall at the k-th ranked clip. The earlier baseline trained only on Google's released embeddings scored around mAP 0.31–0.36; PANNs, training end-to-end on raw audio, pushed past it decisively.

The headline numbers

ModelInputAudioSet mAP
Google baseline (embedding features)VGGish embeddings~0.31
CNN6log-mel~0.34
CNN10log-mel~0.38
CNN14log-mel0.431
Wavegram-Logmel-CNNwaveform + log-mel0.439

Two stories sit in this table. First, depth helps: CNN6 → CNN10 → CNN14 climbs steadily as you add blocks. Second, the learned front end helps a little more: the Wavegram-Logmel hybrid edges CNN14 from 0.431 to 0.439. The gain from the learnable branch is real but modest — the dominant lever is "train a good deep CNN end-to-end on raw audio at scale."

Ablations the paper ran

KnobFinding
Sample rate (8 / 16 / 32 kHz)Higher is better — 32 kHz keeps high-frequency cues many sounds need
Mel bands (32 / 64 / 128)64 is a sweet spot; more bands give marginal gains at higher cost
Pooling (avg / max / attention)Average and attention pooling beat max; max is noisy on weak labels
Data balancingBalanced sampling of rare classes improves their AP without hurting common ones
Augmentation (mixup, SpecAugment)Helps generalisation and the rare-class tail
Model sizeBigger CNNs help but with diminishing returns — late blocks cost quadratically
Average precision: the PR curve under the hood

For one class, clips are ranked by predicted score. As you sweep the threshold (drag), precision and recall trace a curve; AP is the area under it. A better ranker pushes the curve toward the top-right. The bar shows the resulting AP.

Model quality 0.60
Common misconception: "0.439 mAP sounds terrible — barely above random." It is not. AudioSet has 527 imbalanced, multi-label, weakly-labelled classes drawn from noisy YouTube; many classes are near-synonyms or co-occur constantly. mAP near 0.44 here was a large jump over the prior ~0.31 and represented state of the art. Don't compare an AudioSet mAP to a clean single-label accuracy — different planet.
Which statement best summarises PANNs' empirical findings?

Chapter 7: Transfer Learning

The AudioSet mAP is impressive, but the lasting contribution is transfer — the claim that a PANN is a good starting point for other audio tasks, the way an ImageNet ResNet is for vision. This is where "pretrained" in the name earns its keep.

The transfer recipe

1. Load CNN14
Front end + 6 conv blocks + the 2048-d embedding, with AudioSet weights
2. Replace the head
Swap the 527-class layer for a new linear layer sized to YOUR classes (e.g. 50 for ESC-50)
3. Fine-tune
Train on the small target set, with a low LR on the backbone (or freeze it and train only the head)

Two modes: frozen vs fine-tuned

You can use a PANN two ways, and the choice is an engineering tradeoff:

ModeWhat trainsWhen to use
Frozen feature extractorOnly the new headTiny target dataset; fast; less overfitting risk
Fine-tuningWhole network, low backbone LRModerate target dataset; squeezes out the best accuracy

Fine-tuning with a low backbone learning rate is the usual winner: it adapts the AudioSet features to the new domain without destroying them. Freezing is the safe choice when you have only a few hundred examples. PANNs reports that fine-tuned CNN14 set new states of the art across the downstream suite.

Where it transferred

TaskWhat it isPANNs result
ESC-5050-class environmental sound classificationNew SOTA accuracy (well above prior CNNs)
DCASE acoustic scenesWhere was this recorded? (park, metro...)Strong improvement over scratch training
GTZAN10-class music genre classificationCompetitive / SOTA
Speech command / sound eventsKeyword + event detectionPretraining beats from-scratch by large margins

The pattern is the vision pattern: pretraining helps most when the target dataset is small, and even on larger targets it speeds convergence and lifts the ceiling. A 50-clip-per-class problem that was hopeless from scratch becomes tractable because the backbone already knows what "sounds" are.

Worked example: how few labels does transfer rescue?

ESC-50 has 50 classes × 40 clips = 2000 clips total. Training CNN14's 81M parameters from scratch on 2000 clips would catastrophically overfit — far more parameters than data points. But with AudioSet pretraining, those 81M weights are already in a good basin; the 2000 clips only have to nudge them. That's why frozen-feature linear probes on ESC-50 already beat many from-scratch deep models, and fine-tuning pushes further. The lesson: pretraining converts a data-starved problem into a data-sufficient one.

Transfer: frozen vs fine-tuned vs scratch

Schematic downstream accuracy over training epochs for three strategies on a small target task. Scratch climbs slowly and plateaus low; frozen features start high but cap out; fine-tuning starts high AND keeps climbing. Drag to change target-set size.

Target set size small
python
# Transfer CNN14 to ESC-50 (50 classes) — the canonical PANNs use
import torch, torch.nn as nn
from panns_inference import Cnn14

model = Cnn14(sample_rate=32000, ..., classes_num=527)
model.load_state_dict(torch.load("Cnn14_mAP=0.431.pth")["model"])

# Swap the 527-class head for ESC-50's 50 classes
model.fc_audioset = nn.Linear(2048, 50)

# Fine-tune: low LR on the pretrained backbone, higher on the new head
opt = torch.optim.Adam([
    {"params": model.fc_audioset.parameters(), "lr": 1e-3},
    {"params": [p for n, p in model.named_parameters()
                if "fc_audioset" not in n], "lr": 1e-4},
])
# 2000 ESC-50 clips now suffice — the backbone already understands audio
Common misconception: "frozen feature extraction is always safer, so always freeze." Not on AudioSet-adjacent tasks. The AudioSet domain (messy YouTube) differs from clean ESC-50 recordings, so the features need some adaptation. A frozen backbone leaves accuracy on the table whenever you have enough target data to fine-tune safely (a few thousand clips). Freeze only when truly data-starved.
Why does PANNs pretraining help most on small downstream datasets like ESC-50?

Chapter 8: The Front-End Explorer

Here is the whole pipeline in one interactive instrument. Build a sound from components, watch it pass through the log-mel front end, the (schematic) CNN14 backbone, and the multi-label tagging head, and read the clipwise tag probabilities at the end. This is PANNs end-to-end: waveform → log-mel → features → sigmoid tags.

PANNs end-to-end: from sound to tags

Toggle which sound sources are present (each maps to a class), set the log-mel band count and the pooling temperature, and read the resulting clipwise tag probabilities. The mel panel shows the front end; the tag bars show the multi-label sigmoid head. Notice the head outputs DON'T sum to 1 — that's multi-label.

Mel bands 40 Sharpness 5

Things to try in the explorer:

What you're seeing: every knob in this explorer is a real PANNs design decision — mel band count (Ch. 2 + ablation), the CNN backbone collapsing the map (Ch. 3), and the sigmoid + pooling head that produces independent, clip-level tags (Ch. 4). The explorer is the paper, made tactile.

Chapter 9: Limitations & Connections

PANNs gave audio its ImageNet backbone, but it is a 2020 CNN, and the field moved. Knowing its limits tells you when to reach for something newer.

Limitations

LimitationWhy it bitesWhat addresses it
Clip-level only (weak)Tagging says what, not when; localising events needs moreAttention pooling gives crude framewise scores; dedicated SED models do better
Fixed CNN inductive biasConvolution's locality can miss long-range temporal structureAudio transformers (AST, BEATs) attend globally over patches
Supervised pretrainingNeeds AudioSet's labels; tied to its 527-class ontologySelf-supervised audio models pretrain without labels
81M params, fixed input lengthHeavy for edge; 10 s assumptionSmaller PANNs (CNN6/10), MobileNet variants
Log-mel discards phaseSome tasks (source separation) want phaseWavegram recovers some; complex/raw-waveform models keep it

The lineage

The 3×3-stack backbone PANNs borrows
AudioSet + VGGish (2017)
The dataset and the embedding baseline PANNs beat
This paper — PANNs (2020)
End-to-end CNN14 + Wavegram, the pretrained audio backbone
Transformers take over the log-mel image

Related Veanors & Gleams

The same log-mel input, fed to a ViT instead of a CNN — the direct successor
Self-supervised audio pretraining — drops AudioSet's labels
Raw-waveform modelling — the lineage the Wavegram draws on
STFT, mel, MFCC from zero — the front end in depth
Conv blocks, pooling, the backbone pattern CNN14 uses
Frozen vs fine-tuned, the exact transfer recipe in Ch. 7

Cheat sheet

ConceptWhat to remember
GoalAn ImageNet-style pretrained, transferable audio backbone
DataAudioSet: ~1.9M clips, 527 classes, weak (clip-level) + multi-label
Front end32 kHz → STFT (1024/320) → 64-band mel → log → [~1000, 64]
Mel scalemel(f)=2595·log10(1+f/700); narrow triangles low, wide high
BackboneCNN14: 6 VGG-style conv blocks, 2×2 avg-pool, widths 64→2048 → [2048,15,1]
Head1×1 embed → per-frame sigmoid (multi-label) → clipwise average pool
LossPer-class binary cross-entropy, summed over 527 (independent Bernoullis)
WavegramLearned 1-D-conv front end from waveform; CONCATENATED with log-mel
Best resultWavegram-Logmel-CNN, mAP 0.439; CNN14 log-mel, 0.431
MetricmAP = mean over 527 classes of per-class average precision
TransferSwap the 527-head, fine-tune with low backbone LR; SOTA on ESC-50 etc.
Closing thought: PANNs is not a flashy idea — it is a meticulous one. It took the obvious recipe (deep CNN, log-mel, train on the big dataset, release the weights) and executed it well enough to give an entire field a shared starting point. Years later, when you call panns_inference to embed a sound, or fine-tune CNN14 on your 2000 clips, you're standing on that execution. What I cannot create, I do not understand — and PANNs made the audio backbone something you can create, end to end, from a waveform.