Simhash 与汉明距离问题求解

Simhash 与汉明距离问题求解

Simhash 是一种聪明的方法,可以在一个大语料库中快速找到几乎相同的文档(或其他项目),而不必单独将每个文档和其他文档进行比较。对任何规模的语料库使用 simhash 包含两个部分:生成 simhash本身和解决汉明距离问题。二者缺一不可。与 minhash 不同,simhash 方法实际上不允许完全相似性检测,因为它敏感的相似性范围非常小。最好用近似重复检测来描述。谷歌已经为 simhash 申请了专利(但据我所知,并不是汉明距离解决方案)。

Simhash 除了它的其他限制外,实际上比 minhash 更复杂,更难调整。它真正的好处是,即使是在大语料上,它的执行速度也比 minhash 快得多,而且通常需要更少的存储空间。

simhash 的生成

simhash 是从一组特征中生成的一个位序列(通常是 64 位),其方式是如果改变这些特征的一小部分将导致产生的 hash 只改变其中的一小部分位;然而,修改所有特征将导致产生的哈希看起来完全不同(大约有一半的位将更改)。

与其他相似性检测方案一样,它的成功取决于特征的选择是否合理。假设你正在比较文档:要选择的一个典型特征可能是单词的 shingles,是否有空格以及大小写规范化等。你也可以添加其他特征,如文档标题、文本风格等。

必须使用廉价但有良好的标准(非加密)hash(如 FNV-1a)对每个特征进行散列,其位长度与预期的simhash 相同。

要将许多特征散列组合成单个 simhash,需要计算所有特征散列的位平均值。很简单。取每个特征散列的第一位并平均它们。得到的值介于 0 和 1 之间。如果小于 0.5,则将该值四舍五入为 0,否则为 1。这给了你第一个简单的想法。对每个特征散列的第二位执行相同的操作,以生成 simhash 的第二位。等等。

在这里插入图片描述

特征散列被组合成一个按位平均值,然后这些平均值被舍入以产生 simhash 的位。

可以看到,在数千个特征散列中仅更改一个特征的散列可能不会更改 simhash 中的许多(或任何)位,而更改许多特征散列将产生更大的影响。

现在比较两个文档的(估计)相似性就像确定两个 simhash 之间的汉明距离一样简单,这可以非常有效地计算出来。不幸的是,在大多数情况下,你不是将一个文档与另一个文档进行比较;而是把每一份文件和其他文件一一进行比较。即使有如此有效的汉明距离计算,要在一个 1000 万份文档的语料库中找到所有近乎重复的文档,也需要 10 M × ( 10 M − 1 ) / 2 10\text{M} \times(10\text{M}-1)/2 10M×(10M1)/2 将近 50 万亿次的比较,这是完全不可行的。这就是为什么如果不“解决汉明距离问题”,simhash 就没有多大用处,如下所述。这个问题的解决方案只适用于非常小的汉明距离:通常是 2 到 7 位的差异,这取决于你的存储容量、速度要求和语料库大小。

出于这个原因,我们进行了大量细致的调整,试图使 simhash 对那些我们认为最重要的特征敏感而对其他特征不那么敏感。这涉及到为散列选择合适的特征以及合适的正则化方法(例如小写文本和简化空格)。它还涉及赋予某些特征更高或更低的权重:因此,我们将 simhash 的每一位计算为(四舍五入的)加权平均值。

作为示例,你可能希望排除的网页中的广告文本。有一些简单的技巧可以减少广告文本的影响,比如只使用包含至少一个停止词的 shingles (因为广告通常包含很少的停用词)。但是你可能需要做更复杂的特征检测。

计算 simhash 的方法在 Google 的专利中用稍微不同的术语描述。正如他们所描述的,每一位都有一个计数,从零开始。对于每个特征散列的每一位,如果它是 1,则将该特征的权重添加到相应的计数中;如果它是零,则从计数中减去它的重量。在最终计数中,如果对应的计数大于 0,则将 simhash 位设置为 1,否则将其设置为 0。具体说明如下:

在这里插入图片描述

在本例中,提取特征并将其转换为加权哈希;然后在每个位的计数中加上(向上箭头)或减去(向下箭头)每个特征的每个位的权重。simhash 的位由每个计数总数的符号决定。特征散列是彩色的,因此你可以看到每个特征散列是如何对计数做出贡献的。

用这种方式描述算法有点麻烦,但应该清楚的是,它完全等同于按位取整的加权平均数。

我建议保持特定类型的特征权重不变。例如,不要在文件开始部分加重 shingles 的权重,以及在文件尾逐渐减少 shingles 的权重。否则,文本位置上相对较小的更改(例如在文档的前面部分插入一个额外的段落)会对 simhash 产生实质性的影响。

