知识大全 基数估计算法

Posted 基数

篇首语:鸟贵有翼,人贵有志。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 基数估计算法相关的知识,希望对你有一定的参考价值。

  一个简单直观的基数估计方法

  让我们从一个简单直观的例子开始吧 假设你通过如下步骤生成了一个数据集

   随机生成n个服从均匀分布的数字

   随便重复其中一些数字 重复的数字和重复次数都不确定

   打乱这些数字的顺序 得到一个数据集

  我们要如何估计这个数据集中有多少不同的数字呢?因为知道这些数字是服从均匀分布的随机数字 一个比较简单的可行方案是 找出数据集中最小的数字 假如m是数值上限 x是找到的最小的数 则m/x是基数的一个估计 例如 我们扫描一个包含 到 之间数字组成的数据集 其中最小的数是 则一个 比较合理的推断是数据集中大约有 个不同的元素 否则我们应该预期能找到一个更小的数 注意这个估计值和重复次数无关 就如最小值重复多少次都不改变 最小值的数值

  这个估计方法的优点是十分直观 但是准确度一般 例如 一个只有很少不同数值的数据集却拥有很小的最小值 类似的一个有很多不同值的数据集可能最小 值并不小 最后一点 其实只有很少的数据集符合随机均匀分布这一前提 尽管如此 这个原型算法仍然是了解基数估计思想的一个途径 后面我们会了解一些更加 精巧的算法

  基数估计的概率算法

  最早研究高精度基数估计的论文是Flajolet和Martin的Probabilistic Counting Algorithms for Data Base Applications 后来Flajolet又发表了LogLog counting of large cardinalities和HyperLogLog: The analysis of a near optimal cardinality estimation algorithm两篇论文对算法进行了进一步改进 通过逐篇阅读这些论文来了解算法的发展和细节固然有趣 不过在这篇文章中我会忽略一些算法的理论细节 把精力主要放在如何通过论文中的算法解决问题 有兴趣的读者可以读一下这三篇论文 本文不会介绍其中的数学细节

  Flajolet和Martin最早发现通过一个良好的哈希函数 可以将任意数据集映射成服从均匀分布的(伪)随机值 根据这一事实 可以将任意数据集变换为均匀分布的随机数集合 然后就可以使用上面的方法进行估计了 不过只是这样是远远不够的

  接下来 他们陆续发现一些其它的基数估计方法 而其中一些方法的效果优于之前提到的方法 Flajolet和Martin计算了哈希值的二进制表示 的 前缀 结果发现在随机数集合中 通过计算每一个元素的二进制表示的 前缀 设k为最长的 前缀的长度 则平均来说集合中大约有 k个不同的元素 我们 可以用这个方法估计基数 但是 这仍然不是很理想的估计方法 因为和基于最小值的估计一样 这个方法的方差很大 不过另一方面 这个估计方法比较节省资 源 对于 位的哈希值来说 只需要 比特去存储 前缀的长度

  值得一提的是 Flajolet Martin在最初的论文里通过一种基于bitmap的过程去提高估计算法的准确度 关于这点我就不再详述了 因为这种方法已经被后续论文中更好的方法所取代 对这个细节有兴趣的读者可以去阅读原始论文

  到目前为止 我们这种基于位模式的估计算法给出的结果仍然不够理想 如何进行改进呢?一个直观的改进方法就是使用多个相互独立的哈希函数 通过计算每个哈希函数所产生的最长 前缀 然后取其平均值可以提高算法的精度

  实践表明从统计意义来说这种方法确实可以提高估计的准确度 但是计算哈希值的消耗比较大 另一个更高效的方法就是随机平均(stochastic averaging) 这种方法不是使用多个哈希函数 而是使用一个哈希函数 但是将哈希值的区间按位切分成多个桶(bucket) 例如我们希望取 个数进行平均 那么我们可以取哈希值的前 比特作为桶编号 然后计算剩下部分的 前缀长度 这种方法的准确度和多哈希函数方法相当 但是比计算 多个哈希效率高很多

  根据上述分析 我们可以给出一个简单的算法实现 这个实现等价于Durand Flajolet的论文中提出的LogLog算法 不过为了方便 这个实现中统计的是 尾缀而不是 前缀 其效果是等价的

  def trailing_zeroes(num):

   Counts the number of trailing bits in num

  if num == :

  return # Assumes bit integer inputs!

  p =

  while (num >> p) & == :

  p +=

  return p

  def estimate_cardinality(values k):

   Estimates the number of unique elements in the input set values

  Arguments:

  values: An iterator of hashable elements to estimate the cardinality of

  k: The number of bits of hash to use as a bucket number; there will be **k buckets

  

  num_buckets = ** k

  max_zeroes = [ ] * num_buckets

  for value in values:

  h = hash(value)

  bucket = h & (num_buckets ) # Mask out the k least significant bits as bucket ID

  bucket_hash = h >> k

  max_zeroes[bucket] = max(max_zeroes[bucket] trailing_zeroes(bucket_hash))

  return ** (float(sum(max_zeroes)) / num_buckets) * num_buckets *

  这段代码实现了我们上面讨论的估计算法 我们计算每个桶的 前缀(或尾缀)的最长长度 然后计算这些长度的平均数 假设平均数是x 桶数量是m 则 最终的估计值是 x×m 其中一个没提过的地方是魔法数字 统计分析显示这种预测方法存在一个可预测的偏差 这个魔法数字是对这个偏差的修 正 实际经验表明计算值随着桶数量的不同而变化 不过当桶数量不太小时(大于 ) 计算值会收敛于估计值 原论文中描述了这个结论的推导过程

  这个方法给出的估计值比较精确 —— 在分桶数为m的情况下 平均误差为 /m√ 因此对于分桶数为 的情况(所需内存 * = 位 或 字节) 大约会有 %的平均误差 每桶 比特的存储已经足以估计 的数据集 而我们只用的不到 k的内存!

  让我们看一下试验结果

  >>> [ /estimate_cardinality([random random() for i in range( )] ) for j in range( )]    [ ]    不错!虽然有些估计误差大于 %的平均误差 但总体来说效果良好 如果你准备自己做一下这个试验 有一点需要注意 Python内置的 hash() 方法将整数哈希为它自己 因此诸如 estimate_cardinality(range( ) ) 这种方式得到的结果不会很理想 因为内置 hash() 对于这种情况并不能生成很好的散列 但是像上面例子中使用随机数会好很多

  提升准确度 SuperLogLog和HyperLogLog

  虽然我们已经有了一个不错的估计算法 但是我们还能进一步提升算法的准确度 Durand和Flajolet发现离群点会大大降低估计准确度 如果 在计算平均值前丢弃一些特别大的离群值 则可以提高精确度 特别的 通过丢弃最大的 %的桶的值 只使用较小的 %的桶的值来进行平均值计算 则平均 误差可以从 /m降低到 /m!这意味着在我们上面的例子中 使用 个字节可情况下可以将平均误差从 %降低到 % 而所 需内存并没有增加

  最后 Flajolet等人在HyperLogLog论文中给出一种不同的平均值 使用调和平均数取代几何平均数(译注 原文有误 此处应该是算数 平均数) 这一改进可以将平均误差降到 /m 而且并没不需要额外资源 但是这个算法比前面的算法复杂很多 因为对于不同基数的数据集要做不 同的修正 有兴趣的读者可以阅读原论文

  并行化

  这些基数估计算法的一个好处就是非常容易并行化 对于相同分桶数和相同哈希函数的情况 多台机器节点可以独立并行的执行这个算法 最后只要将各个节 点计算的同一个桶的最大值做一个简单的合并就可以得到这个桶最终的值 而且这种并行计算的结果和单机计算结果是完全一致的 所需的额外消耗仅仅是小于 k 的字节在不同节点间的传输

  结论

