Bloom Filter原理

本文将介绍:

  • Bloom Filter和它的变形与拓展
  • Bloom Filter的使用场景
  • Bloom Filter的详细数学分析
  • D-left hashing
  • D-left counting bloom filter

提出问题

Google的爬虫每天需要抓取大量的网页。于是就有一个问题:每当爬虫分析出一个url的时候,是抓呢,还是不抓呢?如何知道这个url已经爬过了?这个问题,归纳抽象后可以定义为:给定一个集合S(注意,这里的集合是传统意义上的集合:元素彼此不同。本文不考虑multiset),给定一个元素e,需要判断 eS 是否成立。(学术界一般称为membership问题)


分析问题

都有哪些方案可以解决这个问题?

一种简单的想法是把url存储在一个哈希表中,每次去表里look up下判断是否存在。假如每个url占用40B,那么10亿条url将占用大概30多GB的内存!Can this be more space efficient ?

解决问题

我们可不可以不存url本身?这样子所需空间就会大大减少了。于是我们想到一个很经典的做法:bitmap(位图)。将集合S中的url哈希到bitmap上,给定一个url,只需要将它hash,得到它在bitmap的下标,检查该位置是否为1即可。

这样做空间是省了,可是也产生了一个问题:由于冲突(碰撞),不是集合S中的元素也可能被哈希到值为1的位置上,导致误报。

给定一个元素e,如果实际上 eS ,而被判为 eS ,那么我们称e是false positive(伪正例。顺便说一句,false positive等的分析在machine learning的classification任务里评价model时非常重要)。

如何降低false positive的概率呢?Bloom Filter的想法是使用多个独立的哈希函数。

Standard Bloom Filter

在传统的Bloom Filter中,我们有:

  • 集合S:其大小为m。也就是说,集合中有m个不同元素。
  • 可用内存B:B被当成位数组bitmap来使用,大小为n。(有n个bit)。
  • 哈希函数:有k个独立的、均匀分布的哈希函数。

Bloom Filter的做法是:初始时,所有比特位都初始化为0。对于集合中的每个元素,利用k个哈希函数,对它哈希得到k个位置,将bitmap中的对应的k个位置置为1。

给定一个元素e,为了判断它是否是集合中的元素,也利用该k个函数得到k个位置,检查该k个位置是否都为1,如果是,认为 eS ,否则认为 eS

不难看出,如果 eS ,那么Bloom Filter肯定会正确判断出 eS ,但是它还是可能产生false positive。那么,如何分析false positive的概率呢?

false positive发生时,表示哈希该元素后得到的k个位置都为1。这个概率大概为:

Ppk1

其中 p1 代表某位为1的概率,它等于:
p1=1p0

对于 p0 ,表示某个特定的比特位为0。什么时候该位才为0呢?也就是说m个元素各自经过k次哈希得到km个对象,没有一个对象定位到了该位置。某个对象定位到该位置的概率为 1n ,因此我们可以得到:
p0=(11n)mk

分析 p0 。在实际应用中,n一般很大,根据重要极限公式,我们有:

p0=(11n)mk=(11n)nmknemkn

代入到最上面的那个式子,我们不难得到:
P(1emkn)k

当k为何值时,P取得最小,false positive possibility最低呢?

