Bloom Filter學習資料

Bloom Filter概念和原理(引用自http://blog.csdn.net/jiaomeng/archive/2007/01/27/1495500.aspx講得很好,通俗易懂

Bloom Filter概念和原理

焦萌 2007127

 

Bloom Filter是一種空間效率很高的隨機數據結構,它利用位數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合。Bloom Filter的這種高效是有一定代價的:在判斷一個元素是否屬於某個集合時,有可能會把不屬於這個集合的元素誤認爲屬於這個集合(false positive)。因此,Bloom Filter不適合那些“零錯誤”的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節省。

集合表示和元素查詢

下面我們具體來看Bloom Filter是如何用位數組表示集合的。初始狀態時,Bloom Filter是一個包含m位的位數組,每一位都置爲0

爲了表達S={x1, x2,…,xn}這樣一個n個元素的集合,Bloom Filter使用k個相互獨立的哈希函數(Hash Function),它們分別將集合中的每個元素映射到{1,…,m}的範圍中。對任意一個元素x,第i個哈希函數映射的位置hi(x)就會被置爲11ik)。注意,如果一個位置多次被置爲1,那麼只有第一次會起作用,後面幾次將沒有任何效果。在下圖中,k=3,且有兩個哈希函數選中同一個位置(從左邊數第五位)。   

 

在判斷y是否屬於這個集合時,我們對y應用k次哈希函數,如果所有hi(y)的位置都是11ik),那麼我們就認爲y是集合中的元素,否則就認爲y不是集合中的元素。下圖中y1就不是集合中的元素。y2或者屬於這個集合,或者剛好是一個false positive

錯誤率估計

前面我們已經提到了,Bloom Filter在判斷一個元素是否屬於它表示的集合時會有一定的錯誤率(false positive rate),下面我們就來估計錯誤率的大小。在估計之前爲了簡化模型,我們假設kn<m且各個哈希函數是完全隨機的。當集合S={x1, x2,…,xn}的所有元素都被k個哈希函數映射到m位的位數組中時,這個位數組中某一位還是0的概率是:

其中1/m表示任意一個哈希函數選中這一位的概率(前提是哈希函數是完全隨機的),(1-1/m)表示哈希一次沒有選中這一位的概率。要把S完全映射到位數組中,需要做kn次哈希。某一位還是0意味着kn次哈希都沒有選中它,因此這個概率就是(1-1/m)的kn次方。令p = e-kn/m是爲了簡化運算,這裏用到了計算e時常用的近似:

 

ρ爲位數組中0的比例,則ρ的數學期望E(ρ)= p’。在ρ已知的情況下,要求的錯誤率(false positive rate)爲:

(1-ρ)位數組中1的比例,(1-ρ)k就表示k次哈希都剛好選中1的區域,即false positive rate。上式中第二步近似在前面已經提到了,現在來看第一步近似。p’只是ρ的數學期望,在實際中ρ的值有可能偏離它的數學期望值。M. Mitzenmacher已經證明[2] ,位數組中0的比例非常集中地分佈在它的數學期望值的附近。因此,第一步的近似得以成立。分別將pp’代入上式中,得:

   

   

相比p’f’,使用pf通常在分析中更爲方便。

最優的哈希函數個數

既然Bloom Filter要靠多個哈希函數將集合映射到位數組中,那麼應該選擇幾個哈希函數才能使元素查詢時的錯誤率降到最低呢?這裏有兩個互斥的理由:如果哈希函數的個數多,那麼在對一個不屬於集合的元素進行查詢時得到0的概率就大;但另一方面,如果哈希函數的個數少,那麼位數組中的0就多。爲了得到最優的哈希函數個數,我們需要根據上一小節中的錯誤率公式進行計算。

 

先用pf進行計算。注意到f = exp(k ln(1 − e−kn/m)),我們令g = k ln(1 − e−kn/m),只要讓g取到最小,f自然也取到最小。由於p = e-kn/m,我們可以將g寫成

根據對稱性法則可以很容易看出當p = 1/2,也就是k = ln2· (m/n)時,g取得最小值。在這種情況下,最小錯誤率f等於(1/2)k  (0.6185)m/n。另外,注意到p是位數組中某一位仍是0的概率,所以p = 1/2對應着位數組中0和1各一半。換句話說,要想保持錯誤率低,最好讓位數組有一半還空着。

 

