HyperLogLog 算法講解

HyperLogLog

更好的閱讀體驗html

基數計數基本概念

**基數計數(cardinality counting)**一般用來統計一個集合中不重複的元素個數,例如統計某個網站的UV,或者用戶搜索網站的關鍵詞數量。數據分析、網絡監控及數據庫優化等領域都會涉及到基數計數的需求。 要實現基數計數,最簡單的作法是記錄集合中全部不重複的元素集合算法

,當新來一個元素
,若
中不包含元素
​,則將
加入
,不然不加入,計數值就是
​的元素數量。這種作法存在兩個問題:

  1. 當統計的數據量變大時,相應的存儲內存也會線性增加數據庫

  2. 當集合數組

    變大,判斷其是否包含新加入元素
    ​的成本變大

機率算法

實際上目前尚未發現更好的在大數據場景中準確計算基數的高效算法,所以在不追求絕對準確的狀況下,使用機率算法算是一個不錯的解決方案。機率算法不直接存儲數據集合自己,經過必定的機率統計方法預估基數值,這種方法能夠大大節省內存,同時保證偏差控制在必定範圍內。目前用於基數計數的機率算法包括:服務器

  • Linear Counting(LC):早期的基數估計算法,LC在空間複雜度方面並不算優秀,實際上LC的空間複雜度與簡單bitmap方法是同樣的(可是有個常數項級別的下降),都是O(N​max​​);網絡

  • LogLog Counting(LLC):LogLog Counting相比於LC更加節省內存,空間複雜度只有O(log​2​​(log​2​​(N​max​​)))框架

  • HyperLogLog Counting(HLL):HyperLogLog Counting是基於LLC的優化和改進,在一樣空間複雜度狀況下,可以比LLC的基數估計偏差更小。分佈式

HLL

直觀演示

HLLDEMO函數

HLL的實際步驟

  1. 經過hash函數計算輸入值對應的比特串大數據

  2. 比特串的低 

    位對應的數字用來找到數組S中對應的位置 i

  3. t+1位開始找到第一個1出現的位置 k,將 k 記入數組

    位置

  4. 基於數組S記錄的全部數據的統計值,計算總體的基數值,計算公式能夠簡單表示爲:

HLLLLC的偏差改進,實際是基於LLC

算法來源(N次伯努利過程)

下面非正式的從直觀角度描述LLC算法的思想來源。

a爲待估集合(哈希後)中的一個元素,由上面對H的定義可知,a能夠看作一個長度固定的比特串(也就是a的二進制表示),設H哈希後的結果長度爲L比特,咱們將這L個比特位從左到右分別編號爲一、二、…、L


又由於 a是從服從均與分佈的樣本空間中隨機抽取的一個樣本,所以 a每一個比特位服從以下分佈且相互獨立。


通俗說就是a的每一個比特位爲0和1的機率各爲0.5,且相互之間是獨立的。

ρ(a)爲a的比特串中第一個「1」出現的位置,顯然1≤ρ(a)≤L,這裏咱們忽略比特串全爲0的狀況(機率爲

)。若是咱們遍歷集合中全部元素的比特串,取
爲全部**ρ(a)**的最大值。

此時咱們能夠將

做爲基數的一個粗糙估計,即:

解釋

注意以下事實:

因爲比特串每一個比特都獨立且服從0-1分佈,所以從左到右掃描上述某個比特串尋找第一個「1」的過程從統計學角度看是一個伯努利過程,例如,能夠等價看做不斷投擲一個硬幣(每次投擲正反面機率皆爲0.5),直到獲得一個正面的過程。在一次這樣的過程當中,投擲一次就獲得正面的機率爲1/2,投擲兩次獲得正面的機率是

,投擲 k次才獲得第一個正面的機率爲

如今考慮以下兩個問題:

一、進行n次伯努利過程,全部投擲次數都不大於k的機率是多少?

二、進行n次伯努利過程,至少有一次投擲次數等於k的機率是多少?

首先看第一個問題,在一次伯努利過程當中,投擲次數大於k的機率爲

,即連續擲出k個反面的機率。所以,在一次過程當中投擲次數不大於k的機率爲
。所以,n次伯努利過程投擲次數均不大於k的機率爲:

顯然第二個問題的答案是:

從以上分析能夠看出,當

