HyperLogLog原理

1. 背景

基數(cardinality)統計,即求一個集合中,不重複的元素個數。例如集合{1,1,2,3,4}的基數是4。
在互聯網中,典型的應用場景就是uv統計,下面就用uv統計作爲例子去闡述。

對於uv統計,最簡單的做法,是對被統計項,維護一個set去重,但這樣做會有兩個問題:

  1. 如果uv統計上限很高,那麼這個set的空間開銷就很大
  2. 如果被統計項有很多個,例如對於每一樣商品,都要統計uv,那麼空間開銷巨大

針對這種情況,我們可以用允許一點誤差,用概率統計的方法,將空間消耗極大降低

這裏的set也能用BloomFilter去做,有誤差但開銷也比一般的用要小很多。

2. 思路和原理

2.1 去重

首先,這裏的關鍵問題儘量降低代價的去重。顯然,可以用哈希,hash(user_id)就把string變成整數,每個user_id都能唯一確定一個整數(看你有衝突),這樣就去重來。然後再看看怎麼統計。

2.2 統計

既然是基於概率的統計方法。
我們想想拋硬幣,反面記0,正面記1,正反面的概率都是1/2。第一次出現正面的位置記爲ρ(x),那麼ρ(0001)=3,0001出現的概率是1/23=1/16。換句話講,就是進行16次實驗,很可能出現一次或以上0001。再換句話講,進行n輪實驗,最大ρ(x)爲y,那麼可以估算進行出n=2y

然後,我們只要把要去重的key,轉換成一串01字符串,就能套用上面的統計方法了。

記hash函數的最大值爲2L,把hash(key)看成長度爲L的01串,換句話說,hash(key)就是進行L次拋硬幣,並且每次只要key相同,拋硬幣的結果就相同(去重了),然後從左到右找第一個1的位置就ok了。例如:
有三個key,相當於進行三次試驗
hash(key1) = 01010110,ρ(01010110) = 2
hash(key2) = 01110010,ρ(01110010) = 2
hash(key3) = 00100110,ρ(00100110) = 3
最大值是3,所以根據概率看,有23=8次。可以看到,在數據量小時,誤差會比較大,而且根據這個算法,統計出來的數字只會是2的次冪,雖然這樣,但是基本思想已經掌握,接下來的就是優化了。

2.3 優化

2.3.1 分桶(log counting算法)

直接用最大的ρ(x),受隨機事件的影響很大,例如如果前幾次就來一個0000000000000001。有一個方法,可以降低這種影響,就是分桶取平均數,例如分4個桶,取前兩位作爲桶的標誌,
hash(key1) = 01010110,ρ(01010110) = 2,bucket 01
hash(key2) = 01110010,ρ(01110010) = 0,bucket 01
hash(key3) = 00000011,ρ(00000011) = 5,bucket 00

bucket max ρ
bucket 00 5
bucket 01 2
bucket 10 0
bucket 11 0
bucket avg (5+2)/4,向上取整得2

所以估算值爲22=4,這樣影響就比較小了

2.3.2 調和平均數

但是如果遇到更極端的隨機事件,例如hash函數最大是232,去到最後一位,對分桶取算數平均數的影響還是很大的,怎麼辦呢?數學上有個叫調和平均數的東西,我們用調和平均數取代算數平均數即可。

2.4 合併

多個HLL取並集,很簡單,就是對比相同位置上的bucket,只保留最大的bucket。

2.5 最終公式與誤差


const常數的選擇

// m 爲桶數,p是m的以2爲底的對數
switch (p) {
   case 4:
       constant = 0.673 * m * m;
   case 5:
       constant = 0.697 * m * m;
   case 6:
       constant = 0.709 * m * m;
   default:
       constant = (0.7213 / (1 + 1.079 / m)) * m * m;
}

在剛開始樣本比較少的時候,用上面的算法還是容易偏大,這時可以用下面的方法估算:

(DV代表估計的基數值,m代表桶的數量,V代表結果爲0的桶的數目,log表示自然對數)
if DV < (5 / 2) * m:
    DV = m * log(m/V)

參考文章:https://www.jianshu.com/p/55defda6dcd2

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章