【问题标题】:How does the HyperLogLog algorithm work?HyperLogLog 算法是如何工作的?
【发布时间】:2012-09-01 20:42:08
【问题描述】:

我最近在业余时间学习了不同的算法,我遇到的一个看起来很有趣的算法叫做 HyperLogLog 算法——它估计一个列表中有多少个独特的项目。

这对我来说特别有趣,因为当我看到“基数”值(直到最近我一直认为它是计算而不是估计的)时,它让我回到了我的 MySQL 时代。

所以我知道如何在 O(n) 中编写一个算法来计算数组中有多少唯一项。我是用 JavaScript 写的:

function countUniqueAlgo1(arr) {
    var Table = {};
    var numUnique = 0;
    var numDataPoints = arr.length;
    for (var j = 0; j < numDataPoints; j++) {
        var val = arr[j];
        if (Table[val] != null) {
            continue;
        }
        Table[val] = 1;
        numUnique++;
    }
    return numUnique;
}

但问题是我的算法虽然 O(n) 使用了大量内存(将值存储在 Table 中)。

我一直在阅读this paper,了解如何在 O(n) 时间内计算列表中的重复项并使用最少的内存。

它解释了通过散列和计数位或某种东西可以在一定概率内(假设列表是均匀分布的)估计列表中唯一项目的数量。

我已经阅读了这篇论文,但我似乎无法理解它。谁能给一个更外行的解释?我知道哈希是什么,但我不明白它们是如何在这个 HyperLogLog 算法中使用的。

【问题讨论】:

  • 这篇论文(research.google.com/pubs/pub40671.html)也总结了HyperLogLog算法和一些改进。我认为它比原始论文更容易理解。
  • 只是对命名法的提示:有些人使用集合一词来描述独特项的集合。对他们来说,如果您改用术语列表或数组,您的问题可能会更有意义。

标签: database algorithm math data-structures hyperloglog


【解决方案1】:

直觉是,如果您的输入是一大组随机数(例如散列值),它们应该在一个范围内均匀分布。假设范围最大为 10 位以表示最大为 1024 的值。然后观察最小值。假设它是 10。那么基数估计约为 100 (10 × 100 ≈ 1024)。

当然要阅读论文,了解真正的逻辑。

另一个很好的示例代码解释可以在这里找到:
Damn Cool Algorithms: Cardinality Estimation - Nick's Blog

【讨论】:

  • 赞成该死的酷算法博客文章的链接。这真的帮助我掌握了算法。
【解决方案2】:

这个算法背后的主要技巧是,如果你观察一个随机整数流,看到一个二进制表示以某个已知前缀开头的整数,那么流的基数很有可能是 2^(size of前缀)。

也就是说,在随机整数流中,约 50% 的数字(二进制​​)以“1”开头,25% 以“01”开头,12.5% 以“001”开头。这意味着,如果您观察随机流并看到“001”,则该流的基数为 8 的可能性更高。

(前缀“00..1”没有特殊含义。它的存在只是因为在大多数处理器中很容易找到二进制数中的最高有效位)

当然,如果你只观察到一个整数,这个值错误的可能性就很高。这就是算法将流分成“m”个独立子流并保持每个子流的可见“00...1”前缀的最大长度的原因。然后,通过取每个子流的平均值来估计最终值。

这就是这个算法的主要思想。有一些缺失的细节(例如,对低估计值的修正),但在论文中都写得很好。对不起,糟糕的英语。

【讨论】:

  • "这个流的基数为 8 的可能性更高" 你能解释一下为什么 000 意味着预期的试验次数 2^3。我试图计算试验次数的数学期望,假设我们至少有一次运行有 3 个零并且没有运行有 4 个零......
  • 直到我读到这篇文章才完全理解这篇论文。现在说得通了。
  • @yura 我知道这是一个非常古老的评论,但它可能对其他人有用。他说“也就是说,在随机的整数流中,(...) 12,5% 以“001”开头。”可能的基数是 8,因为 12.5% 代表整个流的八分之一。
  • 这是我读过的对 hll 最好/基本的解释。
  • @DimanNe 请注意,我们谈论的是一个 随机 数字流,通常是通过对原始流应用哈希函数产生的,虽然不是严格随机的,但它是一个很好的足够的近似。在这种情况下,我们假设每个位有 50% 的机会为 0 或 1,因此使用 uint16 或 uint64 不会对前缀长度的预期值产生太大影响(同样假设 expected cardinality &lt;&lt; 2^(bit length))。
