4.6
Training
Learning the codebooks
So far the codebooks were given. Learning them is the easy half, because the decoder is linear. The trap is that free codebooks have a great many parameters.
1The decoder update is least squares
Fix the codes for training vectors. Build a sparse matrix with one 1 in each of the blocks of every row, marking the entry that row uses. Stack all codebooks into one matrix .
The reconstruction of the whole training set is then , and the best codebooks for those codes solve a plain least-squares problem:
Alternate: encode, then solve for the codebooksnumpy
def update_codebooks(X, codes, K):
N, M = codes.shape
B = np.zeros((N, M * K)) # sparse in practice
B[np.arange(N)[:, None], np.arange(M) * K + codes] = 1
C = np.linalg.lstsq(B, X, rcond=None)[0] # (M*K, d)
return np.split(C, M)
codebooks = rq_train(X) # the starting point matters a lot
for epoch in range(10):
codes = encode(X, codebooks) # beam / ICM: expensive
codebooks = update_codebooks(X, codes, K) # least squares: cheap
This is k-means with a different assignment step.
Training inherits the encoder’s cost.
Every epoch re-encodes the whole training set with the expensive encoder. That is why LSQ-style methods spent much of their engineering on fast GPU encoding.
2The overfitting trap
Three defences, in order of preference:
- More data than parameters. Marginalia has 300 million vectors to train on; the experiment here had 63,120, about one sample per ten parameters.
- Regularise. A ridge term in the least-squares solve keeps rarely used codewords from chasing noise.
- Always score on held-out vectors. It is the only way to notice.