【问题标题】:How many hash functions are required in a minhash algorithmminhash算法需要多少个hash函数
【发布时间】:2020-06-23 22:59:40
【问题描述】:

我热衷于尝试实施 minhashing 以查找接近重复的内容。 http://blog.cluster-text.com/tag/minhash/ 写得很好,但问题是您需要在文档中的带状疱疹上运行多少散列算法才能获得合理的结果。

上面的博文提到了 200 种散列算法。 http://blogs.msdn.com/b/spt/archive/2008/06/10/set-similarity-and-min-hash.aspx 将 100 列为默认值。

显然随着hash数量的增加,准确率也会提高,但是多少hash函数才是合理的呢?

引自博客

在我们的相似性估计中很难得到太多的误差线 小于 [7%],因为统计上的误差条 采样值刻度——要将误差条减半,我们需要四个 数倍的样本。

这是否意味着将哈希数减少到 12 (200 / 4 / 4) 会导致 28% (7 * 2 * 2) 的错误率?

【问题讨论】:

    标签: algorithm hash


    【解决方案1】:

    生成 200 个散列值的一种方法是使用良好的散列算法生成一个散列值,并通过将良好的散列值与 199 组具有相同长度的随机位进行异或运算来廉价地生成 199 个值(即,如果您的良好哈希是 32 位,则构建一个包含 199 个 32 位伪随机整数的列表,并将每个良好的哈希与 199 个随机整数中的每一个进行异或)。

    如果您使用无符号整数(有符号整数很好),不要简单地旋转位以廉价地生成哈希值 - 这通常会一遍又一遍地选择相同的瓦。将位向下循环一位与除以 2 并将旧的低位复制到新的高位位置相同。大约 50% 的好散列值的低位为 1,因此当低位旋转到高位位置时,它们将具有巨大的散列值,而不会祈祷成为最小散列。当您移动一位时,其他 50% 的良好哈希值将简单地等于它们的原始值除以 2。除以 2 不会改变哪个值最小。因此,如果具有良好散列函数的最小散列值恰好在低位中具有 0(50% 的可能性),那么当您移动一位时,它将再次给出最小散列值。举个极端的例子,如果来自良好散列函数的具有最小散列值的瓦的散列值恰好为 0,那么无论您旋转多少位,它都将始终具有最小散列值。有符号整数不会出现此问题,因为最小哈希值具有极端负值,因此它们往往在最高位有一个 1,后跟零(100...)。因此,只有最低位为 1 的哈希值在向下旋转一位后才有机会成为新的最低哈希值。如果具有最小哈希值的木瓦的最低位为 1,则在向下旋转一位后,它将看起来像 1100...,因此几乎可以肯定它会被具有类似 10 的值的不同木瓦击败...旋转后,避免了同一个木瓦以50%的概率连续两次被捡到的问题。

    【讨论】:

    • 我的论点是,旋转哈希中的位(首先)比异或更有可能强调不同的带状疱疹。您的 cmets 大约“大约 50%”适用于任何按位操作。因为任何单个位都有 50% 的机会被设置。旋转选择不同的位为最重要,XOR 改变哪些值/范围实际上是“最小”,但仍将主要选择那些最高位的函数。 因此,纯粹基于 XOR 的方法将始终在相同的最高位上进行最显着选择,并且可能无法有效地使用其他哈希位。
    • 我认为您对按位旋转的理解和逻辑在这里是错误的。例如,右移 3 位将 LSB 的 3 位放在前一个 MSB 的前面。选择其中的“最少”将针对完全不同的带状疱疹。 50% 的 LSB 是 1 和 50% 是 0 的论点是虚假的,就像提到“好”哈希值一样——这有点像那个等待部署降落伞但他到达 10 英尺并说没关系的人——“我可以从这里跳下去!”。类似的逻辑错误——他认为 10 英尺没有区别,你认为 1 位没有区别。
    • 但是,我确实喜欢您的异或方法,并且很乐意将它与旋转结合使用,与按位随机播放或旋转结合选择的伪随机整数表应该是一个很好的可靠的基本方法.
    • @ThomasW,您当然可以在 XORing 中添加位旋转,但是对于额外的 CPU 周期,您实际上并没有得到太多。异或时给出最小散列的瓦将是与被异或的随机数具有最高共同位的瓦,因为它们的共同位将在结果中给出零(平局与随机数相同的高位的数量使低位发挥作用)。如果您的 199 个随机数在高位中非常不同,那么所选的带状疱疹将非常不同(随机)。
    • @ThomasW 感谢您分享您的代码。我之前的所有 cmets 在查找最小哈希值时都假定进行无符号整数比较。使用有符号整数而不是无符号整数可以避免我描述的问题。使用旋转生成 32 个哈希在使用有符号比较时会产生 1 个重复,但在使用无符号比较时会产生 13 个重复(并且这些重复大多是相邻的值,即相差一位旋转的值,这与我对问题的描述一致)。我将更新我的答案,以明确我对位旋转的批评仅适用于无符号整数。
    【解决方案2】:

    差不多.. 但 28% 将是“错误估计”,这意味着报告的测量结果经常会不准确 +/- 28%。

    这意味着报告的 78% 测量值很容易来自仅 50% 的相似性。 或者,50% 的相似性很容易被报告为 22%。对我来说,这对于业务预期来说听起来不够准确。

    从数学上讲,如果您报告两位数,则第二个应该是有意义的。

    为什么要将哈希函数的数量减少到 12 个? “200 个哈希函数”的真正含义是,为每个 shingle/string 计算一次质量不错的哈希码 - 然后应用 200 个廉价且快速的转换,以强调某些因素/将某些位放在前面。

    我建议将按位旋转(或洗牌)和XOR 操作结合起来。每个哈希函数可以组合旋转一定数量的位,然后通过随机生成的整数进行异或。

    这既“传播”了 min() 函数在位周围的选择性,也“传播”了 min() 最终选择的值。

    旋转的基本原理是“min(Int)”将在 256 次中的 255 次中仅选择 8 个最高有效位。只有当所有高位都相同时,低位才会在比较中产生任何影响。因此,扩展对于避免过度强调瓦中的一两个字符很有用。

    XOR 的基本原理是,就其本身而言,按位旋转 (ROTR) 可以 50% 的时间(当从左侧移入 0 位时)收敛到零,这将导致“单独的”哈希函数显示出一种不希望的趋势,即一起向零重合 - 因此他们最终选择相同的瓦而不是独立的瓦的过度趋势。

    有符号整数有一个非常有趣的“按位”怪癖,其中 MSB 为负数,但所有后续位均为正数,这使得 有符号整数的旋转收敛趋势不太明显 -- unsigned 是显而易见的。无论如何,在这些情况下仍必须使用 XOR。

    Java 内置了 32 位哈希码。如果您使用 Google Guava 库,则可以使用 64 位哈希码。

    感谢@BillDimm 的投入和坚持指出异或是必要的。

    【讨论】:

    • 按照比尔在下面所说的,我想建议用“异或”补充“旋转”,以改变哪些位是“最具选择性”的,并随机化“最小”函数选择的值.谢谢@BillDimm!
    • @BillDimm 和我在我的 Java 示例代码中调查了哈希函数的实际行为——结果证明 ROTATE 应该始终与 XOR 结合使用。 ROTATE 本身会产生过多的冲突,因为 50% 的单位旋转会收敛到零,“最小”可能倾向于选择相同的 shingles。感谢 Bill 帮助您指出这一点!
    • 我认为XOR方法也不好。当然,minhash 良好属性的证明不适用于此类哈希函数,因为它们要求哈希函数来自 2-uniform family,但事实并非如此。
    • 谢谢@RobertObryk。根据我对en.wikipedia.org/wiki/Universal_hashing 的阅读,来自均匀分布随机数的加法为所有散列函数提供了强独立性,来自均匀分布随机数的 XOR 仅在散列使用按位时才提供强独立性,即模 M 是2. 所以我会读罗伯特的建议说,ADDITION 可能比 MODULO 更好。有没有cmets?
    • 如果您正在构建 Minhash 算法,en.wikipedia.org/wiki/Tabulation_hashingen.wikipedia.org/wiki/Rolling_hash 文章可能也值得一读。
    【解决方案3】:

    您想要的可以从universal hashing轻松获得。像Corman et al 这样的流行教科书在第 11.3.3 pp 265-268 节中作为非常易读的信息。简而言之,您可以使用以下简单等式生成哈希函数族:

    h(x,a,b) = ((ax+b) mod p) mod m
    
    • x 是你要散列的键
    • a 是任何奇数,您可以在 1 到 p-1 之间进行选择。
    • b 是您可以在 0 到 p-1 之间选择的任意数字。
    • p 是一个大于 x 的最大可能值的素数
    • m 是您想要的哈希码 + 1 的最大可能值

    通过选择不同的 a 和 b 值,您可以生成许多相互独立的哈希码。

    这个公式的优化版本可以在 C/C++/C#/Java 中实现如下:

    (unsigned) (a*x+b) >> (w-M)
    

    在这里, - w 是机器字的大小(通常为 32) - M 是您想要的哈希码的大小(以位为单位) - a 是适合机器字的任何奇数 - b 是任何小于 2^(w-M) 的整数

    以上适用于对数字进行哈希处理。要对字符串进行哈希处理,请使用 GetHashCode 等内置函数获取哈希码,然后在上述公式中使用该值。

    例如,假设您需要 200 个 16 位的字符串 s 哈希码,那么可以编写以下代码作为实现:

    public int[] GetHashCodes(string s, int count, int seed = 0)
    {
        var hashCodes = new int[count];
        var machineWordSize = sizeof(int);
        var hashCodeSize = machineWordSize / 2; 
        var hashCodeSizeDiff = machineWordSize - hashCodeSize;
        var hstart = s.GetHashCode();
        var bmax = 1 << hashCodeSizeDiff;
        var rnd = new Random(seed);     
    
        for(var i=0; i < count; i++) 
        {
            hashCodes[i] = ((hstart * (i*2 + 1)) + rnd.Next(0, bmax)) >>  hashCodeSizeDiff;
        }
    }
    

    注意事项:

    1. 我使用哈希码字长作为机器字长的一半,在大多数情况下为 16 位。这并不理想,而且发生碰撞的可能性要大得多。这可以通过将所有算术升级到 64 位来使用。
    2. 通常您希望在上述范围内随机选择 a 和 b。

    【讨论】:

    • 通用散列很好,但是这个代码不遵守散列码的规则。哈希应该是确定性的,并且此实现使用伪随机数生成器。
    • @Snives - 你是对的。将此更改为使用随机种子。或者,也可以使用简单的 mod 操作生成的随机数。
    • 在 CLRS 书中,a 不需要是奇数。你有什么理由让这里变得奇怪吗?
    • 嗨,关于这个的一个简单的问题。如果必须散列属于 GF(2^q) 的有限域的非常大的数,应该选择哪种方法?这个数字是否应该首先转换为位进行散列?
    • 您好,类似的python代码可以在stackoverflow.com/a/68715556/4564156找到
    【解决方案4】:

    只需使用 1 个哈希函数!(并保存 1/(f ε^2) 最小值。)

    查看this article,了解最先进的实践和理论界限。它有这个漂亮的图表(下图),解释了为什么您可能只想使用一个 2 独立哈希函数并保存 k 最小值。

    在估计集合大小时,论文表明您可以获得大约 ε = 1/sqrt(f k) 的相对误差,其中 f 是 jaccard 相似度,k 是保留值的数量。所以如果你想要错误ε,你需要k=1/(fε^2),或者如果你的集合在1/3附近有相似性并且你想要10%相对错误,你应该保持300的最小值。

    【讨论】:

    • 感谢您关注需要多少散列函数/值的原始问题。但我的目标不是找到与 minhash 的相似之处吗?那么我如何使用相似性来估计我应该保留的值的数量?另外:文档中的带状疱疹或行数不应该对 k 产生影响吗?
    • 你说得对,你需要一个相似度估计来找到相似度,这有点烦人。那是因为我们要求的是相对误差而不是附加误差。请注意,有一个下限就足够了,因此您可以将其设置为您愿意承受的最低值。你也可以先做一个更昂贵的实验来得到一个粗略的想法。最后,您是对的,当您的文档增长时不需要更多值,这令人惊讶,但这就是草图的魔力:-)
    【解决方案5】:

    似乎另一种获得 N 个好的散列值的方法是用 N 个不同的盐值对同一个散列进行加盐。

    在实践中,如果第二次应用盐,您似乎可以散列数据,然后“克隆”散列器的内部状态,添加第一个盐并获得第一个值。您将此克隆重置为干净的克隆状态,添加第二个盐,并获得第二个值。冲洗并重复所有 N 项。

    可能不如对 N 值的 XOR 便宜,但似乎有可能以最小的额外成本获得更高质量的结果,尤其是当被散列的数据远大于盐值时。

    【讨论】:

      猜你喜欢
      • 2012-06-25
      • 1970-01-01
      • 1970-01-01
      • 2012-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多