VQ-VAE Vector Quantization - Generative Models
Implement VQ-VAE Vector Quantization in Python with NumPy. hard exercise with examples, hints, and instant feedback. No setup or signup required.
VQ-VAE Vector Quantization
What you'll build
The discrete bottleneck of a VQ-VAE (van den Oord et al., 2017): snap every encoder vector to its nearest codebook entry, and compute the two auxiliary losses that train the codebook and the encoder.
Background
A VQ-VAE replaces the Gaussian latent of a VAE with a discrete one: a grid of indices into a learned codebook of K vectors. Discrete codes are what image and audio tokenizers (VQGAN, SoundStream, the image tokens of many multimodal LLMs) produce, so a Transformer can then model them like text.
Algorithm: vq_quantize
- Flatten the encoder output
z_efrom(B, H, W, D)toN = B·H·Wvectors of size D. - Compute all squared distances to the K codes with the expansion
||z − e_k||² = ||z||² − 2·z·e_k + ||e_k||², giving an(N, K)matrix. indices = argminover K (ties go to the lowest index, asnp.argmindoes).z_q = codebook[indices], reshaped back to(B, H, W, D); indices to(B, H, W).
Losses: vq_loss
sg[·] is stop-gradient (.detach() in PyTorch):
codebook_loss = mean( (sg[z_e] − z_q)² ) # pulls codes toward encoder outputs
commitment_loss = β · mean( (z_e − sg[z_q])² ) # keeps the encoder near its codes
Means are over all elements. In a forward pass both squared errors have the same value (sg only changes gradients), so commitment_loss = β · codebook_loss. The full VQ-VAE loss is reconstruction + codebook_loss + commitment_loss. The straight-through estimator z_q = z_e + sg[z_q − z_e] copies the decoder's gradient to the encoder; it only matters in an autograd framework (see the PyTorch track and E2E: VQ-VAE).
Inputs
z_e: float array(B, H, W, D)codebook: float array(K, D)beta: float, commitment weight (default 0.25)
Expected return format
vq_quantizereturns(z_q, indices):z_qfloat(B, H, W, D),indicesint(B, H, W)vq_lossreturns(codebook_loss, commitment_loss): Python floats rounded to 4 decimals
Constraints
NumPy only. Vectorise the distance computation: no loop over positions or codes.
Follow-up
- What is codebook collapse, and how do EMA codebook updates or dead-code restarts fix it?
- Memory of the
(N, K)distance matrix for a 32×32 grid, batch 64 and K = 8192? (64·1024·8192·4 bytes ≈ 2 GB, so chunk it.)
Examples
Input: z_e = [[[[1, 1], [9, 8], [4, 6]]]] (shape (1, 1, 3, 2)), codebook = [[0, 0], [10, 10], [5, 5]] Output: indices = [[[0, 1, 2]]], z_q = [[[[0, 0], [10, 10], [5, 5]]]]
[9, 8] is at squared distance 5 from [10, 10] but 25 from [5, 5]; [4, 6] is 2 from [5, 5]
Input: vq_loss(z_e, z_q, beta=0.25) for the example above Output: (1.5, 0.375)
Squared errors 1, 1, 1, 4, 1, 1 have mean 1.5; the commitment loss is 0.25 · 1.5
Practice in Python
import numpy as np
def vq_quantize(z_e, codebook):
"""
Vector quantization for VQ-VAE.
Args:
z_e: Encoder output, shape (batch, H, W, embedding_dim)
codebook: Code vectors, shape (num_embeddings, embedding_dim)
Returns:
z_q: Nearest code for every position, shape (batch, H, W, embedding_dim)
indices: Index of that code, int array of shape (batch, H, W);
ties go to the lowest index
"""
# Your code here
pass
def vq_loss(z_e, z_q, beta=0.25):
"""
Codebook and commitment losses (forward values).
Args:
z_e: Encoder output, shape (batch, H, W, embedding_dim)
z_q: Quantized vectors, same shape
beta: Commitment loss weight (default 0.25)
Returns:
(codebook_loss, commitment_loss): Python floats rounded to 4 decimals,
codebook_loss = mean((z_e - z_q)**2), commitment_loss = beta * that
"""
# Your code here
pass
The interactive editor loads with Python and NumPy. Run your implementation against 8 tests, or use the progressive hints.