cha138/Article/program/Java/hx/201311/11149

相关参考

知识大全 排序算法的各趟排序算法

  以关键字序列()为例分别写出执行以下排序算法的各趟排序结束时关键字序列的状态  ()直接插入排序()希尔排序()冒泡排序()快速排序  ()直接选择排序()堆排序()归并排序()基数排序  上述方

知识大全 排序算法的各趟排序算法

  以关键字序列()为例分别写出执行以下排序算法的各趟排序结束时关键字序列的状态  ()直接插入排序()希尔排序()冒泡排序()快速排序  ()直接选择排序()堆排序()归并排序()基数排序  上述方

知识大全 第8章排序(基础知识)习题练习

以关键字序列()为例分别写出执行以下排序算法的各趟排序结束时关键字序列的状态 ()直接插入排序()希尔排序()冒泡排序()快速排序 ()直接选择排序()堆排序()归并排序()基数排序  上述方法中哪些

知识大全 第8章排序(基础知识)习题练习答案

以关键字序列()为例分别写出执行以下排序算法的各趟排序结束时关键字序列的状态 ()直接插入排序()希尔排序()冒泡排序()快速排序 ()直接选择排序()堆排序()归并排序()基数排序  上述方法中哪些

社会保险缴费基数的确定

社会保险缴费基数的确定职工个人以本人上年度工资收入总额的月平均数作为本年度月缴费基数,其中:新进本单位的人员以职工本人起薪当月的足月工资收入作为缴费基数;参保单位以本单位全部参保职工月缴费基数之和作为

知识大全 2015年社保最低基数是多少

2015年社保最低基数是多少这个问话太大,社保缴费基数不是全国统一的,是以地区为单位计算的,不知家住何方是不会知道的。你是哪个地区的,每个地区的社保基数都有区别的2009年社保最低基数是多少?北京市社

社会保险缴费基数的申报

社会保险缴费基数的申报每年2月-5月,参保单位须到办理参保登记的市社会保险费征缴管理中心或区社会保险所(以下统称社保经办机构)申报下一结算年度的社会保险缴费基数。参保单位申报的缴费基数,须经参保职工本

知识大全 单位缴纳住房公积金的基数怎么计算

单位缴纳住房公积金的基数怎么计算?缴存基数计算,住房公积金的缴费基数按职工上年度12月份工资总额计算;工资总额应按照国家统计局规定列入的项目计算。新录入和调入职工住房公积金的缴费基数是职工本人当月工资

社会保险缴费基数的足额申报

社会保险缴费基数的足额申报缴费基数是参保人员享受社会保险待遇的重要计算依据。劳动者参加社会保险后,社保经办机构为其建立一个终身不变的基本养老保险个人帐户和医疗保险个人帐户。职工缴费基数越高,其个人帐户

社会保险的月缴费基数

社会保险的月缴费基数社会保险的月缴费基数一般是按照职工上年度全年工资的月平均值来确定的,每年确定一次,且一旦确定以后,一年内不再变动。根据规定,职工上年度月平均工资作为交纳社会保险的月缴费基数,但是,