Showing posts with label Approximate Algorithms. Show all posts
Showing posts with label Approximate Algorithms. Show all posts

Tuesday, March 26, 2019

Lossy Counting



https://micvog.com/2015/07/18/frequency-counting-algorithms-over-data-streams/
https://blog.csdn.net/dm_ustc/article/details/46051091
https://www.afenxi.com/20042.html
比如,一个社交网站上有上亿的用户主页,而且每天有上十亿的访问量,想实时知道最常被访问的主页有哪些,然后给出一个排名。常用的做法是给每个主页一个计数器,这样需要很大的内存(往往装不下)来保存这些计数器,但极大多数的计数器其实只有一次两次,这是一个非常大的浪费,而且现实资源不允许这么做。
再比如,一个网站有海量的IP访问。为了检测爬虫或是DOS,需要计算一个IP在一个时间单位(比如一分钟,一小时,一天,等)中访问的次数,然后屏蔽或是captcha单位时间内访问次数过多的IPs。由于web server中能用来统计IP访问次数的内存资源有限,不可能存储所有的访问IPs。
还有很多类似的例子,比如,一段时间内的流行商品,一段时间内的人气王,经常被拨打的电话,作弊的账号,等等。
对于这些实时数据流的频率统计问题,有在数学上可能保证错误小于某个要求值的近似算法,Lossy Counting Algorithm 就是 Gurmeet Singh Manku 提出和证明的一个算法。
这些问题都可以统一为以下的问题描述,对于一个数据流,找出所有的频率大于某个值(通常表示为总数的百分比,这里用s表示)的所有元素,进而可以找出top n。
实时大数据流上的频率统计:Lossy Counting Algorithm
Lossy Couting 算法的第一步是将数据流分成一样大小的很多个窗口(或是片)。窗口的大小和期望的错误率息息相关。之后具体讨论。

从第一个窗口开始,计算每个元素的频率。注意了,高明之处来了,在窗口结束时,将每个元素的计数减一,然后将哪些计数值为零的元素从内存中删除。这样在保证近似性的同时也限制了内存资源的占用。妙吧!

然后,处理下一个窗口中的元素,同样,在窗口的结束时,将每个元素的计数减一,同时清除那些计数为零的元素。直到所有数据处理完毕,或是实时流数据的检查点到了。最后,将内存中的元素按频率排序,就能得当当前的top n。

很明显,Lossy Counting 算法是个近似算法,但它的错误是可以分析和数学上可以证明它的边界的。如果错误率是e,窗口大小为1/e(哈哈,这样才能保证错误率小),对于长度为N的流,有N/(1/e)=eN 个窗口,由于每个窗口结束时减一了,那么频率最多被少计数了窗口个数eN。

最后留下的元素是频率超过 sN-eN的。近似算法能保证:
  • 频率最多被少算eN;
  • 没有错误的反例(不应该出来的元素出来了);
  • 错误的正例有的真实频率至少是sN-eN。
最后,内存的使用的最坏情况是 1/e log (eN) 个计数器。参考阅读原文看证明。

http://dmac.rutgers.edu/Workshops/WGUnifyingTheory/Slides/cormode.pdf
Simplified version: – Track items and counts – For each block of 1/ε items, merge with stored items and counts – Decrement all counts by one, delete items with zero count ♦ Easy to see that counts are accurate to εN ♦ Analysis shows O(1/ε log εN) items are stored ♦ Full version keeps extra information to reduce error

SpaceSaving Algorithm
“SpaceSaving” algorithm [Metwally, Agrawal, El Abaddi 05] merges Lossy Counting and FREQUENT algorithms ♦ Keep k = 1/ε item names and counts, initially zero Count first k distinct items exactly ♦ On seeing new item: – If it has a counter, increment counter – If not, replace item with least count, increment count





Monday, September 14, 2015

HyperLogLog



http://www.tutorialspoint.com/redis/redis_hyperloglog.htm
Redis HyperLogLog is a algorithm that use randomization in order to provide an approximation of the number of unique elements in a set using just a constant, and small, amount of memory.
HyperLogLog provides a very good approximation of the cardinality of a set even using a very small amount of memory around 12 kbytes per key with a standard error of 0.81% and there is no limit to the number of items you can count, unless you approach 264 items.
PFADD tutorials "redis"
PFCOUNT tutorials

