局部敏感哈希(LSH)相似度(杰卡德)分析TopN
概念
局部敏感哈希,英文locality-sensetive hashing,常简称为LSH。局部敏感哈希在部分中文
文献中也会被称做位置敏感哈希。LSH是一种哈希算法,最早在1998年由Indyk在[1]上提出。
不同于我们在数据结构教材中对哈希算法的认识,哈希最开始是为了减少冲突方便快速增删改
查,在这里LSH恰恰相反,它利用的正式哈希冲突加速检索,并且效果极其明显。LSH主要运
用到高维海量数据的快速近似查找。近似查找便是比较数据点之间的距离或者是相似度。因
此,很明显,LSH是向量空间模型下的东西。一切数据都是以点或者说以向量的形式表现出来的
思想
LSH的主要思想是,高维空间的两点若距离很近,那么设计一种哈希函数对这两点进行哈希
值计算,使得他们哈希值有很大的概率是一样的。同时若两点之间的距离较远,他们哈希值相
同的概率会很小。
使用场景
海量数据相似度
海量文本、页面近似检索
海量视频、音频数据近似检索
Spark ml MinHashLSH (scala代码)
- 读取到数据(df: DataFrame)
- 将数据各维度字段类型转换为double
//将所有列转换为double类型
val cols = df.columns.map(f => col(f).cast(DoubleType))
var newDF = df.select(cols: _*)
- 将各维度转换为特征向量(类型:org.apache.spark.ml.linalg.Vector)
val assembler = new VectorAssembler()
.setInputCols(featuresArr)
.setOutputCol("features")
newDF = assembler.transform(newDF)
- 创建MinHashLSH对象,训练模型
val mh = new MinHashLSH()
.setNumHashTables(numHashTables) // 设置最小HashTables
.setInputCol("features") // 设置输入特征向量列
.setOutputCol("hashValues") //设置转换后输出列名
val mhModel = mh.fit(newDF) //训练模型
- 将数据集复制两个dataframe 对标识列重命名
val dfA = newDF.withColumnRenamed(labelCol, "label1").cache()
println("dfA ===========")
dfA.show()
val dfB = newDF.withColumnRenamed(labelCol, "label2").cache()
println("dfB ===========")
dfB.show()
- 用模型进行数据预测求两两相似度
val predictionsDF = mhModel.approxSimilarityJoin(dfA, dfB, 1.0, "JaccardDistance")
println("predictionsDF ===========" + predictionsDF.count())
predictionsDF.show()
- 从预测结果dataframe中拿出标识列
predictionsDF.createOrReplaceTempView("predictions")
val pairs = sparkSession.sql("select datasetA.label1, datasetB.label2, JaccardDistance from predictions where JaccardDistance != 0").cache()
println("pairs ===========")
pairs.show()
- 对结果进行TopN截取
val finalDF = pairs.rdd
.map{row =>
(row.getAs[String]("label1"),(row.getAs[String]("label2"),row.getAs[Double]("JaccardDistance")))
}
.groupBy(_._1)
.map{ row=>
val ajbh = row._1
val top20 = row._2.toArray.sortWith(_._2._2 > _._2._2).take(topN).map{row =>
row._2
}
(row._1,top20.mkString(","))
}
.toDF(labelCol, "TOP"+ topN)