需要強調的一點是,p = 1/2時錯誤率最小這個結果並不依賴於近似值pf。同樣對於f’ = exp(k ln(1 − (1 − 1/m)kn))g’ = k ln(1 − (1 − 1/m)kn)p’ = (1 − 1/m)kn,我們可以將g’寫成

同樣根據對稱性法則可以得到當p’ = 1/2時,g’取得最小值。

位數組的大小

下面我們來看看,在不超過一定錯誤率的情況下,Bloom Filter至少需要多少位才能表示全集中任意n個元素的集合。假設全集中共有u個元素,允許的最大錯誤率爲є,下面我們來求位數組的位數m

 

假設X爲全集中任取n個元素的集合,F(X)是表示X的位數組。那麼對於集合X中任意一個元素x,在s = F(X)中查詢x都能得到肯定的結果,即s能夠接受x。顯然,由於Bloom Filter引入了錯誤,s能夠接受的不僅僅是X中的元素,它還能夠є (u - n)false positive。因此,對於一個確定的位數組來說,它能夠接受總共n + є (u - n)個元素。在n + є (u - n)個元素中,s真正表示的只有其中n個,所以一個確定的位數組可以表示

個集合。m位的位數組共有2m個不同的組合,進而可以推出,m位的位數組可以表示

   

個集合。全集中n個元素的集合總共有

   

個,因此要讓m位的位數組能夠表示所有n個元素的集合,必須有

   

即:

   

上式中的近似前提是nєu相比很小,這也是實際情況中常常發生的。根據上式,我們得出結論:在錯誤率不大於є的情況下,m至少要等於n log2(1/є)才能表示任意n個元素的集合。

 

上一小節中我們曾算出當k = ln2· (m/n)時錯誤率f最小,這時f = (1/2)k = (1/2)mln2 / n。現在令fє,可以推出

這個結果比前面我們算得的下界n log2(1/є)大了log2 e  1.44倍。這說明在哈希函數的個數取到最優時,要讓錯誤率不超過єm至少需要取到最小值的1.44倍。

總結

在計算機科學中,我們常常會碰到時間換空間或者空間換時間的情況,即爲了達到某一個方面的最優而犧牲另一個方面。Bloom Filter在時間空間這兩個因素之外又引入了另一個因素:錯誤率。在使用Bloom Filter判斷一個元素是否屬於某個集合時,會有一定的錯誤率。也就是說,有可能把不屬於這個集合的元素誤認爲屬於這個集合(False Positive),但不會把屬於這個集合的元素誤認爲不屬於這個集合(False Negative)。在增加了錯誤率這個因素之後,Bloom Filter通過允許少量的錯誤來節省大量的存儲空間。

 

自從Burton Bloom70年代提出Bloom Filter之後,Bloom Filter就被廣泛用於拼寫檢查和數據庫系統中。近一二十年,伴隨着網絡的普及和發展,Bloom Filter在網絡領域獲得了新生,各種Bloom Filter變種和新的應用不斷出現。可以預見,隨着網絡應用的不斷深入,新的變種和應用將會繼續出現,Bloom Filter必將獲得更大的發展。

參考資料

[1] A. Broder and M. Mitzenmacher. Network applications of bloom filters: A survey. Internet Mathematics, 1(4):485–509, 2005.

[2] M. Mitzenmacher. Compressed Bloom Filters. IEEE/ACM Transactions on Networking 10:5 (2002), 604—612.

[3] www.cs.jhu.edu/~fabian/courses/CS600.624/slides/bloomslides.pdf

[4] http://166.111.248.20/seminar/2006_11_23/hash_2_yaxuan.ppt

