用 Spark's MinHashLSH进行文本语料去重

发布时间 2023-07-09 15:38:03作者: 华东博客

(1)MinHashLSH进行文本去重的算法原理

MinHash (最小哈希) 是一种用于估计两个集合的 Jaccard 相似度的方法,而 MinHashLSH (局部敏感哈希) 则是一种使用 MinHash 来近似查找相似项的技术。

MinHash 算法基于以下观察:如果我们随机排列所有可能的元素,然后对每个集合取其第一个元素,那么这个元素相同的概率等于两个集合的 Jaccard 相似度。

假设我们有两个集合 \(A\) 和 \(B\),它们的 Jaccard 相似度为:

\[
J(A,B) = \frac{|A \cap B|}{|A \cup B|}
\]

MinHash 函数 \(h\) 对于集合 \(S\) 的定义为:

\[
h(S) = \min_{x \in S} h'(x)
\]

其中 \(h'\) 是一个哈希函数,用于将元素映射到一个大的整数集合。

如果我们有 \(k\) 个这样的哈希函数,我们可以得到一个签名 \(sig(S)\),它是一个包含 \(k\) 个元素的向量,每个元素都是通过一个不同的哈希函数得到的最小哈希值:

\[
sig(S) = [h_1(S), h_2(S), \ldots, h_k(S)]
\]

两个集合的签名之间的相似度可以通过计算匹配的哈希值的比例来估计。

然而,如果我们有大量的集合,计算所有集合对的签名相似度将非常耗时。这就是 LSH 算法的作用。在 LSH 中,我们将签名划分为多个段,然后将每个段作为一个桶的键。这样,只有在至少一个桶中相遇的集合才会被认为是候选相似项。

具体来说,如果我们有 \(n\) 个集合,每个集合的签名长度为 \(k\),我们可以将签名划分为 \(b\) 个段,每个段的长度为 \(r\),其中 \(br = k\)。然后,对于每个段,我们都计算一个桶的键,并将所有具有相同键的集合放入同一个桶中。

在这种情况下,两个集合被认为是候选相似项的概率为:

\[
P(A, B) = 1 - (1 - [J(A,B)]^r)^b
\]

所以,通过合理选择 \(b\) 和 \(r\),我们可以控制我们愿意接受的相似度阈值和误报率。

 

(2)用 Spark's MinHashLSH(具有 10 个哈希值)进行文本语料去重

在Apache Spark中,MinHashLSH是一种用于处理大规模数据的局部敏感哈希(LSH)的实现。在处理文本数据时,我们通常首先将文本转化为词袋(Bag of Words)表示,然后将词袋表示转化为特征向量。之后,我们可以使用MinHashLSH来计算这些特征向量的哈希值,然后进行去重。

首先,我们需要导入所需的库,并初始化Spark会话:

from pyspark.sql import SparkSession
from pyspark.ml.feature import HashingTF, IDF, Tokenizer
from pyspark.ml.feature import MinHashLSH

spark = SparkSession.builder.appName("TextDeduplication").getOrCreate()

然后,我们需要加载我们的文本数据。假设我们有一个CSV文件,其中包含一列名为"text"的文本数据:

df = spark.read.csv("texts.csv", inferSchema=True, header=True)

接下来,我们需要将文本数据转化为词袋表示。我们可以使用Tokenizer和HashingTF来完成这个任务:

tokenizer = Tokenizer(inputCol="text", outputCol="words")
wordsData = tokenizer.transform(df)

hashingTF = HashingTF(inputCol="words", outputCol="rawFeatures", numFeatures=20)
featurizedData = hashingTF.transform(wordsData)

然后,我们可以使用MinHashLSH来计算特征向量的哈希值:

mh = MinHashLSH(inputCol="rawFeatures", outputCol="hashes", numHashTables=10)
model = mh.fit(featurizedData)
transformed = model.transform(featurizedData)

最后,我们可以通过比较哈希值来进行去重:

deduplicated = transformed.dropDuplicates(["hashes"])

这样,我们就可以得到去重后的文本数据了。