時,**Pn(X≥k)**的機率幾乎爲0,同時,當
時,**Pn(X≤k)**的機率也幾乎爲0。用天然語言歸納上述結論就是:當伯努利過程次數遠遠小於
時,至少有一次過程投擲次數等於k的機率幾乎爲0;當伯努利過程次數遠遠大於
時,沒有一次過程投擲次數大於k的機率也幾乎爲0。

若是將上面描述作一個對應:一次伯努利過程對應一個元素的比特串,反面對應0,正面對應1,投擲次數k對應第一個「1」出現的位置,咱們就獲得了下面結論:

設一個集合的基數爲n,

爲全部元素中首個「1」的位置最大的那個元素的「1」的位置,若是n遠遠小於
,則咱們獲得
爲當前值的機率幾乎爲0(它應該更小),一樣的,若是n遠遠大於
,則咱們獲得
爲當前值的機率也幾乎爲0(它應該更大),所以
能夠做爲基數n的一個粗糙估計。

以上結論能夠總結爲:進行了n次進行拋硬幣實驗,每次分別記錄下第一次拋到正面的拋擲次數kk,那麼能夠用n次實驗中最大的拋擲次數

來預估實驗組數量n: 


回到基數統計的問題,咱們須要統計一組數據中不重複元素的個數,集合中每一個元素的通過hash函數後能夠表示成0和1構成的二進制數串,一個二進制串能夠類比爲一次拋硬幣實驗,1是拋到正面,0是反面。二進制串中從低位開始第一個1出現的位置能夠理解爲拋硬幣試驗中第一次出現正面的拋擲次數k,那麼基於上面的結論,咱們能夠經過屢次拋硬幣實驗的最大拋到正面的次數來預估總共進行了多少次實驗,一樣能夠能夠經過第一個1出現位置的最大值​來
預估總共有多少個不一樣的數字(總體基數)。

LogLogCounting

均勻隨機化

與LC同樣,在使用LLC以前須要選取一個哈希函數H應用於全部元素,而後對哈希值進行基數估計。H必須知足以下條件(定性的):

一、H的結果具備很好的均勻性,也就是說不管原始集合元素的值分佈如何,其哈希結果的值幾乎服從均勻分佈(徹底服從均勻分佈是不可能的,D. Knuth已經證實不可能經過一個哈希函數將一組不服從均勻分佈的數據映射爲絕對均勻分佈,可是不少哈希函數能夠生成幾乎服從均勻分佈的結果,這裏咱們忽略這種理論上的差別,認爲哈希結果就是服從均勻分佈)。

二、H的碰撞幾乎能夠忽略不計。也就是說咱們認爲對於不一樣的原始值,其哈希結果相同的機率很是小以致於能夠忽略不計。

三、H的哈希結果是固定長度的。

以上對哈希函數的要求是隨機化和後續機率分析的基礎。後面的分析均認爲是針對哈希後的均勻分佈數據進行。

分桶平均

上述分析給出了LLC的基本思想,不過若是直接使用上面的單一估計量進行基數估計會因爲偶然性而存在較大偏差。所以,LLC採用了分桶平均的思想來消減偏差。具體來講,就是將哈希空間平均分紅m份,每份稱之爲一個桶(bucket)。對於每個元素,其哈希值的前k比特做爲桶編號,其中

,然後L-k個比特做爲真正用於基數估計的比特串。桶編號相同的元素被分配到同一個桶,在進行基數估計時,首先計算每一個桶內元素最大的第一個「1」的位置,設爲M[i],而後對這m個值取平均後再進行估計,即:

這至關於物理試驗中常用的屢次試驗取平均的作法,能夠有效消減因偶然性帶來的偏差。

下面舉一個例子說明分桶平均怎麼作。

假設H的哈希長度爲16bit,分桶數m定爲32。設一個元素哈希值的比特串爲「0001001010001010」,因爲m爲32,所以前5個bit爲桶編號,因此這個元素應該納入「00010」即2號桶(桶編號從0開始,最大編號爲m-1),而剩下部分是「01010001010」且顯然ρ(01010001010)=2,因此桶編號爲「00010」的元素最大的ρ即爲M[2]的值。

誤差修正