simash 的位长可能没有太多选择。我建议使用 64 位。 128 位对于几乎任何目的都是过大的,而 32 位对于大多数目的来说是不够的。请记住,32 位仅存储略超过 40 亿个值,这听起来可能很多,但对于碰撞已经概率很大了。假设你的哈希长度为 32 位,k 为 4。对于任何给定的指纹,在 k 位的汉明距离内有41,449 ( = C 32 4 + C 32 3 + C 32 2 + C 32 1 + C 32 0 = 35960 + 4960 + 496 + 32 + 1 =C^4_{32}+C^3_{32}+C^2_{32}+C^1_{32}+C^0_{32}=35960+4960+496+32+1 =C324+C323+C322+C321+C320=35960+4960+496+32+1)位模式,并且由于哈希而碰巧具有这些指纹之一的任何文档碰撞将被检测为相似。拥有 100,000个文档的语料库,你已经经常会遇到冲突。

图像的处理

Simhash、minhash 等可以用于检测相似的图像,前提是你选择合理的特征来生成特征哈希。特征的提取方式应确保相似的图像可能包含许多相同的特征。这远非易事。根据你想要考虑的“相似”特性,这些特征可能需要对比例、方向、颜色平衡、模糊/锐度等的变化具有鲁棒性。

你可能希望将嵌入文档中的图像视为该文档的特征,并生成它们的简单二进制哈希(如上面的示例所示)。如果这样做,请注意任何可见或其他更改都将导致不同的特征散列。

求解汉明距离问题

现在是真正聪明的一点,这避免了我们必须单独将每个 simhash 与其他每个 simhash 进行一一比较,以便在语料库中找到所有重复的。这就是为什么我们不用执行 50 万亿汉明距离计算来消除 1000 万个文档的重复数据!谷歌论文中的解释很难理解,我找不到更简单的解释,所以我在这里创建了一个。我将坚持论文中的术语和符号,以避免混淆。

假设我们是一个 Google 网络爬虫,我们发现了一个带有指纹(simhash)F 的新文档。碰巧我们以前遇到过另一个文档,这与它的指纹 G 非常相似,最多相差 k=3 位。
Fingerprint F指纹 F:根据我们收到的文件生成
在这里插入图片描述
指纹 G:来自以前遇到的文档

Differing bits highlighted两者之间只有3位不同。

如果我们将 64 位分成 6 个块,我们知道不同的位可能出现在这些块中的不超过 3 个块中:

img

然后,我们可以排列这 6 个块,以便包含不同位的三个块都移到低位:
在这里插入图片描述

置换的 F 我们称之为 F′,置换的 G 我们称之为 G′。注意,在前 3 个块(总共 32 位)中,F′ 和 G′ 是相同的。

现在,我们已经保存了一个表,其中包含我们以前遇到的所有文档的排列哈希。该表中的所有哈希都应用了完全相同的排列,并对该表进行排序。这意味着所有与我们的置换指纹 F′ 共享前 32 位的置换哈希都在表的一个连续部分中,我们可以使用二分搜索,甚至更有效的插值搜索来快速找到它们。

一旦我们找到了共享前 32 位的置换哈希,我们仍然需要比较低阶位来确定总的汉明距离。然后我们找到了类似的文件。

“等等!”,我听到你说。我们选择的排列方式可能适用于这个例子,但是其他指纹的 1、2 或 3 个不同的位可能落在不同的块中呢?这就是为什么我们实际上存储了 20 个这样的表,每个表有不同的排列,所以在每个表中,3 个块的不同组合被推入低位。 C 6 3 = 20 C^3_6=20 C63=20。当我们寻找与 F 相似的指纹时,我们必须生成它的 20 个不同排列,并在 20 个表中的每个表中进行搜索。

20 permuted tables

用于 20 个不同表的置换方案,以将不同的块推入低端位。

但是让我们回到 F′ 和 G′ 以及这个特殊的置换表。插值搜索产生了一组我们必须计算汉明距离的候选者。这是一个相当廉价的操作,但如果有很多候选人,仍然可能是耗时的。那么,我们平均应该期待多少候选者呢?

谷歌索引了大约 80 亿个文档,大约是 2 34 2^{34} 234 个。所以我们的表包含了大约 2 34 2^{34} 234 个指纹。平均而言,只有一个文档与 F′ 共享最高的 34 位,或者有 4 个文档共享最高的 32 位(因为 2 34 − 32 2^{34-32} 23432)。所以平均来说,我们只需要计算 4 个候选者的汉明距离(在这个表中)。很好!

