更好的阅读体验html
**基数计数(cardinality counting)**一般用来统计一个集合中不重复的元素个数,例如统计某个网站的UV,或者用户搜索网站的关键词数量。数据分析、网络监控及数据库优化等领域都会涉及到基数计数的需求。 要实现基数计数,最简单的作法是记录集合中全部不重复的元素集合算法
当统计的数据量变大时,相应的存储内存也会线性增加数据库
当集合数组
实际上目前尚未发现更好的在大数据场景中准确计算基数的高效算法,所以在不追求绝对准确的状况下,使用几率算法算是一个不错的解决方案。几率算法不直接存储数据集合自己,经过必定的几率统计方法预估基数值,这种方法能够大大节省内存,同时保证偏差控制在必定范围内。目前用于基数计数的几率算法包括:服务器
Linear Counting(LC):早期的基数估计算法,LC在空间复杂度方面并不算优秀,实际上LC的空间复杂度与简单bitmap方法是同样的(可是有个常数项级别的下降),都是O(Nmax);网络
LogLog Counting(LLC):LogLog Counting相比于LC更加节省内存,空间复杂度只有O(log2(log2(Nmax)))框架
HyperLogLog Counting(HLL):HyperLogLog Counting是基于LLC的优化和改进,在一样空间复杂度状况下,可以比LLC的基数估计偏差更小。分布式
HLLDEMO函数
经过hash函数计算输入值对应的比特串大数据
比特串的低
t+1位开始找到第一个1出现的位置 k,将 k 记入数组
基于数组S记录的全部数据的统计值,计算总体的基数值,计算公式能够简单表示为:
HLL是LLC的偏差改进,实际是基于LLC。
下面非正式的从直观角度描述LLC算法的思想来源。
设a为待估集合(哈希后)中的一个元素,由上面对H的定义可知,a能够看作一个长度固定的比特串(也就是a的二进制表示),设H哈希后的结果长度为L比特,咱们将这L个比特位从左到右分别编号为一、二、…、L:
设ρ(a)为a的比特串中第一个“1”出现的位置,显然1≤ρ(a)≤L,这里咱们忽略比特串全为0的状况(几率为
此时咱们能够将
注意以下事实:
因为比特串每一个比特都独立且服从0-1分布,所以从左到右扫描上述某个比特串寻找第一个“1”的过程从统计学角度看是一个伯努利过程,例如,能够等价看做不断投掷一个硬币(每次投掷正反面几率皆为0.5),直到获得一个正面的过程。在一次这样的过程当中,投掷一次就获得正面的几率为1/2,投掷两次获得正面的几率是
如今考虑以下两个问题:
一、进行n次伯努利过程,全部投掷次数都不大于k的几率是多少?
二、进行n次伯努利过程,至少有一次投掷次数等于k的几率是多少?
首先看第一个问题,在一次伯努利过程当中,投掷次数大于k的几率为
显然第二个问题的答案是:
从以上分析能够看出,当
若是将上面描述作一个对应:一次伯努利过程对应一个元素的比特串,反面对应0,正面对应1,投掷次数k对应第一个“1”出现的位置,咱们就获得了下面结论:
设一个集合的基数为n,
以上结论能够总结为:进行了n次进行抛硬币实验,每次分别记录下第一次抛到正面的抛掷次数kk,那么能够用n次实验中最大的抛掷次数
与LC同样,在使用LLC以前须要选取一个哈希函数H应用于全部元素,而后对哈希值进行基数估计。H必须知足以下条件(定性的):
一、H的结果具备很好的均匀性,也就是说不管原始集合元素的值分布如何,其哈希结果的值几乎服从均匀分布(彻底服从均匀分布是不可能的,D. Knuth已经证实不可能经过一个哈希函数将一组不服从均匀分布的数据映射为绝对均匀分布,可是不少哈希函数能够生成几乎服从均匀分布的结果,这里咱们忽略这种理论上的差别,认为哈希结果就是服从均匀分布)。
二、H的碰撞几乎能够忽略不计。也就是说咱们认为对于不一样的原始值,其哈希结果相同的几率很是小以致于能够忽略不计。
三、H的哈希结果是固定长度的。
以上对哈希函数的要求是随机化和后续几率分析的基础。后面的分析均认为是针对哈希后的均匀分布数据进行。
上述分析给出了LLC的基本思想,不过若是直接使用上面的单一估计量进行基数估计会因为偶然性而存在较大偏差。所以,LLC采用了分桶平均的思想来消减偏差。具体来讲,就是将哈希空间平均分红m份,每份称之为一个桶(bucket)。对于每个元素,其哈希值的前k比特做为桶编号,其中
这至关于物理试验中常用的屡次试验取平均的作法,能够有效消减因偶然性带来的偏差。
下面举一个例子说明分桶平均怎么作。
假设H的哈希长度为16bit,分桶数m定为32。设一个元素哈希值的比特串为“0001001010001010”,因为m为32,所以前5个bit为桶编号,因此这个元素应该纳入“00010”即2号桶(桶编号从0开始,最大编号为m-1),而剩下部分是“01010001010”且显然ρ(01010001010)=2,因此桶编号为“00010”的元素最大的ρ即为M[2]的值。
上述通过分桶平均后的估计量看似已经很不错了,不过经过数学分析能够知道这并非基数n的无偏估计。所以须要修正成无偏估计。这部分的具体数学分析在“Loglog Counting of Large Cardinalities”中,过程过于艰涩这里再也不具体详述,有兴趣的朋友能够参考原论文。这里只简要提一下分析框架:
首先上文已经得出:
所以:
这是一个未知通项公式的递推数列,研究这种问题的经常使用方法是使用生成函数(generating function)。经过运用指数生成函数和poissonization获得上述估计量的Poisson指望和方差为:
最后经过depoissonization获得一个渐进无偏估计量:
其中:
其中m是分桶数。这就是LLC最终使用的估计量。
不加证实给出以下结论:
在应用LLC时,主要须要考虑的是分桶数m,而这个m主要取决于偏差。根据上面的偏差分析,若是要将偏差控制在ϵ以内,则:
内存使用与m的大小及哈希值得长度(或说基数上限)有关。假设H的值为32bit,因为
与LC不一样,LLC的合并是以桶为单位而不是bit为单位,因为LLC只需记录桶的
HyperLogLog Counting(如下简称HLLC)的基本思想也是在LLC的基础上作改进,具体细节请参考“HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm”这篇论文。
HLLC的第一个改进是使用调和平均数替代几何平均数。注意LLC是对各个桶取算数平均数,而算数平均数最终被应用到2的指数上,因此整体来看LLC取得是几何平均数。因为几何平均数对于离群值(例如这里的0)特别敏感,所以当存在离群值时,LLC的误差就会很大,这也从另外一个角度解释了为何n不太大时LLC的效果不太好。这是由于n较小时,可能存在较多空桶,而这些特殊的离群值强烈干扰了几何平均数的稳定性。
所以,HLLC使用调和平均数来代替几何平均数,调和平均数的定义以下:
调和平均数能够有效抵抗离群值的扰动。使用调和平均数代替几何平均数后,估计公式变为以下:
其中:
根据论文中的分析结论,与LLC同样HLLC是渐近无偏估计,且其渐近标准差为:
所以在存储空间相同的状况下,HLLC比LLC具备更高的精度。例如,对于分桶数m为2^13(8k字节)时,LLC的标准偏差为1.4%,而HLLC为1.1%。
在HLLC的论文中,做者在实现建议部分还给出了在n相对于m较小或较大时的误差修正方案。具体来讲,设E为估计值:
当
当
当
关于分段误差修正效果分析也能够在原论文中找到。
这些基数估计算法的一个好处就是很是容易并行化。对于相同分桶数和相同哈希函数的状况,多台机器节点能够独立并行的执行这个算法;最后只要将各个节点计算的同一个桶的最大值作一个简单的合并就能够获得这个桶最终的值。并且这种并行计算的结果和单机计算结果是彻底一致的,所需的额外消耗仅仅是小于1k的字节在不一样节点间的传输。
基数估计算法使用不多的资源给出数据集基数的一个良好估计,通常只要使用少于1k的空间存储状态。这个方法和数据自己的特征无关,并且能够高效的进行分布式并行计算。估计结果能够用于不少方面,例如流量监控(多少不一样IP访问过一个服务器)以及数据库查询优化(例如咱们是否须要排序和合并,或者是否须要构建哈希表)。
Redis new data structure: the HyperLogLog
HyperLogLog — Cornerstone of a Big Data Infrastructure