The first self-supervised audio model whose masked-prediction targets are semantic discrete tokens — produced by a tokenizer that co-evolves with the model in an iterative loop. It closed the gap between reconstruction-style and label-style audio SSL and set state of the art on AudioSet.
You have ten million YouTube clips and no labels. You want a model that, after a tiny bit of fine-tuning, can tell a dog bark from a doorbell, a violin from a viola, applause from rain. The trick everyone uses is masked prediction: hide part of the input, ask the model to fill it in, and the representations it learns along the way turn out to be useful for everything downstream. BERT did this for text, MAE did it for images, HuBERT did it for speech. The question this paper answers is deceptively narrow: when you mask part of an audio clip, what exactly should the model be asked to predict?
That question is the whole ball game, and before BEATs nobody had a satisfying answer for general audio. Let's see why the obvious choices were broken.
The simplest target is the audio itself — predict the masked mel-spectrogram patch, pixel-for-pixel, with an L1 or L2 loss. This is what SSAST and MAE-style audio models (Audio-MAE) do. It works, but it spends the model's capacity on the wrong thing. A mel patch is full of low-level texture: the exact phase of a hum, the speckle of background noise, the precise energy in band 73. Forcing the model to regress all of that pulls its representation toward acoustic detail and away from meaning. You end up an excellent denoiser and a mediocre classifier.
For speech, HuBERT and wav2vec 2.0 instead predict a discrete target — a cluster id. HuBERT runs k-means on MFCC features to assign every frame a label in {1..K}, then asks the model to classify the masked frame into the right cluster. Classification (cross-entropy over K classes) is a much friendlier loss than regression: it ignores within-cluster acoustic detail and only cares about which bucket the sound belongs to. The problem: the buckets come from k-means on hand-picked low-level features. For speech those features happen to track phonemes. For general audio — music, machinery, animal calls, ambience — k-means on MFCCs gives you buckets that mostly track loudness and spectral shape, not what the sound is. The target is discrete but not semantic.
Slide from a purely acoustic target (reconstruct the patch) to a purely semantic one (predict an event id). Watch the two failure modes: an acoustic target wastes capacity on texture; a hand-built discrete target loses meaning. BEATs aims for the green band — discrete and semantic — and gets there by letting the tokenizer learn.
So the field had a fork: discrete-but-dumb (HuBERT-style cluster ids) or continuous-but-detail-obsessed (MAE-style reconstruction). BEATs' thesis is that you should not have to choose. You want a target that is discrete (so the loss is a clean classification) and semantic (so predicting it forces the model to understand the sound). The catch: a semantic discrete target does not exist for free. You cannot k-means your way to "this 16×16 patch is part of a guitar." You have to learn the tokenizer — and learn it in a way that injects meaning into the codes.
That loop is the paper. By the end of this veanor you will have built the quantizer, the masked-prediction loss, and the distillation objective with your own hands. Let's start where the data starts: turning a waveform into something a Transformer can read.
A Transformer eats a sequence of vectors. Audio arrives as a 1-D waveform — tens of thousands of samples per second. The standard bridge, which BEATs inherits from the vision-transformer view of audio (HTS-AT, AST), has two steps: turn the waveform into a 2-D mel spectrogram, then chop that image into square patches.
Run a short-time Fourier transform: slide a ~25 ms window across the waveform every ~10 ms, take the magnitude of its frequency content, and warp the frequency axis onto the perceptual mel scale (which spaces low frequencies finely and high frequencies coarsely, like the ear does). For BEATs the result is a matrix with 128 mel bins on the frequency axis and one column per 10 ms frame. A 10-second clip at this hop is roughly 998 frames × 128 mels — call it F frames by M mels.
Treat that spectrogram as a single-channel image of shape [1, F, M] and split it into non-overlapping 16×16 patches, exactly as a ViT splits a photo. With F ≈ 1000 and M = 128 this yields about (1000/16) × (128/16) = 62 × 8 ≈ 496 patches. Each patch is a 16×16 = 256-number tile of the spectrogram. A small convolution (a "patch-embed" stem) projects each tile to a D-dimensional vector — in BEATs, D = 768. We now have a sequence:
This is the object every later chapter operates on. The tokenizer assigns each patch xn a discrete code; the encoder masks a subset of patches and predicts those codes; the distillation step re-fits the codes. Everything is per-patch.
Let's make the shapes concrete on a tiny clip so the arithmetic is unambiguous. Take a 1-second clip, hop 10 ms, so F = 100 frames; M = 128 mels; patch = 16. Patches along time: ⌊100/16⌋ = 6 (the leftover 4 frames are dropped or padded). Patches along frequency: 128/16 = 8. Total patches N = 6 × 8 = 48. Each patch is 256 raw numbers; the conv stem maps 256 → 768. So a 1-second clip becomes a sequence of 48 vectors of length 768 — a [48, 768] tensor. Double the clip length, double N; the per-patch dimension never changes.
A mel spectrogram (frequency up, time right) carved into 16×16 patches. Drag the slider to change the patch size and watch how N — the number of tokens the Transformer sees — changes. Bigger patches = fewer tokens = cheaper attention but coarser resolution.
The tokenizer is the heart of BEATs. Its job: take each patch vector and emit a single integer in {1,…,K} — its code id. The set of K possible codes is the codebook. BEATs uses a codebook of K = 1024 entries, each a learnable vector in a low-dimensional code space (the paper uses a 256-dim quantizer space, projected from the encoder's 768). Quantizing means: for a patch, find the nearest codebook vector and report its index.
Concretely the tokenizer is itself a small Transformer (the "tokenizer encoder") followed by a vector-quantization (VQ) layer. Let zn ∈ ℝd be the tokenizer's continuous output for patch n, and let the codebook be {e1,…,eK} with each ek ∈ ℝd. The code id is nearest-neighbour:
and the quantized vector handed downstream is just the chosen codebook entry, q̂n = ecn. Two patches that map to the same nearest entry get the same token — they are declared "the same kind of sound."
BEATs (following the BEiT-v2 / VQ-KD line) ℓ2-normalizes both zn and the codebook entries before computing distance. On the unit sphere, squared Euclidean distance and cosine similarity are equivalent: ‖a−b‖² = 2−2·a·b when ‖a‖=‖b‖=1. So nearest-neighbour quantization becomes "pick the code with the highest cosine similarity." Normalizing keeps the codebook from collapsing onto a few high-norm entries and makes training far more stable — a recurring lesson in VQ.
argmin is not differentiable — you cannot backprop through "pick the nearest". The fix, from VQ-VAE, is the straight-through estimator: on the forward pass use the quantized q̂n; on the backward pass copy the gradient straight through as if q̂n = zn. In code this is the one-liner q = z + stop_grad(qhat − z): the value equals q̂n, but the gradient flows to zn unchanged.
To actually move the codebook and the encoder toward each other, VQ adds two auxiliary terms. With sg[·] the stop-gradient:
The first term (the codebook loss) pulls the chosen code toward the encoder output. The second (the commitment loss, weight β ≈ 0.25) pulls the encoder output toward its code so the encoder "commits" to codes instead of drifting. (BEATs in practice uses the exponential-moving-average update for the codebook, an equivalent of the first term; the commitment term stays.)
Take a 2-D code space (d = 2) and a tiny codebook of three entries: e1 = (1, 0), e2 = (0, 1), e3 = (−1, 0). The tokenizer outputs z = (0.8, 0.3). Distances:
‖z−e1‖² = (0.8−1)² + (0.3−0)² = 0.04 + 0.09 = 0.13
‖z−e2‖² = (0.8−0)² + (0.3−1)² = 0.64 + 0.49 = 1.13
‖z−e3‖² = (0.8−(−1))² + 0.09 = 3.24 + 0.09 = 3.33
The smallest is e1, so c = 1 and the quantized vector is (1, 0). The commitment loss would then push z toward (1, 0), and the codebook update would nudge e1 toward (0.8, 0.3). You will reproduce exactly this nearest-code logic in Code Lab #1.
Eight codebook vectors (teal dots) partition the plane into Voronoi cells. Drag the encoder output (warm dot) and watch which code it snaps to — and the squared distance to each. The cell boundaries are exactly where the argmin flips. This is one token assignment.
python # Vector quantization with the straight-through estimator (VQ-VAE) import torch import torch.nn.functional as F def quantize(z, codebook, beta=0.25): """ z: [N, d] tokenizer outputs (one per patch) codebook: [K, d] the K learnable code vectors returns: q [N, d] quantized, ids [N], vq_loss """ # L2-normalize so distance == cosine (BEATs / BEiT-v2 trick) zn = F.normalize(z, dim=-1) en = F.normalize(codebook, dim=-1) # Squared distances [N, K]; argmin over K = nearest code d2 = (zn.pow(2).sum(-1, keepdim=True) - 2 * zn @ en.T + en.pow(2).sum(-1)) # [N, K] ids = d2.argmin(-1) # [N] the token ids qhat = en[ids] # [N, d] chosen codes # Codebook + commitment losses cb = F.mse_loss(qhat, zn.detach()) cm = F.mse_loss(zn, qhat.detach()) vq_loss = cb + beta * cm # Straight-through: forward = qhat, backward = identity to z q = zn + (qhat - zn).detach() return q, ids, vq_loss
Now freeze the tokenizer for a moment and look at the model being pre-trained. Given the patch sequence X = [x1,…,xN], the tokenizer has labeled every patch with a code id cn ∈ {1,…,K}. We sample a mask set M ⊂ {1,…,N} covering about 75% of the patches. For masked positions we replace xn with a shared learnable [MASK] embedding; unmasked positions pass through unchanged. The Transformer encoder reads this corrupted sequence and produces hidden states h1,…,hN. A linear "prediction head" maps each masked position's hidden state to a distribution over the K codes:
and the loss is plain cross-entropy against the tokenizer's labels, summed over masked positions only:
("MAM" = masked audio modeling.) That is the entire pre-training objective for the model. No reconstruction, no waveform, no autoregression — just "for each blanked patch, classify which of the 1024 acoustic codes it was." Because the codes are semantic (we'll earn that adjective in Chapters 4–5), classifying them forces the encoder to reason about what the surrounding sound is, not just what it looks like.
Two design choices that look small and aren't:
High mask ratio. If you mask only 15% (BERT's text ratio), audio patches are so locally redundant that the model can solve each blank by copying a neighbour — a barely-disturbed harmonic stack is trivially interpolated. Masking ~75% (as MAE found for images, and BEATs adopts) removes the easy local cues and forces long-range, semantic inference: to fill a hole you must recognize the event spanning the whole clip.
Score only masked positions. If you also asked the model to predict unmasked patches it can see, it would just learn a copy — the loss would reward identity, not understanding. Restricting LMAM to M makes every contributing term a genuine guess.
Suppose K = 4 codes and the prediction head outputs logits (2.0, 1.0, 0.1, −1.0) for a masked patch whose true code is c = 1 (the first). Softmax: exponentiate → (7.39, 2.72, 1.11, 0.37), sum = 11.59, so p = (0.638, 0.235, 0.096, 0.032). The loss for this patch is −log p1 = −log 0.638 = 0.449 nats. If the model had been confident-and-correct (p1 → 1) the loss → 0; confident-and-wrong (p1 → 0) sends it to ∞. Cross-entropy only looks at the probability assigned to the right code — the within-code acoustic texture is invisible to it. That invisibility is the whole reason discrete targets beat reconstruction.
A 12×8 grid of spectrogram patches. Slide the mask ratio: masked patches (warm) are replaced by [MASK]; the encoder must predict their token ids from the visible (teal) ones. The counter shows how many of the visible patches are "easy neighbours" — at 75% almost none survive, forcing semantic inference.
python # Masked Audio Modeling loss — predict tokenizer codes at masked patches import torch import torch.nn.functional as F def mam_loss(encoder, head, patches, code_ids, mask_ratio=0.75): N, D = patches.shape # 1. choose ~75% of positions to mask n_mask = int(N * mask_ratio) mask = torch.randperm(N)[:n_mask] # 2. replace masked patches with the learnable [MASK] embedding x = patches.clone() x[mask] = head.mask_token # [D] broadcast # 3. encode the corrupted sequence (bidirectional, all visible at once) h = encoder(x) # [N, D] # 4. classify each masked position over the K codes logits = head.proj(h[mask]) # [n_mask, K] # 5. cross-entropy against tokenizer labels, masked positions only return F.cross_entropy(logits, code_ids[mask])
We have postponed the central question long enough: how does a code id become semantic? A VQ codebook trained only to reconstruct, or trained on random patches, gives codes that cluster acoustically — they separate loud from quiet, high from low — but not violin from flute. BEATs' answer is label distillation (it calls the objective the acoustic tokenizer's knowledge distillation): train the tokenizer so that its discrete codes predict the outputs of a trained masked-prediction model. The model has, through pre-training and fine-tuning, learned a semantic representation; distillation pours that semantics into the discrete codes.
The tokenizer is trained as a tiny student that must reconstruct a teacher's behaviour through the bottleneck of a discrete code. Two pieces:
The distillation loss makes the prediction-from-code match the teacher. BEATs maximizes the cosine similarity between the code-derived prediction t̂n and the teacher target tn (equivalently, minimizes a normalized MSE):
where t̂n is computed from the quantized code only, sg[·] freezes the teacher, and Lvq is the codebook/commitment term from Chapter 2. The crucial constraint: t̂n may depend on the patch only through its discrete code ecn. So if two patches must yield very different teacher representations, they are forced into different codes. The discreteness becomes an information bottleneck that the codebook must spend wisely on whatever the teacher considers meaningful — i.e. on semantics.
Imagine three patches A, B, C with teacher targets tA = (1, 0), tB = (0.9, 0.1), tC = (0, 1). The teacher says A and B are nearly identical (cos ≈ 0.996) and C is orthogonal. A code can only emit one prediction t̂ per id. If A and B share code 1 (predicting (0.95, 0.05)) the cosine loss is tiny for both. But forcing C into code 1 as well would cost cos((0.95,0.05),(0,1)) ≈ 0.05 — terrible. So distillation pushes C onto a different code. The codebook self-organizes so that "patches the teacher treats alike share a code; patches the teacher distinguishes get split." That is semantics, learned. You'll implement this similarity-driven split in Code Lab #2.
Left: a tokenizer trained only on acoustics — codes split by loudness/pitch (rings). Right: after distillation — codes split by what the teacher considers the same event (colored clusters). Drag the slider to interpolate and watch low-level rings dissolve into semantic clusters.
python # Acoustic-tokenizer knowledge distillation (cosine objective) import torch import torch.nn.functional as F def tokenizer_distill_loss(tok, decoder, teacher, patches): # teacher is FROZEN — the trained BEATs model from the last iteration with torch.no_grad(): t = teacher(patches) # [N, D] target representation z = tok(patches) # [N, d] tokenizer outputs q, ids, vq = quantize(z, tok.codebook) # discrete bottleneck # predict the teacher target FROM THE CODE ONLY t_hat = decoder(q) # [N, D] # match direction, not magnitude distill = -F.cosine_similarity(t_hat, t.detach(), dim=-1).mean() return distill + vq, ids
We have a chicken-and-egg problem. To make codes semantic (Chapter 4) we distill from a trained BEATs model. But to train that model (Chapter 3) we need semantic codes. Which comes first? BEATs breaks the cycle with a cold start and then iterates.
You cannot distill from a teacher you don't have. So the very first tokenizer is not learned at all — it is a fixed random-projection quantizer. Project each patch through a random (frozen) matrix into the code space, then nearest-neighbour against a random codebook. The codes are acoustically meaningful but not semantic — exactly the HuBERT-style starting point. Crucially, even a random discrete target is enough to bootstrap a first masked-prediction model that learns something useful, just as HuBERT's first iteration runs on k-means-of-MFCC labels.
Each pass through the loop has two phases that never train together: (a) freeze the model, train the tokenizer by distillation; (b) freeze the tokenizer, train the model by masked prediction. Like the EM algorithm or like HuBERT's relabeling rounds, alternating between two frozen-and-trained roles is what keeps the loop stable. Joint end-to-end training would let the model and tokenizer collude on a degenerate code (e.g. constant labels with zero loss). The freezing prevents collapse.
Score "label semanticity" as the fraction of same-event patches that share a code (higher = more semantic). Suppose random-projection labels give 0.30 (mostly acoustic accidents). Iter-1's model, trained on those, learns features that — when distilled — produce codes scoring 0.62. Iter-2's model, trained on the 0.62 labels, distills to 0.71. Iter-3 would give ~0.73. The deltas — +0.32, +0.09, +0.02 — show the textbook diminishing returns that justify stopping at two. (These numbers are illustrative; the paper reports the analogous downstream-accuracy gains plateauing by iteration 2.)
Press "Iterate" to run the loop. Each press: (1) train the model on the current labels (the encoder's accuracy bar rises), then (2) distill it into a new tokenizer (the label-quality bar rises). Watch both climb together and saturate after ~2 rounds — the paper's empirical stopping point.
Both the tokenizer and the masked-prediction model share the same backbone: a ViT-style Transformer encoder adapted for spectrograms, drawn from HTS-AT-flavoured audio transformers. Let's pin down the architecture and the data flow with real shapes.
| Stage | Operation | Tensor shape |
|---|---|---|
| Input | Mel spectrogram | [1, F, 128] |
| Patch embed | Conv stem, 16×16 stride 16, → D=768 | [N, 768], N≈496 |
| Pos. encoding | Convolutional positional embedding (depthwise conv) | [N, 768] |
| ×12 blocks | Self-attn (rel-pos bias) → Add&Norm → FFN → Add&Norm | [N, 768] |
| Pre-train head | Linear → K logits (masked positions only) | [|M|, 1024] |
| Fine-tune head | Mean-pool over N → Linear → C classes | [C] |
The base BEATs encoder is roughly 90M parameters — 12 Transformer layers, hidden width 768, 12 attention heads, FFN width 3072. Two adaptations matter for audio:
Convolutional positional embedding. Instead of (or in addition to) sinusoidal position codes, a depthwise convolution over the patch sequence injects relative position — it lets nearby patches in time-frequency know they are neighbours, which is more natural for the 2-D grid structure of a spectrogram than 1-D sinusoids.
Relative-position bias in attention. Each attention score gets an additive learned bias depending on the relative offset between query and key patch. So the raw attention is
where bi−j is a learned scalar shared by all patch pairs the same distance apart. This is the relative-position scheme from Swin/HTS-AT — it improves length generalization and locality without fixing a maximum sequence length the way learned absolute positions do.
During pre-training the encoder reads a masked patch sequence and the head classifies codes at masked positions. During fine-tuning the same encoder reads the full (unmasked) sequence; a new head mean-pools all N hidden states into a clip-level vector and classifies it into the C downstream labels (e.g. 527 AudioSet classes). The expensive 90M-parameter encoder is reused verbatim — only the cheap output head is replaced. That reuse is the entire value proposition of self-supervised pre-training: pay once for the encoder on unlabeled data, amortize over many downstream tasks.
For N = 496 patches and D = 768, one attention layer costs roughly N²·D for the QKT·V products ≈ 496²·768 ≈ 1.9×108 mults, while the FFN costs roughly N·D·(4D) ≈ 496·768·3072 ≈ 1.2×109 — the FFN dominates a single layer at this sequence length. But because attention is O(N²), doubling the clip length (N→992) quadruples the attention term while only doubling the FFN term. This is why long-audio modeling is hard and why patching (which keeps N small) is essential.
Click "Highlight" to step through the stack and watch the tensor shape at each stage — from spectrogram to patches to 12 Transformer blocks to either the pre-train code head or the fine-tune class head.
BEATs' headline claim is straightforward: with the same encoder size and data, swapping the masked-prediction target from reconstruction or hand-built clusters to iterated semantic codes moves audio classification to state of the art. The flagship benchmark is AudioSet (~2M weakly-labeled 10-second YouTube clips, 527 sound classes, scored by mean average precision, mAP).
| Setting | What it shows | AudioSet-2M mAP |
|---|---|---|
| Prior SSL (SSAST, MAE-style) | reconstruction / patch-recovery targets | lower (mid-40s) |
| BEATs iter 1 (random-proj. labels) | discrete but non-semantic target | strong already |
| BEATs iter 3 (semantic codes) | distilled, iterated discrete target | SOTA single model (~48.6 mAP) |
| BEATs ensemble | multiple fine-tuned models | ~50.6 mAP (new SOTA) |
The single-model BEATs reached roughly 48.6 mAP on AudioSet-2M and the ensemble roughly 50.6 mAP — the best reported at the time, surpassing supervised and prior-SSL audio transformers. It also topped smaller benchmarks (ESC-50 environmental sounds near the high-90s accuracy, strong scores on speech-command and other tasks), showing the representation transfers beyond AudioSet.
The paper's experiments are designed to isolate why it works:
mAP averages the per-class average precision over all 527 classes. Suppose reconstruction-target pre-training gives 45.0 mAP and semantic-code pre-training gives 48.6. The +3.6 absolute looks small, but on AudioSet — where the gap between consecutive SOTA papers is often well under a point — 3.6 mAP from a single design change (the target) is large. It says: on average across every sound class, the semantic-code model ranks true positives meaningfully higher. The cost was zero extra labels — the whole pipeline ran on the same unlabeled audio.
Toggle the masked-prediction target and read the (illustrative) downstream mAP. Reconstruction and random-projection codes lag; iterated semantic codes lead. Same encoder, same data — only the target differs.
This is the whole pipeline in one interactive surface. A synthetic clip is patchified; a learnable-looking codebook tokenizes each patch into a code id; a fraction of patches are masked; the masked-prediction head guesses their codes. Drive every knob and watch the mask, the token map, and the (simulated) prediction accuracy respond. There's no quiz here — this is the payoff. Read the three readouts as you move the controls.
Top: the patch grid colored by token id (each color = one of K codes). Masked patches are dimmed and X'd. Bottom: a bar of predicted-vs-true codes at masked positions. Raise K for a finer codebook, raise mask to make the task harder, raise iter to make the codes more semantic (and the model better at recovering them).
Things to notice as you play:
That interaction — high mask ratio is only learnable when the target is semantic, which only happens after iteration — is the single most important intuition in the paper. The mask ratio and the iterative tokenizer are not two independent tricks; they are two halves of one mechanism.
BEATs is a clean, influential result, but it is not magic. Where does it strain, and where does it sit in the larger map of representation learning?
BEATs is the audio member of a family of discrete-token masked-prediction methods that all share one move — make the prediction target a learned discrete code rather than raw input:
| Method | Domain | Discrete target from | Relation to BEATs |
|---|---|---|---|
| HuBERT | Speech | k-means on features, re-clustered each iter | The iterative-relabeling idea; non-semantic clusters |
| BEiT / BEiT-v2 | Images | dVAE / VQ-KD codes (distilled) | Direct ancestor of the distilled-codebook idea |
| MAE / Audio-MAE | Image / Audio | none — reconstructs pixels/patches | The reconstruction baseline BEATs beats |
| BEATs | Audio | iterated semantic acoustic tokenizer | This paper — semantic codes + iteration |
| Concept | One-line summary |
|---|---|
| Goal | Self-supervised audio encoder whose masked-prediction target is discrete and semantic. |
| Input | Mel spectrogram [1,F,128] → 16×16 patches → N≈496 vectors of dim 768. |
| Tokenizer | Small Transformer + VQ codebook (K=1024); patch → nearest code id. ℓ2-normalized, straight-through gradient. |
| Model objective | LMAM: mask ~75% of patches, cross-entropy classify their code ids (masked positions only). |
| Making codes semantic | Distill a frozen teacher: predict its representation from the code only, cosine loss. Forces same-event patches to share a code. |
| Iterative loop | Cold start with random-projection labels → train model → distill tokenizer → retrain. Saturates by iter 2. |
| Encoder | ~90M, 12-layer ViT/HTS-AT-style; conv positional embed + relative-position attention bias. |
| Fine-tune | Reuse encoder on full sequence; swap head → mean-pool + linear over C classes. |
| Result | SOTA on AudioSet (~48.6 single / ~50.6 ensemble mAP); ablations show the target is the lever. |
| Key tradeoff | Multi-stage, costly pipeline; O(N²) attention caps clip length; tokenizer hyperparameters are heuristic. |