Be greedy
Let every codebook use all the dimensions. Quantize, subtract, quantize what is left. It is cheap, it is good, and it is not optimal.
1Quantize what is left
Now let every codebook use all dimensions. The simplest encoder:
- Pick the best entry from codebook 1.
- Subtract it. What remains is the residual.
- Quantize the residual with codebook 2.
- Repeat for all codebooks.
Train the same way: k-means on , then k-means on the residuals, and so on. This is residual quantization (RQ), an old idea from signal coding (Juang and Gray, 1982) that came back for vector search and for neural audio codecs.
def rq_encode(X, codebooks): # M arrays of shape (K, d)
R, codes = X.copy(), []
for C in codebooks:
i = nearest(R, C)
codes.append(i)
R -= C[i] # the next stage sees only what's left
return np.stack(codes, axis=1)
def additive_decode(codes, codebooks):
return sum(C[codes[:, m]] for m, C in enumerate(codebooks))
Greedy is cheap: , the same order as PQ. But it is not optimal.
Drag the target The dashed gold path is greedy; the solid mint path is the best of all 64 combinations. Greedy commits to the closest first arrow even when a slightly worse first arrow would let the smaller arrows land closer.
2The shape of the failure
Stage chooses without knowing what stages can do. The readout under the figure says how often that matters in the toy; press New codebooks a few times to see it vary.
3A side effect worth remembering
This is used in three places later:
- One model, many bitrates. Neural audio codecs randomly drop later stages during training so that one model serves every bitrate (4.9 Bells and whistles).
- Depth chosen afterwards. For lossless coding you can train 24 stages and decide how many to use later, even per vector (6.7 Where should the bits go?).
- Refinement on the wire. Send the first stages now and the rest if the client asks (6.9 Two codecs).