数学相关 - HyperLogLog算法原理

应用场景

当需要对大量数据做去重计数, 例如统计一个页面的UV(Unique Visitor, 独立访客), 或者用户搜索的关键词数量, 比较容易想到的方案有

  • 存储到数据库表, 使用distinct count计算
  • 使用Redis的set, bitmap等数据结构

但都存在一些问题, 随着数据量增加, 存储空间占用越来越大; 统计速度慢, 性能并不理想

数据分析, 网络监控及数据库优化等领域都会涉及到基数计数的需求.

基数, 一个集合中不重复元素的个数.

目前还没有在大数据场景中准确计算基数的高效算法, 因此在允许一定误差的情况下, 使用概率算法是一个不错的选择

概率算法

概率算法不直接存储数据本身, 而通过一定的概率统计方法预估基数值, 这种方法可以大大节省内存, 同时保证误差控制在一定范围内
目前用于基数计数的概率算法有

  • LC(Linear Counting): 早期的基数估计算法, 在空间复杂度方面不算优秀, O ( N m a x ) O(N_{max}) O(Nmax)
  • LLC(LogLog Counting): 相比于LC更加节省内存, 空间复杂度只有 O ( l o g 2 ( l o g 2 ( N m a x ) ) ) O(log_2(log_2(N_{max}))) O(log2(log2(Nmax)))
  • HLL(HyperLogLog Counting): HLL基于LLC的优化和改进, 在同样空间复杂度情况下, 能够比LLC的基数估计误差更小, 例如Redia中HLL只占12KB, 可以计算接近 2 64 2^{64} 264个不同基数, 标准误差为0.81%

极大似然估计

极大似然估计是建立在极大似然原理的基础上的一个统计方法, 是概率论在统计学中的应用
极大似然估计提供了一种给定观察数据来评估模型参数的方法, 即: 模型已定, 参数未知
通过若干次试验, 观察其结果, 利用试验结果得到某个参数值能够使样本出现的概率为最大, 则称为极大似然估计

目的就是: 利用已知的样本结果, 反推最有可能/最大概率导致这样结果的参数值

例如有两个箱子, 甲箱有99白球1个黑球, 乙箱有99黑球1白球, 现在取出了一个黑球
问黑球是从哪个箱子取出的
你的第一印象就是黑球最像是从乙箱取出的, 这个推断符合人们的经验事实, 最像就是最大似然之意, 这种想法常称为极大似然原理

伯努利试验

伯努利试验(Bernoulli experiment), 是在同样的条件下重复地, 相互独立地进行的一种随机试验
其特点是该随机试验只有两种可能结果: 发生或者不发生
我们假设该项试验独立重复地进行了n次, 那么就称这一系列重复独立的随机试验为n重伯努利试验
单个伯努利试验是没有多大意义的, 然而当我们反复进行伯努利试验, 去观察这些试验有多少是成功的多少是失败的, 事情就变得有意义了, 这些累计记录包含了很多潜在的非常有用的信息

以抛硬币为例, 每次抛硬币出现正面的概率都是50%, 假设一直抛硬币, 直到出现正面, 则一个伯努利试验结束
假设第1次伯努利试验抛硬币的次数为K1, 第n次伯努利试验抛硬币的次数为Kn
我们记录这n次伯努利试验的最大抛硬币次数为Kmax
那么重点来了
在某次伯努利试验中, kmax出现的概率, 就是投掷了kmax-1次反面, 和1次正面, 概率就是 1 / 2 k m a x 1/2^{k_{max}} 1/2kmax
也就是说需要进行 2 k m a x 2^{k_{max}} 2kmax次试验可能会出现一次kmax, 那么根据极大似然估算法, 我们就粗略估计本次进行的n次伯努利试验的 n = 2 k m a x n=2^{k_{max}} n=2kmax

同理, 应用到基数统计中就是, 把集合中每个元素都经过hash后表示为二进制数串, 一个数串类比成一次抛硬币试验, 1是抛到正面, 0是反面
二进制串中从低位开始第一个1出现的位置, 理解为抛硬币中第一次出现正面的抛掷次数k, 依旧通过 2 k m a x 2^{k_{max}} 2kmax来估算集合中一共有多少不同的数字
因为重复的值hash后二进制数串一致, 得到的k值不会变化, 所以是天然去重的, 重复数据不会对估算值产生任何影响

后续优化

很显然这个估算关系是不准确的
关于估值偏差较大的问题, 可以采用如下方式结合来缩小误差

均匀随机化

选取的哈希函数必须满足以下条件, 优秀的哈希函数是后续概率分析的基础

  1. 哈希结果有很好的均匀性, 无论原始集合元素的值分布如何, 哈希结果都几乎服从均匀分布
  2. 哈希的碰撞几乎可以忽略不计
  3. 哈希结果的长度是固定的

分桶平均

以上直接采用单一估计量会由于偶然性存在较大误差, 因此采用分桶平均的思想来消除误差
以Redis为例, 哈希结果64位, 前14位来分桶, 共16384个桶, 后50位作为真正用于基数估计的比特串
桶编号相同的元素分配到一个桶, 先分别统计每个桶各自的kmax, 然后进行平均
相当于物理试验中多次试验取平均的做法, 可以有效消减因偶然性带来的误差

调和平均

分桶取平均是LLC的算法实现, HLL区别在于采用了调和平均数,
调和平均数指的是倒数的平均数, 相比平均数能有效抵抗离群值对平均值的扰动
比如我和马云的平均资产, 我有两万, 他有两千亿, 平均一下我有一千亿
但是调和平均数, 平均一下我有2/(1/20000 + 1/200000000000)=40000
就和我的资产偏差不是很大

偏差修正

经过上述系列优化后的估计量看似已经不错了, 但通过数学分析可以知道这并不是基数n的无偏估计
因此需要修正成无偏估计, 此处涉及一系列数学公式
简单来说就是, 增加修正因子, 是一个不固定的值, 会根据实际情况来进行值的调整
详细可参考论文

Redis中HyperLogLog结构

具体数据结构, 密集存储结构, 稀疏存储结构及其转换, 可参考一下文章
玩转Redis-HyperLogLog原理探索

ps: 另附一个演示工具, 在明白以上原理以后, 可以直观的看到HLL的工作过程及误差变化情况

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值