【解决方案3】:

HyperLogLog 是probabilistic data structure。它计算列表中不同元素的数量。但与一种直接的方式(拥有一个集合并向集合中添加元素)相比,它以一种近似的方式做到这一点。

在查看 HyperLogLog 算法是如何做到这一点之前,必须了解您为什么需要它。直截了当的问题是它消耗了O(distinct elements) 的空间。为什么这里有一个大的 O 符号,而不仅仅是不同的元素?这是因为元素可以有不同的大小。一个元素可以是1 另一个元素"is this big string"。因此,如果您有一个巨大的列表(或大量元素),它将占用大量内存。


概率计数

如何才能对许多独特元素进行合理估计?假设您有一个长度为m 的字符串,它由{0, 1} 组成,概率相等。它以 0、2 个零、k 个零开始的概率是多少?它是1/21/41/2^k。这意味着,如果您遇到了带有k 零的字符串,那么您已经大致查看了2^k 元素。所以这是一个很好的起点。拥有一个均匀分布在02^k - 1 之间的元素列表,您可以计算二进制表示中最大零前缀的最大数量,这将为您提供合理的估计。

问题在于,从0 t 2^k-1 中均匀分布数字的假设太难实现(我们遇到的数据大多不是数字,几乎从不均匀分布,并且可以在任何值之间。但是使用good hashing function,您可以假设输出位将均匀分布,并且大多数散列函数的输出在02^k - 1 之间(SHA1 为您提供02^160 之间的值)。所以我们到目前为止已经实现的是,我们可以通过只存储一个大小的log(k)位来估计最大基数为k位的唯一元素的数量。缺点是我们的估计有很大的差异。很酷我们几乎创建了1984's probabilistic counting 论文的东西(估计有点聪明,但我们仍然接近)。

LogLog

在进一步进行之前,我们必须了解为什么我们的第一个估计值不是那么好。其背后的原因是高频 0 前缀元素的随机出现会破坏一切。改进它的一种方法是使用许多散列函数,为每个散列函数计算最大值,最后将它们平均。这是一个绝妙的主意,可以改进估算值,但LogLog paper 使用了稍微不同的方法(可能是因为散列有点昂贵)。

他们使用一个散列,但将其分成两部分。一个称为桶(桶的总数为2^x),另一个 - 与我们的哈希基本相同。我很难理解发生了什么,所以我举个例子。假设您有两个元素,并且您的哈希函数将值从 02^10 产生了 2 个值:344387。你决定有 16 个桶。所以你有:

0101 011000  bucket 5 will store 1
0110 000011  bucket 6 will store 4

通过拥有更多的桶,您可以减少方差(您会使用更多的空间,但它仍然很小)。使用数学技能,他们能够量化错误(即1.3/sqrt(number of buckets))。

HyperLogLog

HyperLogLog 没有引入任何新想法,但大多使用大量数学来改进之前的估计。研究人员发现,如果你从桶中删除 30% 的最大数字,你会显着提高估计值。他们还使用了另一种算法来平均数字。这篇论文很重数学。


我想以最近的一篇论文结尾,其中显示了improved version of hyperLogLog algorithm(直到现在我还没有时间完全理解它,但也许以后我会改进这个答案)。

【讨论】:

  • 理论上我假设k zeroes 不是什么特别的东西。您可以改为寻找k ones,逻辑将相同,或者甚至寻找{0,1}k length 字符串,但取一个这样的字符串并坚持下去?因为在这种二进制字符串的情况下,它们都具有相同的 1/2^k 概率?
  • HyperLogLog 不会删除 30% 的最大数字。这也是在 LogLog 论文中描述的 SuperLogLog 算法的思想。 HyperLogLog 算法的主要思想是使用调和平均值而不是 SuperLogLog 和 LogLog 使用的几何平均值来平均二的幂。
猜你喜欢
  • 2017-10-28
  • 2013-12-04
  • 1970-01-01
  • 1970-01-01
  • 2022-07-02
  • 2011-09-16
  • 1970-01-01
  • 2014-07-04
  • 1970-01-01
相关资源
最近更新 更多