f(k)=(1emkn)k ,则:
ln(f(k)=kln(1emkn)
f(k)f(k)=ln(1emkn)+k11emkn(emkn)(mn)
f(k)=f(k)ln(1emkn)+f(k)k1f(k)(emkn)(mn)
f(k)=f(k)ln(1emkn)+k(emkn)(mn)

看起来够复杂了,然而别怕!!!

f(k)=0 , 我们有(注意到 f(k)>0 恒成立):

ln(1emkn)+k(emkn)(mn)=0

作代换,令 λ=emkn ,则 k=nlnλm ,代入上式,得到
(1λ)ln(1λ)=λlnλ

因此
λ=12 k=nmln2

也就是说,当n和m固定时,选择 k=nmln2 附近的一个整数,将使false positive possibility最小。

工程实现时,我们需要k个哈希函数或者哈希函数值。如何构造和获得k个独立的哈希函数呢?这篇论文 提出,只需要两个独立的哈希函数hf1和hf2即可,也就是通过如下方式获得k个哈希函数值:

hash   value=hf1(key)+ihf2(key) ,其中 i=012k1

Counting Bloom Filter

除了存在false positive这个问题之外,传统的Bloom Filter还有一个不足:无法支持删除操作(想想看,是不是这样的)。而Counting Bloom Filter(CBF)就是用来解决这个问题的。

在CBF中,维护的不是单纯的标示0或者1的比特位,而是计数器counter。对于集合中的每个元素,利用k个哈希函数,对它哈希得到k个位置,将对应的k个位置上的k个counter都加1。删除时,只需要把k个counter都减1即可。

那么,这个counter应该占用几位呢?分配太多,浪费空间;分配太少,容易溢出。通过下面的分析,我们可以知道,实际使用时,4位足矣。

考察(是考察,不是考查。这两个词有什么区别?)某个位置,该位置的计数器counter的值 ξ

P(ξ=c)(mkc)(1n)c(11n)mkc=B(km,1n)

这个式子有点点复杂,然而回忆下概率论里的知识:若二项分布B(n,p)里n很大,p很小时,二项分布的极限近似分布是泊松分布 P(λ=k)=λkk!eλ ,其中 λ=np ,因此:
P(ξ=c)(mkc)(1n)c(11n)mkc(kmn)cc!ekmn

k=nmln2 ,代入,我们得到

P(ξ>16)(ln2)1616!12<1116!=1120922789888000

也就是说,选择4位来存counter在实际情况中已经足矣,发生溢出的概率极小。

在此,我们介绍了Standard Bloom Filter(SBF)和Counting Bloom Filter(CBF)。简单回顾下,我们大概思路和历程是:为了解决允许false positive下的membership问题,我们设计了哈希表算法,由于它所需空间巨大,我们引入bitmap方法;因为它false positive possibility太大,我们引入了SBF,它使用多个独立的、均匀分布的哈希函数。而SBF的一个缺点是不支持删除操作,为了能够删除,我们引入了CBF,然而,CBF存在一个问题。

什么问题呢?那就是空间利用率不高。这可以通过简单的数学计算知道大概:

考察某个位置,该位置的计数器counter的值 ξ

P(ξ=c)(mkc)(1n)c(11n)mkc=B(km,1n)

若二项分布B(n,p)里n很大,p很小时,二项分布的极限近似分布是泊松分布 P(λ=k)=λkk!eλ ,其中 λ=np=kmn ,并结合 k=nmln2 ,可以得到,counter的期望值为:

E(ξ)=λ=np=ln20.7

即大量的counter的值都是0,空间效率不高。
在介绍新的Bloom Filter之前,我们一起先看看什么是所谓的d-left hashing。


D-left Hashing

在d-left hashing中,我们有d张哈希子表(序号分别为1,2,…d,并且假设是从左到右),每张子表都包含B个bucket,每个bucket都包含w个cell,每个cell可以存放一个元素。

为了插入一个元素x,我们:

1,首先通过一个哈希函数hf,得到x的哈希函数值 value=hf(x)

2,通过value,得到d个位置(每个位置对应每张子表),表示为:

(1,B1),(2,B2),(d,Bd)

其中 (i,Bi) 表示第i张子表,Bucket的index为 Bi

那么,到底插入哪张表呢?d-left hashing选择 Bi 中负载最小(已经存放的元素最少)的位置。注意,这里说的是bucket的负载,不是子表的负载。如果有多个子表对应的位置负载都是最小,那么选择最左边(序号最小)的子表插入。

为了查找该元素,我们需要检查d个位置,wd个cell(元素)。


D-left Counting Bloom Filter

在基于d-left hashing的CBF中,我们有d张哈希子表(序号分别为1,2,…d,并且假设是从左到右),每张子表都包含B个bucket,每个bucket都包含w个cell,每个cell可以存放一个counter和一个fingerprint。

为了插入一个元素x,我们:

1,首先通过一个哈希函数hf,得到x的哈希函数值 value=hf(x)
2,通过value,得到d个位置(每个位置对应每张子表)和对应的fingerprint,表示为d个位置向量:

(1,B1,fp1),(2,B2,fp2),(d,Bd,fpd)

其中位置向量 (i,Bi,fpi) 表示第i张子表,Bucket的index为 Bi ,fingerprint为 fpi

3,通过d-left hashing将x插入,如果插入的位置 (i,Bi,fpi) 上已经有cell的fingerprint等于 fpi ,那么只需要将它的counter++即可。

举个栗子,假设某时刻,我们有 d1 中,如表下所示:

bucket counter fp counter fp counter fp
0
1 2 001100
2
3
4 3 110011 2 111100
5

d2 中,如表下所示

bucket counter fp counter fp counter fp
0
1
2 5 000001 2 111100
3
4
5 7 111010

2张子表( d=2 ),每张子表有6个bucket( B=6 ),每个bucket包括3个cell( w=3 ),每个cell可以存放一个counter(4位表示,最大值为16)和fingerprint(6位表示)。

假如我们要插入元素x,我们对它进行hash,得到两个位置向量:

(1, 1, 010100)和(2, 2, 010101)

d1 子表中index为1的bucket的负载,要小于 d2 子表中index为2的bucket的负载,因此,我们选择插入 d1 中,如表下所示:

bucket counter fp counter fp counter fp
0
1 1 010100 2 001100
2
3
4 3 110011 2 111100
5

d2 中,如表下所示

bucket counter fp counter fp counter fp
0
1
2 5 000001 2 111100
3
4
5 7 111010

如果得到的位置向量分别是(1, 1, 001100)和(2, 2, 010101)呢?那么,还是插入到 d1 中index为1的bucket中,但是只需要将第二个cell里的counter++即可, d1 中,如表下所示:

bucket counter fp counter fp counter fp
0
1 3 001100
2
3
4 3 110011 2 111100
5

d2 中,如表下所示

bucket counter fp counter fp counter fp
0
1
2 5 000001 2 111100
3
4
5 7 111010

看起来很完美,但是这种方案在删除时会有问题。比如说,还是刚才的例子,我们欲插入x和y。分别得到x和y的位置向量们:
x: (1, 1, 010100)和(2, 2, 010100)
y: (1, 1, 010100)和(2, 4, 010100)
根据d-left hashing,x被插入到 d1 中index为1的bucket中;y被插入到 d2 中index为4的bucket中。好,这没问题。如果现在要删除y呢?我们需要检查两个位置, d1 中的index为1的bucket,以及 d2 中index为4的bucket,要删哪个? fp 都是010100啊。这就出现问题。

什么时候会出现这种问题?当以下条件满足时:

  1. 两个元素的有公共【重合】的位置向量。位置向量相同,表示同一个子表,同一个bucket,以及相同的fingerprint。
  2. 其中一个元素被插入了公共位置向量对应的位置,另外一个元素没有。
  3. 欲删除未使用公共位置向量的元素(比如说上例中的y)

为了解决这个问题,作者做了两点改进:

I. 引入d轮随机置换。

置换,即一一映射。假设置换为 P ,输入为 a b ,那么将满足:

如果 a=b ,那么 P(a)=P(b)
如果 ab ,那么 P(a)P(b)

为了插入x,我们首先通过一个哈希函数hf,计算它的哈希函数值 value=hf(x) 。然后,我们对value进行d轮随机置换,得到d个位置向量:

P1(value)=(1,B1,fp1)
P2(value)=(2,B2,fp2)

Pd(value)=(d,Bd,fpd)

这里面有一个小问题:如果要插入的元素x和y不等,那么它们的置换可能相等吗?当然可能。因为x和y虽然不等,但是它们的hash value可能相等,这样它们的置换结果即位置向量可能相同。

别忘了,我们是对x和y的hash value进行置换,不是对x和y进行置换。

网上流传很广的这篇文章的解释是错误的,小心!!!

II. 插入修正

当得到d个位置向量后,和上面介绍的过程稍微不同:我们首先需要根据d个位置向量,从第1个子表开始,对每个子表 di ,去对应的位置 Bi 处查找是否有cell中的 fp 等于 fpi ,如果有,我们把相应的counter++,插入动作完成,不用再继续检查后续子表了。

否则,当所有d个子表都没有对应的 fpi 时,我们运用d-left hashing,插入x。

综合运用这两点,我们知道上面所说的删除时的问题已经不存在了。

假如欲插入x和y,分别对它们的hash value进行d轮(这里d=2)随机置换后,有没有可能得到如下的位置向量?

x: (1, 1, 010100)和(2, 2, 010101)

y: (1, 1, 010100)和(2, 4, 010111)

不可能。

注意到,x和y的第一个位置向量(对应第一张子表)完全相同(bucket index相同,fp也相同),也就是

P1(hf(x))=P1(hf(y))

那么可以推出 hf(x)=hf(y) ,也就是x和y的哈希函数值相等,那么对于任何的i都有 Pi(hf(x))=Pi(hf(y))

因此:

  • 如果 hf(x)=hf(y) ,那么 Pi(hf(x))=Pi(hf(y)) , (i=1,2,3d) ,假如先插入x后插入y,插入y的时候,会更新counter。后续删除x或者y都不存在上述问题。
  • 如果 hf(x)hf(y) ,那么 Pi(hf(x))Pi(hf(y)) , (i=1,2,3d) ,那么x和y没有公共的位置向量。后续删除x或者y都不存在上述问题。

附录

如何随机置换

作者提供了一个简单的函数:

Pi(value)=(avalue)mod2B

其中value的范围是 [0,2B] ,a是随机的一个奇数。

数学分析

首先,如果z是false positive,那么它的哈希函数值会和集合S中的某个元素的哈希函数值相等。也就是

hf(z)=hf(e) 其中 eS

这是因为,如果z是false positive,那么
pi(hf(z))=pi(hf(e))

根据置换的性质,可得 hf(z)=hf(e)
因此,false positive possibility为
P=1(11B12r)m

根据伯努利公式,当x很小时, (1+x)ax1+ax ,所以
P1(1m1B12r)mB2r

那么d-left CBF的false positive和空间利用情况如何?我们下面简单分析一下:

比较嘛,肯定是相同的空间利用,比较谁的false positive possibility小;相同的false positive possibility下,谁所需空间少。

在CBF中,假设counter需要c位(我们已经分析过,c取4足矣),那么所需的空间是cn。结合 k=nmln2 ,false positive possibility大约为 (0.6185)nm

在d-left CBF中,我们选择 d=4 B=m24 w=8 (平均负载为6,这样 4m246=m ),counter占用2位,fingerprint占用r位。那么它的空间占用为

4m248(2+r)=4m(r+2)3

而false positive possibility的大概为 mB2r=2412r (别忘了, B=m24 )

通过计算不难发现,当空间占用相等时,d-left CBF的false positive possibility是CBF的百分之一;当false positive possibility相等时,d-left CBF所需空间是CBF的一半。


小结

总结下,Bloom Filter可以用在什么地方,或者说,在什么场景下,你应该想到这种技术:

  1. 回答是或者不是的问题。你需要判断一个元素是否属于某个集合,仅仅这样。你不应该要求更多。如果你想获得该元素对应的value或者还有其他payload,那么bloom filter不适合你,你需要哈希表。
  2. 允许false positive。也就是说,发生false positive不应该是致命的。比如说,搜索引擎的爬虫里,如果url不是set的元素,却被bloom filter过滤了,那么顶多就是不抓它而已,没啥特别大的损失。
  3. 空间敏感。作为一种概率数据结构,Bloom Filter不存储原始数据(比如说url),这也是它为什么space efficient的本质原因。

参考资料

  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. B. Bloom. Space/Time Tradeoffs in Hash Coding with Allowable Errors. Communications of the ACM 13:7 (1970), 422—426.
  4. http://www.cs.berkeley.edu/~pbg/cs270/notes/lec27.pdf
  5. www.cs.jhu.edu/~fabian/courses/CS600.624/slides/bloomslides.pdf
  6. http://166.111.248.20/seminar/200611_23/hash_2_yaxuan.ppt

关于程序算法艺术与实践更多讨论与交流,敬请关注本博客和新浪微博songzi_tea.

你可能感兴趣的:(【Algorithms】,程序算法艺术与实践)