LogLogCounting 基数估计算法

介绍

基数估计算法(Cardinality Estimation Algorithm)是基于概率统计理论的估算给定数据集中不重复元素基数的算法。
它是一种基于概率统计理论所设计的概率算法,克服了精确基数计数算法的诸多弊端(如内存需求过大或难以合并等),同时可以通过一定手段将误差控制在所要求的范围内。

什么是基数?

基数指的是一个集合(这里的集合可以包含重复元素,不是集合论中定义的集合)中不同元素的个数,例如集合{1, 2, 3, 3, 4, 5, 5, 6, 6, 6}有9个元素,但是其中不重复的元素只有 1、 2、 3、 4、 5、 6,所以它的基数是6。如果一个集合是有限集,则其基数是一个自然数。

应用场景

一个网站中有10个链接,我们需要统计这10个链接分别被多少个独立访客点击过,即每个链接的UV数量。

基数统计算法

数据的收集和存储不是这篇文章的重点,假设我们已经拿到了数据:10个链接的用户访问数据集合,我们如何获取这些集合的基数呢?

传统的方法

  • 基于bitmap的实现
    • 由于每个用户对于每个链接只有 访问/未访问 这两种状态, 所以我们将每一个独立用户映射到一个bit位上,这样可以使用一串bit数组来表示一个链接的访问基数,计算访问基数的时候只需要计算bit数组中1的数量就可以了。
    • 优点
      • 可以精确地计算出每个链接的UV数
      • 可以高效地进行合并,例如需要计算多个链接的UV数之和,只需要按位进行或运算。
    • 问题
      • 对于每一个链接,都需要维护一个bit数组,这个数组的大小与集合中的元素个数无关,而是与基数的上限有关,计算上限为1亿的基数,需要12.5M字节的bitmap。最大的问题是即使一个链接仅仅只有一个UV,也要为其分配12.5M字节大小的数组,这样会造成巨大的浪费。

LogLogCounting

  • LogLogCounting

    • LogLog代表空间复杂度,这种算法的空间复杂度仅有O(log2(log2(Nmax))),可以通过KB级内存估计数亿级别的基数
  • 均匀随机化

    • 这个算法需要选取一个哈希函数H应用于所有元素,然后对哈希值进行基数估计。H必须满足如下条件:
      1. H的结果具有很好的均匀性,也就是说无论原始集合元素的值分布如何,其哈希结果的值几乎服从均匀分布
      2. H的碰撞几乎可以忽略不计。也就是说我们认为对于不同的原始值,其哈希结果相同的概率非常小以至于可以忽略不计
      3. H的哈希结果是固定长度的
  • 基本思想(伯努利试验以及其推论)

    • 伯努利试验:一次实验的结果只有发生和不发生两种,重复做这个实验,直到结果发生为止,记录下实验的次数。
    • 步骤
      1. 设a为待估集合中的一个元素,通过计算a的哈希值,可以得到一个固定长度的比特串(a的二进制表示),设H哈希后的结果长度为L比特,我们将这L个比特位从左到右分别编号为1、2、…、L。

      2. 由于a是从服从均匀分布的空间中随机抽取的一个样本,所以a的每个比特位为0或者1的概率都是1/2,且相互独立。

      3. ρ(a) 为a的比特串中第一个'1'出现的位置, 则1 ≤ ρ(a) ≤ L成立(忽略比特串全为0的情况(概率为1/2^L))

      4. 遍历集合中所有元素的比特串,取ρmax为所有ρ(a)的最大值

    此时我们可以将2^ρmax作为基数的一个粗糙估计,也就是说这个集合的基数是2的ρmax次方

  • 为什么是2^ρmax?

    • 由于比特串每个比特都独立且服从0-1分布,因此从左到右扫描上述某个比特串寻找第一个“1”的过程从统计学角度看是一个伯努利过程。例如,可以等价看作不断投掷一个硬币(每次投掷正反面概率皆为0.5),直到得到一个正面的过程。在一次这样的过程中,投掷一次就得到正面的概率为1/2,投掷两次得到正面的概率是1/2^2,…,投掷k次才得到第一个正面的概率为1/ 2^k。

    • 以抛硬币(正反面概率相同)为例,连续抛硬币直到出现正面时停止,记录下抛掷次数(这个过程称之为伯努利过程),n次伯努利过程会得到n个首次出现正面的抛掷次数,其中最大抛掷次数为k,可以通过最大抛掷次数估算出总抛掷次数:n = 2^k

    • 问题

      1. 进行n次伯努利过程,所有投掷次数都不大于k的概率是多少?
        • 在一次伯努利过程中,投掷次数大于k的概率为1/2^k,即连续掷出k个反面的概率。因此,在一次过程中投掷次数不大于k的概率为1−1/2^k。n次伯努利过程投掷次数均不大于k的概率为:(1−1/2^k)^n
      2. 进行n次伯努利过程,至少有一次投掷次数等于k的概率是多少?
        • 答案显然是 1-(1-1/2^(k-1))^n
    • 可以看出,当n≪2^k 时, Pn(X≥k)的概率几乎为0,同时,当n≫2^k 时,Pn(X≤k)的概率也几乎为0。即:当伯努利过程次数远远小于2^k 时,至少有一次过程投掷次数等于k的概率几乎为0;当伯努利过程次数远远大于2^k时,没有一次过程投掷次数大于k的概率也几乎为0。

    • 结论:
      设一个集合的基数为n,ρmax为所有元素中首个“1”的位置最大的那个元素的“1”的位置,如果n远远小于2^ρmax,则我们得到ρmax为当前值的概率几乎为0(它应该更小),同样的,如果n远远大于2^ρmax,则我们得到ρmax为当前值的概率也几乎为0(它应该更大),因此2ρmax可以作为基数n的一个粗糙估计。

  • 分桶平均

    • LogLog Counting为了避免偶然性,会先进行分桶,每次抛硬币先随机分配到其中一个桶,每个桶保存最大抛掷次数,最后通过所有桶最大抛掷次数的均值计算,但是在抛掷次数小时误差还是会比较大。具体来说,就是将哈希空间平均分成m份,每份称之为一个桶(bucket)。对于每一个元素,其哈希值的前k比特作为桶编号,其中2^k=m,而后L-k个比特作为真正用于基数估计的比特串。桶编号相同的元素被分配到同一个桶,在进行基数估计时,首先计算每个桶内元素最大的第一个“1”的位置,设为M[i],然后对这m个值取平均后再进行估计
  • 偏差修正

    • 上述的方法并不是基数n的无偏估计。因此需要修正成无偏估计。这里涉及到具体的数学分析方法,可以参考http://algo.inria.fr/flajolet/Publications/DuFl03-LNCS.pdf

你可能感兴趣的:(LogLogCounting 基数估计算法)