sklearn.metrics.pairwise.cosine_similarity() computes pairwise similarities between all samples in vectors and returns array of shape (94955, 94955). Each computation is performed on 5000-dimensional vector. That is too memory costlier for a single machine, and possibly can't scale vertically.
This problem can be solved by scaling horizontally. You can use spark for this.
We had a similar requirement to compute pairwise similarity on huge data. I wrote a vectorised cosine similarity computation code for this. It took few hours; but finally completed, in my case. There were 75,000 x 75,000 similarity comparisons with 1000-dimensional vectors.
The idea is to transform the dataframe in large number of (a,b) pairs, and apply following vectorised operations on columns. Spark will distribute pairs on different worker machines.
First, convert features into two vector columns using pyspark.ml.feature.VectorAssembler. The two vectors will be used to compute pairwise similarities.
from pyspark.ml.feature import VectorAssembler
feature_col_names = [...] # TODO - collect your feature column names
assembler = VectorAssembler(inputCols=feature_col_names, outputCol="features_vec_1")
df = assembler.transform(df)
from pyspark.sql.functions import col
df = df.withColumn("features_vec_2", col("features_vec_1"))
Compute cross-join of the two vector columns for pairwise similarities:
df = df.select("features_vec_1").crossJoin(df.select("features_vec_2"))
Apply following vectorised column operations to compute cosine similarity:
import pyspark.sql.functions as fn
df = df.withColumn("axb", fn.zip_with("features_vec_1", "features_vec_2", lambda x, y: x * y))
df = df.withColumn("axa", fn.zip_with("features_vec_1", "features_vec_1", lambda x, y: x * y))
df = df.withColumn("bxb", fn.zip_with("features_vec_2", "features_vec_2", lambda x, y: x * y))
df = df.withColumn("dot_prod", fn.expr("AGGREGATE(axb, DOUBLE(0), (a,x)->a+x)"))
df = df.withColumn("norm_1", fn.expr("sqrt(AGGREGATE(axa, DOUBLE(0), (a,x)->a+x))"))
df = df.withColumn("norm_2", fn.expr("sqrt(AGGREGATE(bxb, DOUBLE(0), (a,x)->a+x))"))
df = df.withColumn("cosine_similarity", fn.lit(fn.col("dot_prod") / (fn.col("norm_1") * fn.col("norm_2"))))
df = df.drop("axb", "axa", "bxb", "dot_prod", "norm_1", "norm_2")
You may want to apply some termination operation (like collect() or df.write) to trigger the above computations.
df.write.mode("overwrite").parquet("/file/path")
df = sparkSession.read.parquet("/file/path")
OR
similarity_list = df.collect()
At this point you've cosine similarities computed in following pairwise fashion:
+--------------+--------------+-----------------+
|features_row_1|features_row_2|cosine_similarity|
+--------------+--------------+-----------------+
| row_1| row_1| 1.0|
| row_1| row_2| 0.5|
| row_1| ... | |
| row_1| row_94955| 0.7|
| row_2| row_1| 0.5|
| row_2| row_2| 1.0|
| row_2| ... | |
| row_2| row_94955| 0.2|
| row_94955| row_1| 0.7|
| row_94955| row_2| 0.2|
| row_94955| ... | |
| row_94955| row_94955| 1.0|
+--------------+--------------+-----------------+