还有另一种方法可以解释这一点:假设我们有 2 34 2^{34} 234 个真正随机的指纹,按顺序排列。如果我们只取这些指纹的前 34 位(下面的绿色阴影),它们将相当于“几乎是一个计数器”,因为大多数可能的数字将被表示,只有少数可能的数字将被重复。不过,如果我们只取最低有效的 30 位,它们看起来“几乎是随机的”。

A small section of the permuted table showing how the high bits are almost a counter, and the low bits appear virtually random

表的一小部分。上面绿色的 34 位是“几乎是一个计数器”。低 30 位看起来几乎是随机的。还有一个 G′,它的不同部分被突出显示。

但是我们只匹配前 32 位,所以每个查询平均找到 4 个候选。

Three blocks are not quite 'almost a counter'

因为 64 不能被 6 整除,所以我们的块是11,11,11,11,10 和 10 位宽。这意味着我们的不同表将需要用不同的高端位数来搜索:31、32 或 33(取决于排列),平均产生 8、4 或 2个候选者。

通过对 20 个表中的每一个表执行相同的廉价搜索,我们可以确定找到了每个最多相差 k=3 位的指纹。

表的存储

为了快速响应,表应该保存在内存中。每个置换表应该只存储置换哈希,并且一个单独的表应该包含从未置换哈希到文档 ID 的映射。

哈希表最紧凑的存储是排序列表(Google 的论文描述了如何进一步压缩该表)。如果你想在执行过程中插入新条目,那么你将需要树(需要更多的存储空间),或者你可以将大多数条目保留在排序列表中,而将新条目保留在树中,并选中两者,然后定期将树合并回排序列表中。

如果你不必在新文档到达时立即检测相似性,那么可以获得很好的效率。不仅可以将这些表存储为更紧凑的排序列表,还可以通过查找共享前 d 位的每一组哈希置换哈希,并检查它们的汉明距离,在一次遍历所有表的过程中找到所有相似之处。

在 Google 的论文中,他们推荐了一种更进一步压缩排序列表的方法,利用这样一个事实,即许多高端数据从一个条目到下一个条目变化不大。我从来没有为此烦恼过,但如果你真的在存储方面遇到困难,请记住这一点。

其他配置

除了上面讨论的 6 块配置之外,还有其他配置在 k=3 时提供不同的内存/处理权衡。例如,4 个块(每个16 位)只需要 4 个置换表(因为 C 4 3 = 4 C^3_4=4 C43=4)。
在这里插入图片描述

四个块,每个块 16 位,k=3

考虑到我们只对 16 位进行表搜索,我们可以预期每个表的每个查询平均有 2 34 − 16 = 262144 2^{34-16}=262144 23416=262144 个结果,或者每个查询总共有大约一百万个汉明检查。

在上面的例子中,我们匹配 16 位,剩下 48 位不匹配。我们可以进一步细分每个表,将 48 个不匹配位分成 4 个块,每个块 12 位,然后依次排列,总共 16 个表:

4 blocks of 16 bits, with the non-match bits further subdivided into 4 blocks of 12 bits

现在我们可以使用 16 个表来匹配前 28 位,平均每个查询得到 2 34 − 28 = 64 2^{34-28}=64 23428=64 个结果。

Google 的论文中描述了几个这样的表变体,每个变体在所需表的数量和每个查询的平均候选匹配数之间提供了不同的权衡。

许多应用程序不需要索引 2 34 2^{34} 234 个文档,并且可以稍微增加 k。例如,假设你只希望索引大约 2 23 2^{23} 223(约 800 万)个文档。通过将散列划分为 8 个块( C 8 6 = 28 C^6_8=28 C86=28 个表),可以很容易地将可能的汉明距离增加到 k=6。对于每个表,每个查询平均得到 2 23 − 16 = 128 2^{23-16}=128 22316=128 个候选项,这意味着每个查询总共需要执行大约 3500 ( 128 × 28 = 3584 128 \times 28=3584 128×28=3584)个汉明距离检查。

An example permutation scheme for k=6 and d=23

一个置换方案检测汉明距离高达 6 位的例子,对于有 800 万份文件的语料库,这将仍然是合理有效的。

你需要找到一个最适合你的表安排,决定 simhash 的位长度;测试从文档生成的 simhash,以确定需要允许多少位汉明距离(k);并确定你的语料库将增长多大,因此有多少位将是“几乎一个计数器”。然后你就可以找出一个合适的排列方案来权衡存储大小和处理时间。

如果你发现需要检测比这种方法所能容纳的更多的位差,那么你可能正在寻找完全相似性检测,而不是接近重复检测,并且应该研究 minhash 之类的算法。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值