PFADD key element [element ...]
PFCOUNT key [key ...]
PFMERGE destkey sourcekey [sourcekey ...]

PFADD hll1 foo bar zap a
PFADD hll2 a b c foo
PFMERGE hll3 hll1 hll2


https://redislabs.com/blog/how-to-use-redis-at-least-x1000-more-efficiently
HyperLogLog allows you to estimate the cardinality of a set or, put differently, get an approximate count of unique items.

HLL let you do that using a fixed amount of memory and constant complexity.
HLL’s count has a standard error of up to 0.81%.

Assume you want to keep track of how many unique users are using your app throughout the day, hour by hour. Before HyperLogLog, one simple way that you could do that in Redis was by storing the IDs of your users (e.g. their emails) in an hourly set, so for each request you'd do something like:

SADD users:<date>:<hour> <email>

This way, the result of SCARD for any such set would be the number of unique users for that hour.

When using HLL we'd count the users with:
PFADD users:<date>:<hour> <email>
And use PFCOUNT to obtain the hourly count. But what if you wanted (and you usually do) to have aggregates for different time periods, like 3h, 6h, 12h and 24h? With the SADD/SCARD approach, you'll either need to dedicate a few more counter sets for each resolution or execute on-demand/periodic SUNIONs, costing RAM and CPU. You could, as shown above, gain considerably by using HLL but there's actually another big saving with it. Since HLLs can be merged very efficiently, you don't need to save a counter for each of time resolution and justPFMERGE the relevant counters to get the sums.
全宇宙发来天量贺电 简介高级概率数据结构HyperLogLog
《流浪地球》,火了!

火到什么程度呢?火到了全宇宙都知道地球发明出了行星发动机。纷纷发来贺电。天量贺电.
天到什么程度呢?10^21 (1000000000000000000000)这个量级。这也是宇宙中所有星星的估算数量。

地球联合政府代表大刘找到你,要求查出天亮贺电里有多少Unique的星星。你能得到的是每个贺电源星星的坐标: 比如这一段学天文的同学熟悉的字串:RA 3h 4m 38.05s Dec 1° 51' 49.0''
如果用传统的办法,那么自然要用到的空间复杂度为O(N * K) K为星星的identity的平均长度。这个空间复杂度是超出地球上所有存储媒体的大小的。所以是无法实现的。

有一种概率数据结构:Hyperloglog。拥有这样的特质:虽然不是特别精确,有+- 0.81%的标准误差。但是运算快,内存小O(1)。是不是很神奇?

为了理解这个神奇的算法。我们来想想一个简化的例子。给你一些32以下的随机、均匀分布的数(允许重复),求总共多少distinct数。只允许你用一个int variable。

从0-31的随机数,并且按照从左数连续零的长度分组,则会得到:

[Bash shell] 纯文本查看 复制代码
?
从左数连续5个零,共一个:00000
从左数连续4个零,共一个:00001
从左数连续3个零,共两个:00010 00011
从左数连续2个零,共四个:00100 00101 00110 00111
从左数连续1个零,共八个:01000 01001 01010 01011 01100 01101 01110 01111
从左数连续没有零,共十六个:10000 10001 10010 10011 10100 10101 10110 10111 11000 11001 11010 11011 11100 11101 11110 11111


如果你仅仅记录你看到的从左数连续最长的零的数目,那么你就可以估算出一个比较准确的估计值。
比如你见过从左数连续零最长为:2,那么你可以推断出你至少见过00100 00101 00110 00111之一。因为这些数在均匀分布的整个基数空间的概率为4/32 = 1/8 (即1/2^(2+1))。
虽然你也可能见过从左数连续零最长为1,从左数连续零最长为0 的数,但是那些数出现的概率为 1/4 1/2。比不上 1/8. 所以你可以大胆粗糙估计:你应该见过8个左右的distinct的数。
可以想见,这是一个相当粗糙的估计。但是如果基数越大,那么这个估计应该越准确。

