minhash算法中需要多少个哈希函数

时间:2013-10-31 07:58:49

标签: algorithm hash

我热衷于尝试实现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列为默认值。

显然,随着哈希数量的增加,准确度会有所提高,但有多少哈希函数是合理的?

引用博客

  

很难在我们的相似性估计上得到错误条   小于[7%],因为统计上误差条的方式   采样值缩放 - 将错误条切成两半,我们需要四个   样本次数。

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

5 个答案:

答案 0 :(得分:20)

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

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

答案 1 :(得分:14)

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

这意味着报告的78%的测量值很容易只有50%的相似性。 或者,50%的相似性很容易被报告为22%。对我而言,听起来不够准确,无法满足商业期望。

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

为什么要将哈希函数的数量减少到12? “200哈希函数”的真正含义是,每次为每个木瓦/字符串计算一个体面质量的哈希码 - 然后应用200个便宜的&快速变换,强调某些因素/将某些位置带到前面。

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

这两个“扩展”min()函数对位的选择性,以及min()最终选择的值。

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

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

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

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

感谢@BillDimm的输入&坚持指出XOR是必要的。

答案 2 :(得分:11)

您可以从universal hashing轻松获得所需内容。像Corman et al这样的流行教科书在第11.3.3页第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。

答案 3 :(得分:2)

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

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

在估算设置尺寸时,纸张显示您可以获得大约ε = 1/sqrt(f k)的相对误差,其中f是jaccard相似度,k是保留的值的数量。因此,如果您需要错误ε,则需要k=1/(fε^2),或者如果您的集合在1/3附近有相似性而您想要10%相对错误,则应保留300最小的值。

graph

答案 4 :(得分:0)

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

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

可能没有XOR对N值那么便宜,但似乎有可能以最小的额外成本获得更好的质量结果,特别是如果被散列的数据远远大于盐值。