# Speeding up k-means: Random projections

We looked at the basic k-means algorithm (Lloyd’s) in the last post. It’s one of the algorithms used by Mahout for clustering, both sequentially and as a mapreduction. We’ll talk about Mahout’s k-means implementation in a few posts.

Recall that the first step of every iteration in Lloyd’s is to classify each point (assign it to its nearest cluster). To do this, we need to calculate the distance between the current -dimensional, vector and each of the centroids using some distance metric, like the Euclidean distance or the cosine of the angle between the vectors, .

In any case, calculating the distance to each center (of which there are ) and assigning it to the closest one takes per point (we also need to go through the dimensions of the vector), so in total per Lloyd’s iteration.

We can get a faster algorithm by trading optimality (we no longer get the closest neighbor) for speed. One thing that adds quite a bit of overhead is the (the dimension of the vector) because we need to loop through all of them to calculate the distance. Random projections can really help with this. The idea is to sample a set of vectors (that are normally distributed, see [3] for a more detailed lemma) and project the existing vectors (that we want to search for the nearest neighbor in) onto them. Then, we project the vector we’re looking for, and the value of its projection will be close to the value of the projections of its nearest neighbor. The scalar projection is 1D and we only need to compute the projections of the points we’re searching in once. We can get an idea of how close two vectors are to one another by looking at these values. Of course, since we’re colapsing dimensions into just one, we lose information, but it turns out that we only introduce false positives (points that were further apart are brought closer by projecting them).

Let’s look at a bit of math first. Suppose our projection vector is and the vector we want to project is . If (the norm of the vector, a postive real number), the value of the scalar projection is . Now, suppose we’re trying compute how close two vectors and and we’re using the Euclidean distance. We would compute where . If instead, we project them on some vector , we would compare and where and .

For Euclidean distance, we can skip extracting the square root altogether since $\sqrt x$ is a monotonically increasing function. Why is it that we neve get false negatives? What do we mean when we say that when two vectors are “close toghether”, their projects are also “close together”? Let’s compare and . Let’s see if , the actual distance is always be greater or equal than the estimate. Expanding, . Additionally, the projection vector can either be between the two vectors, in which case , or on one side of the vectors, in which case (or the other way around). For the two vectors to be “close together”, the angle between them, , which means . Moreover, the angle drops down to zero (so it’s be a small positive integer). This also means that (for ).

So, if , we know that , so (since and ).

Otherwise, for you add rather than substracting it, which makes the same argument less convincing. You can argue that the inequality still holds based on limits, but here’s a cool picture instead!

This is a heat map of the function , where .

You can clearly see that worst case, , but most of the time, and there are some awesome patterns.

So, how does this help search faster? So yes, projection only ever produces false positives! There are two main approaches that both involve multiple projection vectors. Just one is not precise enough.

## Projection Search

Suppose we use projection vectors (whose components are sampled from for probability guarantees I don’t fully understand… look up the lemma in [3]).

For each random projection vector, get the scalar projections of the vectors that we get the nearest neighbor from (the centroids for k-means) and keep them sorted (either in an array, or a binary search tree, …).

For a given query, project it onto each of the projection vectors and look up its scalar projection in the corresponding sorted set of projections built for the initial vectors. Looking up a projection should take steps. Get a ball of elements around the position and collect them (if we want overall, we can use a heap to keep track of the best ones).

Perform the complete distance calculation for the points that were saved and select the closest one (or the closest ones).

So, the complexity of this algorithm (without the heap) is (generating the projections, projecting the centroids, for each query point, project it, look up the scalar projection, calculate the actual distance to the closest vectors around it). It turns that even for dimensions, the probability of finding the nearest cluster reaches 80% by combining projections [2]. The biggest deal is depending less on (the number of points that might be neighbors).

## Locality Sensitive Hashing

Also based on random projections, rather than look at the entire dimensions, how about generating a hash? The idea is similar to the Rabin-Karp string-matching algorithm, where expensive string comparisons are only done when required (i.e. the hashes match). Here however, if two vectors are close, their hashes should also be close (rather than uniformly distributed as required for a hash table).

So, instead of looking at the scalar projection, we can look at just its sign. If , . Looking at the projection vector as the normal vector of a hyperplane in dimensional space, the sign of the scalar projection represents which side of space the vector is in (“in front” or “in the back”). If we flip a bit (1 for a positive sign, 0 otherwise), and we have projection vectors, we can build an -bit hash. In the end, we hash the vectors to search as well as our query vectors (). We go through the vectors but only perform the expensive comparison if the difference in hash functions between a candidate and the query vector is below a certain threshold (which could be dynamic).

Unfortunately, I’m not exactly sure how LSH is implemented. I mean, yes you hash the values, put them in a set and iterate over them checking to see whether they’re close enough and check the actual distances… but I read about what seemed like other ways of doing this in [1]. I should look more into that. Stay tuned!

Here are some more references:

[1] http://www.slaney.org/malcolm/yahoo/Slaney2008-LSHTutorial.pdf (nice visuals, pretty terse)

[2] http://web.engr.oregonstate.edu/~shindler/papers/FastKMeans_nips11.pdf (look for experimental results; also describes the new k-means algorithm I’m working on with Ted)

[3] http://yima.csl.illinois.edu/psfile/CVPR10-Hashing.pdf (contains a lemma about why the projection vectors need to be normally distributed)