Close Enough 0% ◫Browse AaGlossary
5.2
Amplification

Turning a gentle slope into a cliff

One bit barely separates neighbours from strangers. Combine k bits with AND and L tables with OR and the collision curve becomes an S. Choosing k and L is the whole design problem, and it has a closed form.

~6 min

1AND: make buckets selective

Concatenate kk independent bits into one bucket key. Two points share a bucket only if they agree on all kk:

Pr⁡[same bucket]  =  p k \Pr[\text{same bucket}] \;=\; p^{\,k}

Strangers are filtered out exponentially fast: p2k\kres{p_2}^k. With p2=0.52p_2 = 0.52 and k=12k = 12, a random point lands in the query’s bucket once in about 2,492.

Unfortunately neighbours are filtered too: p1k\kdec{p_1}^{k} with p1=0.61p_1 = 0.61 and k=12k = 12 is about 0.003. One table almost never contains the neighbour.

2OR: give neighbours more chances

Build LL independent tables, each with its own kk bits. A point is a candidate if it shares a bucket with the query in any table:

Pr⁡[candidate]  =  1−(1−p k)L \Pr[\text{candidate}] \;=\; 1 - \big(1 - p^{\,k}\big)^{L}

This is the AND-OR construction, or amplification. As a function of similarity it is an S-curve: near zero for dissimilar points, near one for similar points, with a transition that sharpens as kk and LL grow together.

Fig 5.2.1The LSH S-curve

Set k and L The faint dotted line is a single bit. The dashed markers are a true 10th neighbour and a random pair on Marginalia’s data. Where a marker meets the curve is, roughly, your recall (green) and the share of the database you will have to scan (red). When your settings match a measured configuration, the measurement appears below.

3How to choose k and L

Let NN be the size of the database. The classical recipe (Indyk and Motwani):

  1. Choose kk so that strangers are rare. Set p2 k=1/Np_2^{\,k} = 1/N, which gives about one stranger per table: k=ln⁡Nln⁡(1/p2)k = \dfrac{\ln N}{\ln(1/p_2)}.
  2. Choose LL so that a neighbour is found. A neighbour is in a given table with probability p1 kp_1^{\,k}, so you need about L=1/p1 kL = 1/p_1^{\,k} tables.
  3. Substitute. p1 k=N−ρp_1^{\,k} = N^{-\rho} where
ρ  =  ln⁡(1/p1)ln⁡(1/p2)⟹L≈Nρ,query work≈Nρ,memory≈N1+ρ \kcode{\rho} \;=\; \frac{\ln(1/\kdec{p_1})}{\ln(1/\kres{p_2})} \qquad\Longrightarrow\qquad L \approx N^{\rho}, \qquad \text{query work} \approx N^{\rho}, \qquad \text{memory} \approx N^{1+\rho}

The exponent ρ is always below 1 because p1>p2p_1 > p_2. That is the sense in which LSH is provably sublinear.

4The memory bill

Every table stores a pointer for every vector. LL tables cost L×N×4L \times N \times 4 bytes before you store a single vector: at L=64L = 64 that is 256 bytes per vector of index overhead, as much as an HNSW graph and far more than an IVF index, which stores each vector once.