1. 程式人生 > >【原創】算法分享(4)Cardinality Estimate 基數計數概率算法

【原創】算法分享(4)Cardinality Estimate 基數計數概率算法

設置 value 可能 alt 第一個 公司 cat linear ica

讀過《編程珠璣》(<Programming Pearls>)的人應該還對開篇的Case記憶猶新,大概的場景是:

作者的一位在電話公司工作的朋友想要統計一段時間內不同的電話號碼的個數,電話號碼的數量很大,當時的內存很小,所以不能把所有的電話號碼全部放到內存來去重統計,他的朋友很苦惱。

作者聰明的想到了用bit數組來解決問題,每個電話號碼可以映射為bit數組的index,bit數組初始狀態所有位為0,所有電話號碼逐一處理:將bit數組對應位置為1,處理完之後統計bit數組中有多少個1即可。

示例:[0,1,0,0,0,1,0,...] 這個bit數組表示2和5存在

不得不說這種想法非常精妙,即減少了內存占用(8位電話號碼如果全部放到內存需要381M(每個電話號碼存成Integer占4Byte計算),而使用bit數組只需要11M),而且只需要兩次循環就可以得到結果;

這是一個基數計數的問題,Cardinality:estimating the number of distinct elements.

1 Bitmap

上邊提到的方式是使用bitmap,思路是將dataset中的每一個element映射到一個bit位,不允許沖突,所需要的內存空間大概為基數*1bit(上例中是100,000,000bit),並且計數精準;

但是當基數非常大時,即使bitmap內存也放不下!

好消息是如果不要求計數精準(允許一定範圍內的誤差),可以采用概率估算算法:

2 LC:Linear Counting

公式

技術分享圖片

其中:n是估算值,m是bitmap大小,Vn是bitmap中0出現的比率,比如0.1;

原理

使用固定大小的hashtable來存放dataset,可以想見:

  • dataset的基數越小,hashtable越空,當基數為0時hashtable所有的key都是空的
  • dataset的基數越大,hashtable越滿,沖突也越多,當基數無窮大時hashtable所有的key都被占用並且每個key都有大量沖突

所以可以根據hashtable中key被占用的情況來估算dataset的基數,這裏主要用到了對數曲線的特性(0<x<1這一段):

技術分享圖片

過程

初始化一個bitmap,所有位為0,dataset的每一個element都hash到bitmap的一個bit位,hash之後將對應的bit位置為1,hash允許沖突,最後根據bitmap中0的數量和bitmap大小由公式來估算基數;

註意:雖然LC用的也是bitmap,但是相比原始的bitmap算法,LC的bitmap大小可以比基數小很多,因為LC的映射允許沖突,另外可以設置bitmap大小來決定誤差的大小;

參考:A linear-time probabilistic counting algorithm for database applications

https://wenku.baidu.com/view/9c4489ee0975f46527d3e1d1.html

3 LLC:LogLog Counting

公式

技術分享圖片
其中:m是桶的數量,M為桶的集合,k是用於分桶的位數,ρ為bit數組中第一個為1的下標即index,E是估算值;

原理

來看拋硬幣的過程,拋硬幣的過程是伯努利Bernoulli過程,每次的結果要麽是0,要麽是1,並且概率均為1/2,假設一次拋硬幣game定義為拋到1為止,可能第一次就拋到1(P=0.5),也可能前邊拋了i次0最後才拋到1(P=1-0.5^i);拋硬幣game玩的次數越多,越容易出現前邊很多次都是0的情況,這是因為開頭連續出現的0的個數越多,出現概率越小,需要嘗試伯努利過程的次數就越多,所以可以利用概率根據結果(開頭出現0的個數,即i)來反推出條件(game玩了多少次,即n=2^i);

但是由於隨機性的存在導致誤差較大,所以通過將dataset分為m份,每份單獨統計,最後取算數平均值的方式來降低隨機性從而減小誤差;

過程

dataset的每一個element先映射到一個bit數組,比如32位bit數組,將這個bit數組的1到k位的值作為桶的bucket_index(即第幾個桶),將k+1到32位中第一個為1的index作為value放到桶中,如果桶裏已經有value,桶會保存一個最大的value,數據集元素全部映射完之後,將所有桶的value取算數平均值,根據n=2^i,這樣可以得到每個桶內的基數,再乘以m可以得到整個dataset的基數,公式最前邊的α是修正參數;

比如k=2,則m=2^k=4,即4個桶,dataset中一個element映射的bit數組為[1,0,0,0,0,1,0,...],取前兩位[1,0]對應的值是2,即第2個桶,取第3位之後的數組[0,0,0,1,0,...]可見第一個位1的index是3,將3放到桶2中,以此類推;

參考:Loglog Counting of Large Cardinalities

http://algo.inria.fr/flajolet/Publications/DuFl03-LNCS.pdf

4 HLLC:Hyper LogLog Counting

公式

技術分享圖片

原理

同LLC,只有一點不同:取均值的時候不使用算數平均數而改用調和平均數

參考:HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm

http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf

5 最優實踐

公式

技術分享圖片

過程

在大量實踐中根據各個參數和結果的情況進行調優,綜合使用HLLC和LC等算法

參考:

http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/40671.pdf

總結

目前HLLC在很多開源組件中都有應用,比如redis、druid等

技術分享圖片

其他:

https://research.neustar.biz/2012/10/25/sketch-of-the-day-hyperloglog-cornerstone-of-a-big-data-infrastructure/

【原創】算法分享(4)Cardinality Estimate 基數計數概率算法