译注:给定一个数据集,求解数据集的基数(Cardinality,也译作“势”,表示一个数据集中不同数据项的数量)是非常普遍的一个需求。许多业务需求终终可以归结为基数求解,如网站访问分析中的UV(访客数,指一段时间内访问网站的不同用户的数量)。由于数据集基数是不可聚集指标(两个数据集总的基数无法通过分别的基数简单计算),因此如果要得到N个数据集任意组合的基数,需要
以为如下:
--------------------
假如你有一个巨大的含有重复数据项数据集,这个数据集过于庞大以至于无法全部放到内存中处理。现在你想知道这个数据集里有多少不同的元素,但是数据集没有排好序,而且对如此大的一个数据集进行排序和计数几乎是不可行的。你要如何估计数据集中有多少不同的数据项?很多应用场景都涉及这个问题,例如设计数据库的查询策略:一个良好的数据库查询策略不但和总的数据量有关,同时也依赖于数据中不同数据项的数量。
我建议在继续阅读本文前你可以稍微是思考一下这个问题,因为接下来我们要谈的算法相当有创意,而且实在是不怎么直观。
让我们从一个简单直观的例子开始吧。假设你通过如下步骤生成了一个数据集:
1、随机生成n个服从均匀分布的数字
2、随便重复其中一些数字,重复的数字和重复次数都不确定
3、打乱这些数字的顺序,得到一个数据集
我们要如何估计这个数据集中有多少不同的数字呢?因为知道这些数字是服从均匀分布的随机数字,一个比较简单的可行方案是:找出数据集中最小的数字。假如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计算了哈希值的二进制表示的0前缀,结果发现在随机数集合中,通过计算每一个元素的二进制表示的0前缀,设k为最长的0前缀的长度,则平均来说集合中大约有
值得一提的是,Flajolet-Martin在最初的论文里通过一种基于bitmap的过程去提高估计算法的准确度。关于这点我就不再详述了,因为这种方法已经被后续论文中很好的方法所取代;对这个细节有兴趣的读者可以去阅读原始论文。
到目前为止,我们这种基于位模式的估计算法给出的结果仍然不够理想。如何进行改进呢?一个直观的改进方法就是使用多个相互独立的哈希函数:通过计算每个哈希函数所产生的最长0前缀,然后取其平均值可以提高算法的精度。
实践表明从统计意义来说这种方法确实可以提高估计的准确度,但是计算哈希值的消耗比较大。另一个更高效的方法就是随机平均(stochastic averaging)。这种方法不是使用多个哈希函数,而是使用一个哈希函数,但是将哈希值的区间按位切分成多个桶(bucket)。例如我们希望取1024个数进行平均,那么我们可以取哈希值的前10比特作为桶编号,然后计算剩下部分的0前缀长度。这种方法的准确度和多哈希函数方法相当,但是比计算多个哈希效率高很多。
根据上述分析,我们可以给出一个简单的算法实现。这个实现等价于Durand-Flajolet的论文中提出的LogLog算法;不过为了方便,这个实现中统计的是0尾缀而不是0前缀;其效果是等价的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
这段代码实现了我们上面讨论的估计算法:我们计算每个桶的0前缀(或尾缀)的最长长度;然后计算这些长度的平均数;假设平均数是x,桶数量是m,则终终的估计值是
这个方法给出的估计值比较精确 —— 在分桶数为m的情况下,平均误差为
让我们看一下试验结果:
1 2 |
|
不错!虽然有些估计误差大于4%的平均误差,但总体来说效果良好。如果你准备自己做一下这个试验,有一点需要注意:Python内置的 hash() 方法将整数哈希为它自己。因此诸如 estimate_cardinality(range(10000), 10) 这种方式得到的结果不会很理想,因为内置 hash() 对于这种情况并不能生成很好的散列。但是像上面例子中使用随机数会好很多。
虽然我们已经有了一个不错的估计算法,但是我们还能进一步提升算法的准确度。Durand和Flajolet发现离群点会大大降低估计准确度;如果在计算平均值前丢弃一些特别大的离群值,则可以提高精确度。特别的,通过丢弃很大的30%的桶的值,只使用较小的70%的桶的值来进行平均值计算,则平均误差可以从
后面,Flajolet等人在HyperLogLog论文中给出一种不同的平均值,使用调和平均数取代几何平均数(译注:原文有误,此处应该是算数平均数)。这一改进可以将平均误差降到
这些基数估计算法的一个好处就是非常容易并行化。对于相同分桶数和相同哈希函数的情况,多台机器节点可以独立并行的执行这个算法;后面只要将各个节点计算的同一个桶的很大值做一个简单的合并就可以得到这个桶终终的值。而且这种并行计算的结果和单机计算结果是完全一致的,所需的额外消耗仅仅是小于1k的字节在不同节点间的传输。
基数估计算法使用很少的资源给出数据集基数的一个良好估计,一般只要使用少于1k的空间存储状态。这个方法和数据本身的特征无关,而且可以高效的进行分布式并行计算。估计结果可以用于很多方面,例如流量监控(多少不同IP访问过一个服务器)以及数据库查询优化(例如我们是否需要排序和合并,或者是否需要构建哈希表)。
评论加载中...
|
Copyright@ 2011-2017 版权所有:大连仟亿科技有限公司 辽ICP备11013762-1号 google网站地图 百度网站地图 网站地图
公司地址:大连市沙河口区中山路692号辰熙星海国际2215 客服电话:0411-39943997 QQ:2088827823 42286563
法律声明:未经许可,任何模仿本站模板、转载本站内容等行为者,本站保留追究其法律责任的权利! 隐私权政策声明