HyperLogLog:海量資料下的基數計算

weixin_33936401發表於2018-05-15

1. 什麼是基數計算

基數計算(cardinality counting)指的是統計一批資料中的不重複元素的個數,常見於計算獨立使用者數(UV)、維度的獨立取值數等等。實現基數統計最直接的方法,就是採用集合(Set)這種資料結構,當一個元素從未出現過時,便在集合中增加一個元素;如果出現過,那麼集合仍保持不變。
在大資料的場景中,實現基數統計往往去面臨以下的兩個問題:

  • 如果有效的儲存原始資料,以避免資料佔用空間過多,這裡就涉及到儲存空間壓縮的問題
  • 如果能夠跨不同的維度、不同的時間段實現基數計算,比如在計算日度UV的情況下,如果計算出周度、或者月度的UV

本文旨在介紹目前比較成熟的基數計算的方式,並通過例項對比他們在解決以上兩個問題上的效果,最後引出本文的重點,HyperLogLog演算法的實現和應用。

2. Bitmap

2.1 基本原理

Bitmap進行基數計算的方法,是先定義一個bit陣列,陣列中的每一位對應資料的一種取值。由於bit是計算機中的最小單位,使用bit可以大量的減少儲存空間。例如一個陣列[1,3,4,5],那麼對應的bitmap即為[0,0,0,1,1,1,0,1],後續每新增加一個元素,就和現有的bitmap進行OR操作,通過計算bitmap中1的個數,即可以得到基數計算的結果。

10148685-5c5cc27b62ecc2b4.png

正是因為bitmap之間對OR也是良好支援的,兩個bitmap在進行OR操作之後,便是這兩個條件組合下的基數計算結果,因此使用bitmap是可以實現任意條件下的基數計算。

2.2 空間使用

按照上面介紹的原理,進行bitmap的構建。假如統計1億資料的基數值,大約需要記憶體100000000/8/1024/1024 ~= 12M,如果有100個這樣的物件,就需要1.2G的記憶體空間,可見佔用記憶體還是很大的,在實際業務中基本很少使用。

3. Linear Counting

3.1 基本原理

Linear Counting是採用概率的方式進行基數估計的最簡單的方法。下面通過一個例項描述Linear Counting的計算過程:

  • 資料雜湊:假設原始資料的基數為n,使用一組雜湊空間為m的雜湊函式H,將原始資料轉換為滿足均勻分組的一組雜湊陣列。
  • 分桶資料統計:構建一個長度為m的bitmap,其中每一個bit對應雜湊空間的一個值。生成雜湊陣列的值如果存在,則把相應的bit設定為1。當所有值設定完成後,統計bitmap中為0的bit數為u。


    10148685-ed6c16233b9759b9.png

可以通過下述的公式計算基數估計的結果:


10148685-359e16f7a8a71c97.gif

注意這裡的log指的是自然對數。

公式的推導過程有興趣的可以參考這篇文章。其中最重要的是要清楚,在經過n次資料的雜湊後,bitmap中的某個bit值為0,是一個伯努利事件。記住這一點再理解公式推導就容易多了。

3.2 空間使用

Linear Counting的空間使用,和bitmap相比,空間複雜度是一致的,僅有線性下降。因此如果對於1億的原始資料,仍然需要MB級別的記憶體空間儲存。Linear Counting在實際應用中也很少被使用。

4. LogLog Counting

4.1 伯努利過程

在介紹LogLog Counting之前,我們先來回顧一下伯努利過程的概念。

伯努利過程是一個由有限個或無限個的獨立隨機變數 X1, X2, X3 ,..., 所組成的離散時間隨機過程,其中 X1, X2, X3 ,..., 滿足如下條件:
對每個 i, Xi 等於 0 或 1; 對每個 i, Xi = 1 的概率等於 p. 換言之,伯努利過程是一列獨立同分布的伯努利試驗。每個Xi 的2個結果也被稱為“成功”或“失敗”。所以當用數字 0 或 1 來表示的時候,這個數字被稱為第i個試驗的成功次數。

舉一個常見的例子:每次拋硬幣之後,出現正面和反面的概率分別為1/2,如果不停地拋硬幣,直至出現正面為止,這就是一個伯努利過程。
這樣,我們假設一共進行了n次伯努利過程,出現正面的次數分別為k1, k2, ... kmax,那麼有以下兩個結論:

  • n次伯努利過程的投擲次數都不大於kmax
  • n次伯努利過程,至少有一次的投擲次數等於kmax

