本文将介绍:
Google的爬虫每天需要抓取大量的网页。于是就有一个问题:每当爬虫分析出一个url的时候,是抓呢,还是不抓呢?如何知道这个url已经爬过了?这个问题,归纳抽象后可以定义为:给定一个集合S(注意,这里的集合是传统意义上的集合:元素彼此不同。本文不考虑multiset),给定一个元素e,需要判断 e∈S 是否成立。(学术界一般称为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,如果实际上 e∉S ,而被判为 e∈S ,那么我们称e是false positive(伪正例。顺便说一句,false positive等的分析在machine learning的classification任务里评价model时非常重要)。
如何降低false positive的概率呢?Bloom Filter的想法是使用多个独立的哈希函数。
Standard Bloom Filter
在传统的Bloom Filter中,我们有:
Bloom Filter的做法是:初始时,所有比特位都初始化为0。对于集合中的每个元素,利用k个哈希函数,对它哈希得到k个位置,将bitmap中的对应的k个位置置为1。
给定一个元素e,为了判断它是否是集合中的元素,也利用该k个函数得到k个位置,检查该k个位置是否都为1,如果是,认为 e∈S ,否则认为 e∉S 。
不难看出,如果 e∈S ,那么Bloom Filter肯定会正确判断出 e∈S ,但是它还是可能产生false positive。那么,如何分析false positive的概率呢?
false positive发生时,表示哈希该元素后得到的k个位置都为1。这个概率大概为:
分析 p0 。在实际应用中,n一般很大,根据重要极限公式,我们有:
当k为何值时,P取得最小,false positive possibility最低呢?
令 f(k)=(1−e−mkn)k ,则:
⇒ln(f(k)=kln(1−e−mkn)
⇒f′(k)f(k)=ln(1−e−mkn)+k∗11−e−mkn∗(−e−mkn)∗(−mn)
⇒f′(k)=f(k)ln(1−e−mkn)+f(k)∗k∗1f(k)∗(−e−mkn)∗(−mn)
⇒f′(k)=f(k)ln(1−e−mkn)+k∗(−e−mkn)∗(−mn)
看起来够复杂了,然而别怕!!!
令 f′(k)=0 , 我们有(注意到 f(k)>0 恒成立):
工程实现时,我们需要k个哈希函数或者哈希函数值。如何构造和获得k个独立的哈希函数呢?这篇论文 提出,只需要两个独立的哈希函数hf1和hf2即可,也就是通过如下方式获得k个哈希函数值:
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的值 ξ
令 k=nmln2 ,代入,我们得到
在此,我们介绍了Standard Bloom Filter(SBF)和Counting Bloom Filter(CBF)。简单回顾下,我们大概思路和历程是:为了解决允许false positive下的membership问题,我们设计了哈希表算法,由于它所需空间巨大,我们引入bitmap方法;因为它false positive possibility太大,我们引入了SBF,它使用多个独立的、均匀分布的哈希函数。而SBF的一个缺点是不支持删除操作,为了能够删除,我们引入了CBF,然而,CBF存在一个问题。
什么问题呢?那就是空间利用率不高。这可以通过简单的数学计算知道大概:
考察某个位置,该位置的计数器counter的值 ξ
若二项分布B(n,p)里n很大,p很小时,二项分布的极限近似分布是泊松分布 P(λ=k)=λkk!e−λ ,其中 λ=np=kmn ,并结合 k=nmln2 ,可以得到,counter的期望值为:
在d-left hashing中,我们有d张哈希子表(序号分别为1,2,…d,并且假设是从左到右),每张子表都包含B个bucket,每个bucket都包含w个cell,每个cell可以存放一个元素。
为了插入一个元素x,我们:
1,首先通过一个哈希函数hf,得到x的哈希函数值 value=hf(x)
2,通过value,得到d个位置(每个位置对应每张子表),表示为:
那么,到底插入哪张表呢?d-left hashing选择 Bi 中负载最小(已经存放的元素最少)的位置。注意,这里说的是bucket的负载,不是子表的负载。如果有多个子表对应的位置负载都是最小,那么选择最左边(序号最小)的子表插入。
为了查找该元素,我们需要检查d个位置,wd个cell(元素)。
在基于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个位置向量:
其中位置向量 (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,得到两个位置向量:
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啊。这就出现问题。
什么时候会出现这种问题?当以下条件满足时:
为了解决这个问题,作者做了两点改进:
I. 引入d轮随机置换。
置换,即一一映射。假设置换为 P ,输入为 a 和 b ,那么将满足:
如果 a=b ,那么 P(a)=P(b)
如果 a≠b ,那么 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也相同),也就是
那么可以推出 hf(x)=hf(y) ,也就是x和y的哈希函数值相等,那么对于任何的i都有 Pi(hf(x))=Pi(hf(y)) 。
因此:
作者提供了一个简单的函数:
其中value的范围是 [0,2B] ,a是随机的一个奇数。
首先,如果z是false positive,那么它的哈希函数值会和集合S中的某个元素的哈希函数值相等。也就是
那么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,这样 4∗m24∗6=m ),counter占用2位,fingerprint占用r位。那么它的空间占用为
而false positive possibility的大概为 mB∗2r=24∗12r (别忘了, B=m24 )
通过计算不难发现,当空间占用相等时,d-left CBF的false positive possibility是CBF的百分之一;当false positive possibility相等时,d-left CBF所需空间是CBF的一半。
总结下,Bloom Filter可以用在什么地方,或者说,在什么场景下,你应该想到这种技术:
A. Broder and M. Mitzenmacher. Network applications of bloom filters: A survey.Internet Mathematics, 1(4):485–509, 2005.
M. Mitzenmacher.Compressed Bloom Filters.IEEE/ACM Transactions on Networking 10:5 (2002), 604—612.