上述通過分桶平均後的估計量看似已經很不錯了,不過經過數學分析能夠知道這並非基數n的無偏估計。所以須要修正成無偏估計。這部分的具體數學分析在「Loglog Counting of Large Cardinalities」中,過程過於艱澀這裏再也不具體詳述,有興趣的朋友能夠參考原論文。這裏只簡要提一下分析框架:

首先上文已經得出:

所以:

這是一個未知通項公式的遞推數列,研究這種問題的經常使用方法是使用生成函數(generating function)。經過運用指數生成函數和poissonization獲得上述估計量的Poisson指望和方差爲:


其中
不超過

最後經過depoissonization獲得一個漸進無偏估計量:

其中:

其中m是分桶數。這就是LLC最終使用的估計量。

偏差分析

不加證實給出以下結論:

算法應用

偏差控制

在應用LLC時,主要須要考慮的是分桶數m,而這個m主要取決於偏差。根據上面的偏差分析,若是要將偏差控制在ϵ以內,則:

內存使用分析

內存使用與m的大小及哈希值得長度(或說基數上限)有關。假設H的值爲32bit,因爲

,所以每一個桶須要5bit空間存儲這個桶的
,m個桶就是5×m/8字節。例如基數上限爲一億(約
),當分桶數m爲1024時,每一個桶的基數上限約爲
,而
,所以每一個桶須要5bit,須要字節數就是5×1024/8=640,偏差爲
,也就是約爲4%。

合併

LC不一樣,LLC的合併是以桶爲單位而不是bit爲單位,因爲LLC只需記錄桶的

,所以合併時取相同桶編號數值最大者爲合併後此桶的數值便可。

HyperLogLog Counting

HyperLogLog Counting(如下簡稱HLLC)的基本思想也是在LLC的基礎上作改進,具體細節請參考「HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm」這篇論文。

基本算法

HLLC的第一個改進是使用調和平均數替代幾何平均數。注意LLC是對各個桶取算數平均數,而算數平均數最終被應用到2的指數上,因此整體來看LLC取得是幾何平均數。因爲幾何平均數對於離羣值(例如這裏的0)特別敏感,所以當存在離羣值時,LLC的誤差就會很大,這也從另外一個角度解釋了爲何n不太大時LLC的效果不太好。這是由於n較小時,可能存在較多空桶,而這些特殊的離羣值強烈干擾了幾何平均數的穩定性。

所以,HLLC使用調和平均數來代替幾何平均數,調和平均數的定義以下:

調和平均數能夠有效抵抗離羣值的擾動。使用調和平均數代替幾何平均數後,估計公式變爲以下:

其中:

誤差分析

根據論文中的分析結論,與LLC同樣HLLC是漸近無偏估計,且其漸近標準差爲:

所以在存儲空間相同的狀況下,HLLC比LLC具備更高的精度。例如,對於分桶數m爲2^13(8k字節)時,LLC的標準偏差爲1.4%,而HLLC爲1.1%。

分段誤差修正

在HLLC的論文中,做者在實現建議部分還給出了在n相對於m較小或較大時的誤差修正方案。具體來講,設E爲估計值:

時,使用LC進行估計。

是,使用上面給出的HLLC公式進行估計。

時,估計公式如爲

關於分段誤差修正效果分析也能夠在原論文中找到。

結論

並行化

這些基數估計算法的一個好處就是很是容易並行化。對於相同分桶數和相同哈希函數的狀況,多臺機器節點能夠獨立並行的執行這個算法;最後只要將各個節點計算的同一個桶的最大值作一個簡單的合併就能夠獲得這個桶最終的值。並且這種並行計算的結果和單機計算結果是徹底一致的,所需的額外消耗僅僅是小於1k的字節在不一樣節點間的傳輸。

應用場景

基數估計算法使用不多的資源給出數據集基數的一個良好估計,通常只要使用少於1k的空間存儲狀態。這個方法和數據自己的特徵無關,並且能夠高效的進行分佈式並行計算。估計結果能夠用於不少方面,例如流量監控(多少不一樣IP訪問過一個服務器)以及數據庫查詢優化(例如咱們是否須要排序和合並,或者是否須要構建哈希表)。

參考閱讀

Redis new data structure: the HyperLogLog
HyperLogLog — Cornerstone of a Big Data Infrastructure

相關文章
相關標籤/搜索