已知投擲k次才出現正面的概率為:1/2^k,那麼:

  • 第一種情況的概率為:


    10148685-f1c5b487659d7357.gif
  • 第二種情況的概率為:


    10148685-bcfd1fa426cf6e28.gif

如果n >> 2^k,則P(x >= kmax)為0;如果n << 2^k,則P(x <= kmax)為0。因此我們可以用2^k來作為n的近似估計結果。

4.2 伯努利過程與LogLog Counting

如何將基數計算,等價地認為是一個伯努利過程,是LogLog Counting的關鍵所在。這裡我們可以把原始資料進行雜湊,雜湊後的陣列是滿足均勻分佈的。把每個元素看做一個投擲硬幣的過程,將元素轉換為2進位制之後,每個bit出現0和1的概率是相等的。從高位開始查,第一次出現1的位置記為k,即等同於投擲硬幣時出現第一個正面,將所有元素出現1的位置的最大值,記為kmax,那麼2^kmax就是基數計算的結果。

4.3 LogLog Counting計算過程
  • 資料雜湊:這個和Linear Counting是類似的,都需要保證雜湊後的資料是滿足均勻分佈的。
  • 轉換為2進位制:將雜湊後的每個元素,都轉換為2進位制,由於資料在雜湊後滿足均勻分佈的,2進位制資料每一個bit的0和1出現的概率都是1/2,這裡設2進位制資料的位數為L。
  • 統計第一個1出現的位置:分別計算每個2進位制資料,第一次出現1的次數。所有次數中的最大值為kmax,那麼2^kmax就是最終結果。

下圖中給出一個針對一個元素進行k值計算的過程。


10148685-9d1d1c71ae425b69.png
4.4 資料分桶

上面的計算過程,由於是單一估計量,可能會出現一定的偶然性導致誤差。因此這裡引入資料分桶的方法。取雜湊空間分成m個桶,用雜湊值的前幾個bit的值來決定資料屬於哪一個桶,再對桶內的資料取k值,最終計算出kmax。再將所有桶的kmax取平均數,這樣就通過多次估計取平均的方式,消除了單一估計可能存在的偶然性誤差。計算公式如下:


10148685-64efcb3e89d0233e.gif
4.5 誤差修正和分析

以上的過程仍然是存在誤差的,並不是無偏估計。將上述過程修正為無偏估計的過程由於過於複雜,這裡就不再介紹了。需要了解的是,最終結果的誤差公式為:


10148685-25fdb847964e4204.gif
4.6 空間使用

到這裡,我們就可以理解LogLog Counting中兩個log了,它們的含義分別如下:

  • 第一次log,和Linear Counting很類似,由於使用雜湊,壓縮了資料空間。
  • 第二次log,由於在儲存雜湊值的第一次1出現的次數時,只需要儲存結果k,而不需要儲存原始的雜湊值,進一步節省了空間。

加入雜湊之後的值有32bit,那麼每個桶需要5bit儲存kmax的值,m個桶就是m5/8 B。
如果基數是1億個(227),當分桶數為1024時,每個桶的基數上限為227 / 2^10 = 217,log(log(217))=4.09,那麼每個桶需要5bit儲存kmax,總共需要的空間為5
1024/8,等於640B,可見是非常小的。

5. Adapative Counting

通過概率計算可知,LogLog Counting由於使用了幾何平均值,可能出現在基數較小的情況,有些桶是為空的。空桶對於最後平均值的計算干擾較大。
Adapative Counting的思想是將Linear Counting和LogLog Counting進行結合。Linear Counting和LogLog Counting的儲存結構是類似的,僅僅是Linear Couting關心桶是否為空,而LogLog Counting需要桶中的kmax。

最終分析得到的結論是:在空桶率大於0.051時,使用Linear Counting的偏差率更小;在空桶率小於0.051時,使用LogLog Counting的偏差率更小。

6. HyperLogLog Counting

HyperLogLog Counting,是在LogLog Counting的基礎上,將桶之間計算採用的幾何平均,修改為調和平均,可以有效的減少空桶對於平均值的影響。
調和平均的計算公式如下:


10148685-528b9c4fca4f8396.gif

使用調和平均後的偏差公式為:


10148685-55df934744a77e60.gif

可見偏差期望和LogLog Counting相比要更小。

參考文獻

  1. Sketch of the Day: HyperLogLog — Cornerstone of a Big Data Infrastructure
  2. 解讀Cardinality Estimation演算法(第一部分:基本概念)

相關文章