Probabilistic Data Structures for Web Analytics and Data Mininghtml
對於big data常常須要作以下的查詢和統計,web
Cardinality Estimation (基數或勢), 集合中不一樣元素的個數, 好比, 獨立訪客(Unique Visitor,簡稱UV)統計算法
Frequency Estimation, 估計某個element重複出現次數, 好比, 某個用戶對網站訪問次數數據結構
Heavy Hitters, top-k elements, 好比, 銷量top-100的商鋪dom
Range Query, 好比找出年齡在20~30之間的用戶數據結構和算法
Membership Query, 是否包含某個element, 好比, 該用戶名是否已經被註冊.wordpress
固然你能夠採用精確的數據結構, sorted table或hash table, 結果是須要耗費的空間比較大, 如圖中對於40M數據, 須要4~7M的數據.
可是其實在不少狀況下, 咱們不須要很精確的結果, 能夠容忍較小的偏差, 那麼在這種狀況下, 咱們就可使用些基於機率的數據結構來大大提升時空效率.函數
解讀Cardinality Estimation算法(第一部分:基本概念)測試
Big Data Counting: How to count a billion distinct objects using only 1.5KB of Memory優化
Linear Counting, 比較簡單的一種方法, 相似於Bitmap, 至少在實現上看沒有什麼不一樣, 最終經過數有多少'1'來判斷個數
區別在於, Bitmap是精確的方法直接用'1'的個數來表示Cardinality, 因此必需要分配足夠的空間以免衝突, 好比Cardinality上限爲10000的集合, 就須要分配10000bit的bitmap
而Linear Counting, 是機率近似的方法, 容許衝突, 只要選取合適的m(bitset的大小), 就能夠根據'1'的個數來推斷出近似的Cardinality.
class LinearCounter { BitSet mask = new BitSet(m) // m is a design parameter void add(value) { int position = hash(value) // map the value to the range 0..m mask.set(position) // sets a bit in the mask to 1 } }
因此接下來的問題就是,
如何根據'1'的個數來推斷出近似的Cardinality?
如何選取合適的m? m太大浪費空間, m過小會致使全部bit都被置1從而沒法估計, 因此必須根據Cardinality上限n計算出合適的m
參考下面的公式, 第一個公式就是根據m和w(1的個數)來計算近似的Cardinality
優勢, 簡單, 便於多集合合併(多個bitset直接or便可)
缺點, 空間效率不夠理想, m大約爲n的十分之一, 空間複雜度仍爲O(Nmax)
Case Study, 收到各個網站的用戶訪問log, 須要支持基於時間範圍和網站範圍的UV查詢
對於每一個網站的每一個時間單元(好比小時)創建Linear Counting, 而後根據輸入的時間和網站範圍進行or合併, 最終計算出近似值
這個數據結構和算法比較複雜, 但基於的原理仍是能夠說的清楚的
首先, 須要將集合裏面全部的element進行hash, 這裏的hash函數必需要保證服從均勻分佈(即便集合裏面的element不是均勻的), 這個前提假設是Loglog Counting的基礎
在均勻分佈的假設下, 產生的hash value就有以下圖中的分佈比例, 由於每一個bit爲0或1的機率都是1/2, 因此開頭連續出現的0的個數越多, 出現機率越小, 須要嘗試伯努利過程的次數就越多
Loglog Counting就是根據這個原理, 根據出現的最大的rank數, 來estimate伯努利過程的次數(即Cardinality)
假設設ρ(a)爲a的比特串中第一個"1」出現的位置, 即前面出現連續ρ(a)-1個0, 其實這是個伯努利過程
集合中有n個elements, 而每一個element的ρ(a)都小於k的機率爲, 當n足夠大(>>2^k)的時候接近0
反之, 至少有一個element大於k的機率爲, 當n足夠小(<<2^k)的時候接近0
因此當在集合中出現ρ(a) = k時, 說明n不可能遠大於或遠小於2^k(從機率上講)
故當取得一個集合中的Max(ρ(a))時, 能夠將2^Max(ρ(a))做爲Cardinality的近似值
但這樣的方案的問題是, 偶然性因素影響比較大, 由於小几率事件並非說不會發生, 從而帶來較大的偏差
因此這裏採用分桶平均的方式來平均偏差,
將哈希空間平均分紅m份,每份稱之爲一個桶(bucket)。對於每個元素,其哈希值的前k比特做爲桶編號,其中2^k=m,然後L-k個比特做爲真正用於基數估計的比特串。桶編號相同的元素被分配到同一個桶,在進行基數估計時,首先計算每一個桶內元素最大的第一個「1」的位置,設爲M[i],而後對這m個值取平均後再進行估計,
class LogLogCounter { int H // H is a design parameter, hash value的bit長度 int m = 2^k // k is a design parameter, 劃分的bucket數 etype[] estimators = new etype[m] // etype is a design parameter, 預估值的類型(ex,byte), 不一樣rank函數的實現能夠返回不一樣的類型 void add(value) { hashedValue = hash(value) //產生H bits的hash value bucket = getBits(hashedValue, 0, k) //將前k bits做爲桶號 estimators[bucket] = max( //對每一個bucket只保留最大的預估值 estimators[bucket], rank( getBits(hashedValue, k, H) ) //用k到H bits來預估Cardinality ) } getBits(value, int start, int end) //取出從start到end的bits段 rank(value) //取出ρ(value) }
優勢, 空間效率顯著優化, 能夠支持多集合合併(對每一個bucket的預估值取max)
缺點, n不是特別大時, 計偏差過大, HyperLogLog Counting和Adaptive Counting就是這類改進算法
估計某個element的出現次數
正常的作法就是使用sorted table或者hash table, 問題固然就是空間效率
因此咱們須要在犧牲必定的準確性的狀況下, 優化空間效率
這個方法比較簡單, 原理就是, 使用二維的hash table, w是hash table的取值空間, d是hash函數的個數
對某個element, 分別使用d個hash函數計算相應的hash值, 並在對應的bucket上遞增1, 每一個bucket的值稱爲sketch, 如圖
而後在查詢某個element的frequency時, 只須要取出全部d個sketch, 而後取最小的那個做爲預估值, 如其名
由於爲了節省空間, w*d是遠小於真正的element個數的, 因此必然會出現不少的衝突, 而最小的那個應該是衝突最少的, 最精確的那個
這個方法的思路和bloom filter比較相似, 都是經過多個hash來下降衝突帶來的影響
class CountMinSketch { long estimators[][] = new long[d][w] // d and w are design parameters long a[] = new long[d] long b[] = new long[d] long p // hashing parameter, a prime number. For example 2^31-1 void initializeHashes() { //初始化hash函數family,不一樣的hash函數中a,b參數不一樣 for(i = 0; i < d; i++) { a[i] = random(p) // random in range 1..p b[i] = random(p) } } void add(value) { for(i = 0; i < d; i++) estimators[i][ hash(value, i) ]++ //簡單的對每一個bucket經行疊加 } long estimateFrequency(value) { long minimum = MAX_VALUE for(i = 0; i < d; i++) minimum = min( //取出最小的估計值 minimum, estimators[i][ hash(value, i) ] ) return minimum } hash(value, i) { return ((a[i] * value + b[i]) mod p) mod w //hash函數,a,b參數會變化 } }
優勢, 簡單, 空間效率顯著優化
缺點, 對於大量重複的element或top的element比較準確, 但對於較少出現的element準確度比較差
實驗, 對於Count-Min sketch of size 3×64, i.e. 192 counters total
Dataset1, 10k elements, about 8500 distinct values, 較少重複的數據集, 測試結果準確度不好
Dataset2, 80k elements, about 8500 distinct values, 大量重複的數據集, 測試結果準確度比較高
前面說了Count-Min Sketch只對重度重複的數據集有比較好的效果, 但對於中度或輕度重複的數據集, 效果就不好
由於大量的衝突對較小頻率的element的干擾很大, 因此Count-Mean-Min Sketch就是爲了解決這個問題
原理也比較簡單, 預估sketch上可能產生的noise
怎麼預估? 很簡單, 好比1000數hash到20個bucket裏面, 那麼在均勻分佈的條件下, 一個bucket會被分配50個數
那麼這裏就把每一個sketchCounter裏面的noise減去
最終是取全部sketch的median(中位數), 而不是min
class CountMeanMinSketch { // initialization and addition procedures as in CountMinSketch // n is total number of added elements long estimateFrequency(value) { long e[] = new long[d] for(i = 0; i < d; i++) { sketchCounter = estimators[i][ hash(value, i) ] noiseEstimation = (n - sketchCounter) / (w - 1) e[i] = sketchCounter – noiseEstimator } return median(e) } }
首先top element應該是重度重複的element, 因此使用Count-Min Sketch是沒有問題的
方法,
1. 建個Count-Min Sketch不斷的給全部的element進行計數
2. 須要取top的時候, 對集合中每一個element從Count-Min Sketch取出近似的frequency, 而後放到heap中
其實這裏使用Count-Min Sketch只是計算frequency, Top-n問題仍然是依賴heap來解決
use case, 好比網站IP訪問數的排名
另一種獲取top的思路,
維護一組固定個數的slots, 好比你要求Top-10, 那麼維護10個slots
當elements過來, 若是slots裏面有, 就遞增, 沒有就替換solts中frequency最小的那個
這個算法沒有講清楚, 給的例子也太簡單, 不太能理解e(maximum potential error)幹嘛用的, 爲何4替換3後, 3的frequency做爲4的maximum potential error
個人理解是, 由於3的frequency自己就是最小的, 因此4繼承3的frequency不會影響實際的排名,
這樣避免3,4交替出現所帶來的計數問題, 但這裏的frequency就不是精確的, 3的frequency被記入4是potential error
The figure below illustrates how Stream-Summary with 3 slots works for the input stream {1,2,2,2,3,1,1,4}.
RangeQuery, 毫無疑問須要相似B-tree這樣排序的索引, 對於大部分NoSql都很難支持
這裏要實現的是, SELECT count(v) WHERE v >= c1 AND v < c2, 在必定範圍內的element的個數和
簡單的使用Count-Min Sketch的方法, 就是經過v的索引找出全部在範圍內的element, 而後去Count-Min Sketch中取出每一個element的近似frequency, 而後相加
這個方法的問題在於, 在範圍內的element可能很是多, 而且那麼多的近似值相加, 偏差會被大大的放大
解決辦法就是使用多個Count-Min Sketch, 來提供更粗粒度的統計
如圖, sketch1就是初始的, 以element爲單位的統計, 沒一個小格表明一個element
sketch2, 以2個element爲單位統計, 實際的作法就是truncate a one bit of a value, 好比1110111, 前綴匹配111011.
sketch3, 以4個element爲單位統計......
最終sketchn, 全部element只會分兩類統計, 1開頭或0開頭
這樣再算範圍內的count, 就不須要一個個element加了, 只須要從粗粒度開始匹配查詢
以下圖, 只須要將4個紅線部分的值相加就能夠了
MADlib (a data mining library for PostgreSQL and Greenplum) implements this algorithm to process range queries and calculate percentiles on large data sets.
查詢某個element在不在, 典型的Bloom Filter的應用