更好的閱讀體驗html
**基數計數(cardinality counting)**一般用來統計一個集合中不重複的元素個數,例如統計某個網站的UV,或者用戶搜索網站的關鍵詞數量。數據分析、網絡監控及數據庫優化等領域都會涉及到基數計數的需求。 要實現基數計數,最簡單的作法是記錄集合中全部不重複的元素集合算法
當統計的數據量變大時,相應的存儲內存也會線性增加數據庫
當集合數組
實際上目前尚未發現更好的在大數據場景中準確計算基數的高效算法,所以在不追求絕對準確的狀況下,使用機率算法算是一個不錯的解決方案。機率算法不直接存儲數據集合自己,經過必定的機率統計方法預估基數值,這種方法能夠大大節省內存,同時保證偏差控制在必定範圍內。目前用於基數計數的機率算法包括:服務器
Linear Counting(LC):早期的基數估計算法,LC在空間複雜度方面並不算優秀,實際上LC的空間複雜度與簡單bitmap方法是同樣的(可是有個常數項級別的下降),都是O(Nmax);網絡
LogLog Counting(LLC):LogLog Counting相比於LC更加節省內存,空間複雜度只有O(log2(log2(Nmax)))框架
HyperLogLog Counting(HLL):HyperLogLog Counting是基於LLC的優化和改進,在一樣空間複雜度狀況下,可以比LLC的基數估計偏差更小。分佈式
HLLDEMO函數
經過hash函數計算輸入值對應的比特串大數據
比特串的低
t+1位開始找到第一個1出現的位置 k,將 k 記入數組
基於數組S記錄的全部數據的統計值,計算總體的基數值,計算公式能夠簡單表示爲:
HLL是LLC的偏差改進,實際是基於LLC。
下面非正式的從直觀角度描述LLC算法的思想來源。
設a爲待估集合(哈希後)中的一個元素,由上面對H的定義可知,a能夠看作一個長度固定的比特串(也就是a的二進制表示),設H哈希後的結果長度爲L比特,咱們將這L個比特位從左到右分別編號爲一、二、…、L:
設ρ(a)爲a的比特串中第一個「1」出現的位置,顯然1≤ρ(a)≤L,這裏咱們忽略比特串全爲0的狀況(機率爲
此時咱們能夠將
注意以下事實:
因爲比特串每一個比特都獨立且服從0-1分佈,所以從左到右掃描上述某個比特串尋找第一個「1」的過程從統計學角度看是一個伯努利過程,例如,能夠等價看做不斷投擲一個硬幣(每次投擲正反面機率皆爲0.5),直到獲得一個正面的過程。在一次這樣的過程當中,投擲一次就獲得正面的機率爲1/2,投擲兩次獲得正面的機率是
如今考慮以下兩個問題:
一、進行n次伯努利過程,全部投擲次數都不大於k的機率是多少?
二、進行n次伯努利過程,至少有一次投擲次數等於k的機率是多少?
首先看第一個問題,在一次伯努利過程當中,投擲次數大於k的機率爲
顯然第二個問題的答案是:
從以上分析能夠看出,當
若是將上面描述作一個對應:一次伯努利過程對應一個元素的比特串,反面對應0,正面對應1,投擲次數k對應第一個「1」出現的位置,咱們就獲得了下面結論:
設一個集合的基數爲n,
以上結論能夠總結爲:進行了n次進行拋硬幣實驗,每次分別記錄下第一次拋到正面的拋擲次數kk,那麼能夠用n次實驗中最大的拋擲次數
與LC同樣,在使用LLC以前須要選取一個哈希函數H應用於全部元素,而後對哈希值進行基數估計。H必須知足以下條件(定性的):
一、H的結果具備很好的均勻性,也就是說不管原始集合元素的值分佈如何,其哈希結果的值幾乎服從均勻分佈(徹底服從均勻分佈是不可能的,D. Knuth已經證實不可能經過一個哈希函數將一組不服從均勻分佈的數據映射爲絕對均勻分佈,可是不少哈希函數能夠生成幾乎服從均勻分佈的結果,這裏咱們忽略這種理論上的差別,認爲哈希結果就是服從均勻分佈)。
二、H的碰撞幾乎能夠忽略不計。也就是說咱們認爲對於不一樣的原始值,其哈希結果相同的機率很是小以致於能夠忽略不計。
三、H的哈希結果是固定長度的。
以上對哈希函數的要求是隨機化和後續機率分析的基礎。後面的分析均認爲是針對哈希後的均勻分佈數據進行。
上述分析給出了LLC的基本思想,不過若是直接使用上面的單一估計量進行基數估計會因爲偶然性而存在較大偏差。所以,LLC採用了分桶平均的思想來消減偏差。具體來講,就是將哈希空間平均分紅m份,每份稱之爲一個桶(bucket)。對於每個元素,其哈希值的前k比特做爲桶編號,其中
這至關於物理試驗中常用的屢次試驗取平均的作法,能夠有效消減因偶然性帶來的偏差。
下面舉一個例子說明分桶平均怎麼作。
假設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,因爲
與LC不一樣,LLC的合併是以桶爲單位而不是bit爲單位,因爲LLC只需記錄桶的
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爲估計值:
當
當
當
關於分段誤差修正效果分析也能夠在原論文中找到。
這些基數估計算法的一個好處就是很是容易並行化。對於相同分桶數和相同哈希函數的狀況,多臺機器節點能夠獨立並行的執行這個算法;最後只要將各個節點計算的同一個桶的最大值作一個簡單的合併就能夠獲得這個桶最終的值。並且這種並行計算的結果和單機計算結果是徹底一致的,所需的額外消耗僅僅是小於1k的字節在不一樣節點間的傳輸。
基數估計算法使用不多的資源給出數據集基數的一個良好估計,通常只要使用少於1k的空間存儲狀態。這個方法和數據自己的特徵無關,並且能夠高效的進行分佈式並行計算。估計結果能夠用於不少方面,例如流量監控(多少不一樣IP訪問過一個服務器)以及數據庫查詢優化(例如咱們是否須要排序和合並,或者是否須要構建哈希表)。
Redis new data structure: the HyperLogLog
HyperLogLog — Cornerstone of a Big Data Infrastructure