Bloom Filter C源碼,很簡單的源碼,幫助你理解Bloom Filter的原理(引用自http://blog.chinaunix.net/u2/76292/showart_2056033.html

 

bloom.h

#ifndef __BLOOM_H__
#define __BLOOM_H__

#include<stdlib.h>

typedef unsigned int (*hashfunc_t)(const char *);
typedef struct {
    size_t asize;
    unsigned char *a;
    size_t nfuncs;
    hashfunc_t *funcs;
} BLOOM;

BLOOM *bloom_create(size_t size, size_t nfuncs, ...);
int bloom_destroy(BLOOM *bloom);
int bloom_add(BLOOM *bloom, const char *s);
int bloom_check(BLOOM *bloom, const char *s);

#endif

 

bloom.c

#include<limits.h>
#include<stdarg.h>

#include"bloom.h"

#define SETBIT(a, n) (a[n/CHAR_BIT] |= (1<<(n%CHAR_BIT)))
#define GETBIT(a, n) (a[n/CHAR_BIT] & (1<<(n%CHAR_BIT)))

BLOOM *bloom_create(size_t size, size_t nfuncs, ...)
{
    BLOOM *bloom;
    va_list l;
    int n;
    
    if(!(bloom=malloc(sizeof(BLOOM)))) return NULL;
    if(!(bloom->a=calloc((size+CHAR_BIT-1)/CHAR_BIT, sizeof(char)))) {
        free(bloom);
        return NULL;
    }
    if(!(bloom->funcs=(hashfunc_t*)malloc(nfuncs*sizeof(hashfunc_t)))) {
        free(bloom->a);
        free(bloom);
        return NULL;
    }

    va_start(l, nfuncs);
    for(n=0; n<nfuncs; ++n) {
        bloom->funcs[n]=va_arg(l, hashfunc_t);
    }
    va_end(l);

    bloom->nfuncs=nfuncs;
    bloom->asize=size;

    return bloom;
}

int bloom_destroy(BLOOM *bloom)
{
    free(bloom->a);
    free(bloom->funcs);
    free(bloom);

    return 0;
}

int bloom_add(BLOOM *bloom, const char *s)
{
    size_t n;

    for(n=0; n<bloom->nfuncs; ++n) {
        SETBIT(bloom->a, bloom->funcs[n](s)%bloom->asize);
    }

    return 0;
}

int bloom_check(BLOOM *bloom, const char *s)
{
    size_t n;

    for(n=0; n<bloom->nfuncs; ++n) {
        if(!(GETBIT(bloom->a, bloom->funcs[n](s)%bloom->asize))) return 0;
    }

    return 1;
}

 

 

test.c

#include<stdio.h>
#include<string.h>

#include"bloom.h"

unsigned int sax_hash(const char *key)
{
    unsigned int h=0;

    while(*key) h^=(h<<5)+(h>>2)+(unsigned char)*key++;

    return h;
}

unsigned int sdbm_hash(const char *key)
{
    unsigned int h=0;
    while(*key) h=(unsigned char)*key++ + (h<<6) + (h<<16) - h;
    return h;
}

int main(int argc, char *argv[])
{
    FILE *fp;
    char line[1024];
    char *p;
    BLOOM *bloom;
    
    if(argc<2) {
        fprintf(stderr, "ERROR: No word file specified/n");
        return EXIT_FAILURE;
    }

    if(!(bloom=bloom_create(2500000, 2, sax_hash, sdbm_hash))) {
        fprintf(stderr, "ERROR: Could not create bloom filter/n");
        return EXIT_FAILURE;
    }

    if(!(fp=fopen(argv[1], "r"))) {
        fprintf(stderr, "ERROR: Could not open file %s/n", argv[1]);
        return EXIT_FAILURE;
    }

    while(fgets(line, 1024, fp)) {
        if((p=strchr(line, '/r'))) *p='/0';
        if((p=strchr(line, '/n'))) *p='/0';

        bloom_add(bloom, line);
    }

    fclose(fp);

    while(fgets(line, 1024, stdin)) {
        if((p=strchr(line, '/r'))) *p='/0';
        if((p=strchr(line, '/n'))) *p='/0';

        p=strtok(line, " /t,.;:/r/n?!-/()");
        while(p) {
            if(!bloom_check(bloom, p)) {
                printf("No match for ford /"%s/"/n", p);
            }
            p=strtok(NULL, " /t,.;:/r/n?!-/()");
        }
    }

    bloom_destroy(bloom);

    return EXIT_SUCCESS;
}

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