Redis(和多个开源项目甚至包括Presto)实现HyperLogLog的时候,用到了几大神器和设计
  • 为了对于任意给定的String key产生随机、均匀分布的数,用到了MurmurHash
  • 为了消除误差,用到了16384个桶,实现了O(1)空间复杂度
  • 为了消除桶和桶之间的极端数,在估算Cardinality的时候用到了Harmonic Mean

MurmurHash是个设计极其简单(Multiply, Shift and XOR)但是功能非常强大的哈希算法。给定任意长度bytes,能够产生even distribution的64位(8bytes)。
这样把hash的前几位作为bucket id,把后面的若干位,求从左边数连续的0的长度。如果大于则放入bucket中。这样bucket里就存放了最长见过的连续0的长度。

为了理解桶在hyperloglog里的应用,看下图的上半部分的例子,用4个桶。




Redis实现的时候,其buckets数为(m=2^14=16384)。任何key通过MurmurHash产生一个64位的hash。hash的前14位用于表征去哪个bucket。后面的50bit,最长也就50. 因此每个bucket只需要6个bit,来表征不超过2^6=64的数。这样下来我们看看:16384 * 6 = 98304 bits = 12288 (12K Bytes)。对于不超过2^64个基数空间。则O(1)空间就可以解决统计distinct的问题。

如果碰巧第一次就得到hash 00000 怎么办?Harmonic Mean的巧妙之处在于接近所有观测者(buckets)里最短的从左数最长的连续0。把bucket里0的概率相加。那么碰巧的连续长0的概率(很小的一个值)通过和其它概率相加就被边缘化了。比如在图中的例子里最后得到的是buckets的概率分别是:2^-3,2^-2,2^-2,2^-4. 相加以后2^-3和2^-4会因为2^-2而被边缘化。注意Redis源码里用的是histogram。这里就不认真琢磨了。想来面试中不会考这深的地方。真正写正式码的时候还是要琢磨一下的。

照Redis的官方介绍,其错误空间为standard error of 0.81%. 也就是说,如果放入1000000个distinct值,则Redis count的结果会在[1000000 - 8100, 1000000 + 8100]区间之内。即99.19%-100.81%之间。

因为O(1)空间复杂度,所以Redis还提供了两套桶合并的功能。如果你有两个统计(在Redis里有两个Keys。那么Redis里有两套12K的桶。)合并的时候无非就是每个bucket取max。不细数了。

更多的Redis HyperLogLog的实现请参照 https://github.com/antirez/redis/blob/unstable/src/hyperloglog.c#L1013

好了,上面我们从面试角度浅显低了解了一下HyperLogLog的原理。现在我们回到地球联合政府的海量计算的差事上吧。

10^21 (1000000000000000000000)这么多个要处理的消息。我们假设网络不是问题。大不了大家都去天文台旁边建数据中心好了。硬盘也不是瓶颈。

  • 我们考虑把外星人的消息shard到多台机器上去。shard的基数定位redis能够承受的基数:2^64 = 18446744073709550000
  • 全世界有多少台电脑?查了下发现去年有259390000 PCs卖出去。这是PC不是什么特别好的机器。但这也是分布计算的魅力之所在。
  • 有259390000个shard,每个shard能承受2^64。这样总体能够承受4.78E+27个distinct数,远远大于10^21。所以从基数空间这点上看是可行的。
  • 下面再来考虑运算速度,多长时间出活?
  • 一个Redis process的hyperloglog的运算量估计为:30000 qps。这不是什么特别好的机器。
  • 考虑到哪怕一个不是那么好的机器也应该有若干核:4
  • 那么我们估计一个PC的运算量在:4*30000 = 120000
  • 全球所有的PC的运算量:259390000 * 120000 = 31126800000000
  • 每天24小时,3600秒每小时。所以每天的运算量在:2689355520000000000
  • 每年365天,每年全球的运算量在:981614764800000000000 即:9.81E+20
  • 和10^21很接近了。一年时间。

能交货吗?还能更快吗?

更多参考链接:
http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf
https://sites.google.com/site/murmurhash/

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts