Spark-based approximate nearest neighbors (ANN) using locality-sensitive hashing (LSH)
Spark's MLlib doesn't yet support locality-sensitive hashing or nearest neighbor search. While there are LSH Spark packages available for a variety of distance measures, it has been open question how to support multiple distance measures and hashing schemes behind a unified interface. This library presents one possible way to do that.
You can link against this library (for Spark 1.6+) in your program at the following coordinates:
Using SBT:
libraryDependencies += "com.github.karlhigley" %% "spark-neighbors" % "0.2.2"
Using Maven:
<dependency>
<groupId>com.github.karlhigley</groupId>
<artifactId>spark-neighbors_2.10</artifactId>
<version>0.2.2</version>
</dependency>
This library can also be added to Spark jobs launched through spark-shell or spark-submit by using the --packages command line option. For example, to include it when starting the spark shell:
$ bin/spark-shell --packages com.github.karlhigley:spark-neighbors_2.10:0.2.2
Unlike using --jars, using --packages ensures that this library and its dependencies will be added to the classpath. The --packages argument can also be used with bin/spark-submit.
ANN models are created using the builder pattern:
import com.github.karlhigley.spark.neighbors.ANN
val annModel =
new ANN(dimensions = 1000, measure = "hamming")
.setTables(4)
.setSignatureLength(64)
.train(points)
An ANN model can compute a variable number of approximate nearest neighbors:
val neighbors = model.neighbors(10)
The supported distance measures are "hamming", "cosine", "euclidean", "manhattan", and "jaccard". All distance measures allow the number of hash tables and the length of the computed hash signatures to be configured as above. The hashing schemes for Euclidean, Manhattan, and Jaccard distances have some additional configurable parameters:
This hash function depends on a bucket or quantization width. Higher widths lead to signatures that are more similar:
val annModel =
new ANN(dimensions = 1000, measure = "euclidean")
.setTables(4)
.setSignatureLength(64)
.setBucketWidth(5)
.train(points)
Minhashing requires two additional parameters: a prime larger than the number of input dimensions and the number of minhash bands. The prime is used in the permutation functions that generate minhash signatures, and the number of bands is used in the process of generating candidate pairs from the signatures.
val annModel =
new ANN(dimensions = 1000, measure = "jaccard")
.setTables(4)
.setSignatureLength(128)
.setPrimeModulus(739)
.setBands(16)
.train(points)
Sign random projection:
Scalar random projection:
Minhash:
Survey papers:
Performance evaluation and tuning: