数学之美:布隆过滤器

算法题目

如果一个黑名单网站包含100亿个黑名单网页,每个网页最多占64B,设计一个系统,判断当前的URL是否在这个黑名单当中,要求额外空间不超过30GB,允许误差率为万分之一。

解题思路

  • 布隆过滤器
基础介绍

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

实际工程的应用

实际上,布隆过滤器广泛应用于网页黑名单系统、垃圾邮件过滤系统、爬虫网址判重系统等,有人会想,我直接将网页URL存入数据库进行查找不就好了,或者建立一个哈希表进行查找不就OK了。

当数据量小的时候,这么思考是对的,但如果整个网页黑名单系统包含100亿个网页URL,在数据库查找是很费时的,并且如果每个URL空间为64B,那么需要内存为640GB,一般的服务器很难达到这个需求。

那么,在这种内存不够且检索速度慢的情况下,不妨考虑下布隆过滤器,但业务上要可以忍受判断失误率。
在这里插入图片描述

位图(bitmap)

布隆过滤器其中重要的实现就是位图的实现,也就是位数组,并且在这个数组中每一个位置只占有1个bit,而每个bit只有0和1两种状态。如上图bitarray所示!bitarray也叫bitmap,大小也就是布隆过滤器的大小。

假设一种有k个哈希函数,且每个哈希函数的输出范围都大于m,接着将输出值对k取余(%m),就会得到k个[0, m-1]的值,由于每个哈希函数之间相互独立,因此这k个数也相互独立,最后将这k个数对应到bitarray上并标记为1(涂黑)。

等判断时,将输入对象经过这k个哈希函数计算得到k个值,然后判断对应bitarray的k个位置是否都为1(是否标黑),如果有一个不为黑,那么这个输入对象则不在这个集合中,也就不是黑名单了!如果都是黑,那说明在集合中,但有可能会误,由于当输入对象过多,而集合也就是bitarray过小,则会出现大部分为黑的情况,那样就容易发生误判!因此使用布隆过滤器是需要容忍错误率的,即使很低很低!

布隆过滤器重要参数计算

通过上面的描述,我们可以知道,如果输入量过大,而bitarray空间的大小又很小,那么误判率就会上升。那么bitarray空间大小怎么确定呢?不要慌,已经有人通过数据推倒出公式了!!!哈哈,直接用~

假设输入对象个数为n,bitarray大小(也就是布隆过滤器大小)为m,所容忍的误判率p和哈希函数的个数k。计算公式如下:(小数向上取整)
m=−n×ln⁡p(ln⁡2)2k=ln⁡2×mn=0.7×mnp=(1−e−nkm)k\begin{array}{c}{\mathrm{m}=-\frac{n \times \ln p}{(\ln 2)^{2}}} \\ {\mathrm{k}=\ln 2 \times \frac{m}{n}=0.7 \times \frac{m}{n}} \\ {p=\left(1-e^{-\frac{n k}{m}}\right)^{k}}\end{array}m=(ln2)2n×lnpk=ln2×nm=0.7×nmp=(1emnk)k

推导过程如下
假设布隆过滤器中的hash function满足simple uniform hashing假设:每个元素都等概率地hash到m个slot中的任何一个,与其它元素被hash到哪个slot无关。若m为bit数,则对某一特定bit位在一个元素由某特定hash function插入时没有被置位为1的概率为:
1−1m1-\frac{1}{m}1m1
则k个hash function中没有一个对其置位为1的概率为:
(1−1m)k{(1-\frac{1}{m})}^k(1m1)k
如果插入了n个元素,但都未将其置位为1的概率为:
(1−1m)kn{(1-\frac{1}{m})}^{kn}(1m1)kn
则此位被置位为1的概率为:
1−(1−1m)kn1-{(1-\frac{1}{m})}^{kn}1(1m1)kn
现在考虑query阶段产生误判的概率,如果对于一个query,经过k个hash function之后,产生的k个比特位已被之前的n个元素置为了1,便产生了误判。之前已经计算了n个元素经过k个Hash函数后,某一位被置为1的概率是1−(1−1m)kn1-{(1-\frac{1}{m})}^{kn}1(1m1)kn,那么产生的k个比特位全部被置为1则产生误判,概率为
(1−(1−1m)kn)k{(1-{(1-\frac{1}{m})}^{kn})}^k(1(1m1)kn)k
由于(1+x)1x∼e(1+x)^{\frac{1}{x}} \sim e(1+x)x1e,当x→0x→0x0时,并且−1m-\frac{1}{m}m1,当m很大时趋近于0,所以
(1−(1−1m)kn)k=(1−(1−1m)−m⋅knm)k∼(1−e−nkm)k\left(1-\left(1-\frac{1}{m}\right)^{k n}\right)^{k}=\left(1-\left(1-\frac{1}{m}\right)^{-m \cdot \frac{k n}{m}}\right)^{k} \sim\left(1-e^{-\frac{n k}{m}}\right)^{k}(1(1m1)kn)k=(1(1m1)mmkn)k(1emnk)k
从上式中可以看出,当m增大或n减小时,都会使得误判率减小,这也符合直觉。

现在计算对于给定的m和n,k为何值时可以使得误判率最低。设误判率为k的函数为:

f(k)=(1−e−nkm)kf(k)=\left(1-e^{-\frac{n k}{m}}\right)^{k}f(k)=(1emnk)k
b=enmb=e^{\frac{n}{m}}b=emn,则简化为
f(k)=(1−b−k)kf(k)=\left(1-b^{-k}\right)^{k}f(k)=(1bk)k,两边取对数
ln⁡f(k)=k⋅ln⁡(1−b−k)\ln f(k)=k \cdot \ln \left(1-b^{-k}\right)lnf(k)=kln(1bk),两边对k求导
1f(k)⋅f′(k)=ln⁡(1−b−k)+k⋅11−b−k⋅(−b−k)⋅ln⁡b⋅(−1)=ln⁡(1−b−k)+k⋅b−k⋅ln⁡b1−b−k\begin{aligned} \frac{1}{f(k)} \cdot f^{\prime}(k) &=\ln \left(1-b^{-k}\right)+k \cdot \frac{1}{1-b^{-k}} \cdot\left(-b^{-k}\right) \cdot \ln b \cdot(-1) \\ &=\ln \left(1-b^{-k}\right)+k \cdot \frac{b^{-k} \cdot \ln b}{1-b^{-k}} \end{aligned}f(k)1f(k)=ln(1bk)+k1bk1(bk)lnb(1)=ln(1bk)+k1bkbklnb
下面求最值
ln⁡(1−b−k)+k⋅b−k⋅ln⁡b1−b−k=0\ln \left(1-b^{-k}\right)+k \cdot \frac{b^{-k} \cdot \ln b}{1-b^{-k}}=0ln(1bk)+k1bkbklnb=0
⇒(1−b−k)⋅ln⁡(1−b−k)=−k⋅b−k⋅ln⁡b\Rightarrow \left(1-b^{-k}\right) \cdot \ln \left(1-b^{-k}\right)=-k \cdot b^{-k} \cdot \ln b(1bk)ln(1bk)=kbklnb
⇒(1−b−k)⋅ln⁡(1−b−k)=b−k⋅ln⁡b−k\Rightarrow \left(1-b^{-k}\right) \cdot \ln \left(1-b^{-k}\right)=b^{-k} \cdot \ln b^{-k}(1bk)ln(1bk)=bklnbk
⇒1−b−k=b−k\Rightarrow 1-b^{-k}=b^{-k}1bk=bk
⇒b−k=12\Rightarrow b^{-k}=\frac{1}{2}bk=21
⇒e−knm=12\Rightarrow e^{-\frac{k n}{m}}=\frac{1}{2}emkn=21
⇒knm=ln⁡2\Rightarrow \frac{k n}{m}=\ln 2mkn=ln2
⇒k=ln⁡2⋅mn=0.7⋅mn\Rightarrow k=\ln 2 \cdot \frac{m}{n}=0.7 \cdot \frac{m}{n}k=ln2nm=0.7nm
因此,即当k=0.7⋅mnk=0.7 \cdot \frac{m}{n}k=0.7nm时误判率最低,此时误判率为:
P(error)=(1−12)k=2−k=2−ln⁡2⋅mn≈0.6185mnP(\text {error})=\left(1-\frac{1}{2}\right)^{k}=2^{-k}=2^{-\ln 2 \cdot \frac{m}{n}} \approx 0.6185^{\frac{m}{n}}P(error)=(121)k=2k=2ln2nm0.6185nm
可以看出若要使得误判率≤1/2≤1/21/2,则:
k≥1⇒mn≥1ln⁡2k \geq 1 \Rightarrow \frac{m}{n} \geq \frac{1}{\ln 2}k1nmln21
这说明了若想保持某固定误判率不变,布隆过滤器的bit数m与被add的元素数n应该是线性同步增加的。

设计和应用布隆过滤器的方法

应用时首先要先由用户决定要add的元素数n和希望的误差率P。这也是一个设计完整的布隆过滤器需要用户输入的仅有的两个参数,之后的所有参数将由系统计算,并由此建立布隆过滤器。
系统首先要计算需要的内存大小m bits:
P=2−ln2⋅mn⇒ln⁡p=ln⁡2⋅(−ln⁡2)⋅mn⇒m=−n⋅ln⁡p(ln⁡2)2P=2^{-l n 2 \cdot \frac{m}{n}} \Rightarrow \ln p=\ln 2 \cdot(-\ln 2) \cdot \frac{\mathrm{m}}{\mathrm{n}} \Rightarrow \mathrm{m}=-\frac{n \cdot \ln p}{(\ln 2)^{2}}P=2ln2nmlnp=ln2(ln2)nmm=(ln2)2nlnp
再由m,n得到hash function的个数:
k=ln⁡2⋅mn=0.7⋅mnk=\ln 2 \cdot \frac{m}{n}=0.7 \cdot \frac{m}{n}k=ln2nm=0.7nm
至此系统所需的参数已经备齐,接下来add n个元素至布隆过滤器中,再进行query。

根据公式,当k最优时:
P(error)=2−k⇒log⁡2P=−k⇒k=log⁡21P⇒ln⁡2mn=log⁡21PP(\text {error})=2^{-k} \Rightarrow \log _{2} P=-k \Rightarrow \mathrm{k}=\log _{2} \frac{1}{P} \Rightarrow \ln 2 \frac{\mathrm{m}}{\mathrm{n}}=\log _{2} \frac{1}{P}P(error)=2klog2P=kk=log2P1ln2nm=log2P1
⟹mn=1ln2⋅log⁡21P=1.44⋅log⁡21P\Longrightarrow \frac{\mathrm{m}}{\mathrm{n}}=\frac{1}{ln 2} \cdot \log _{2} \frac{1}{P}=1.44 \cdot \log _{2} \frac{1}{P}nm=ln21log2P1=1.44log2P1
因此可验证当P=1%时,存储每个元素需要9.6 bits:
mn=1.44⋅log⁡210.01=9.6bits\frac{\mathrm{m}}{n}=1.44 \cdot \log _{2} \frac{1}{0.01}=9.6 \mathrm{bits}nm=1.44log20.011=9.6bits
而每当想将误判率降低为原来的1/10,则存储每个元素需要增加4.8 bits:
mn=1.44⋅(log⁡210a−log⁡2a)=1.44⋅log⁡210=4.8bits\frac{\mathrm{m}}{\mathrm{n}}=1.44 \cdot\left(\log _{2} 10 a-\log _{2} a\right)=1.44 \cdot \log _{2} 10=4.8 \mathrm{bits}nm=1.44(log210alog2a)=1.44log210=4.8bits
这里需要特别注意的是,9.6 bits/element不仅包含了被置为1的k位,还把包含了没有被置为1的一些位数。此时的
k=0.7⋅mn=0.7∗9.6=6.72bitsk=0.7 \cdot \frac{m}{n}=0.7 * 9.6=6.72 \mathrm{bits}k=0.7nm=0.79.6=6.72bits
才是每个元素对应的为1的bit位数。
k=0.7⋅mnk=0.7 \cdot \frac{m}{n}k=0.7nm从而使得P(error)最小时,我们注意到:
P(error)=(1−e−nkm)kP(\text {error})=\left(1-e^{-\frac{n k}{m}}\right)^{k}P(error)=(1emnk)k中的e−nkm=12e^{-\frac{n k}{m}}=\frac{1}{2}emnk=21,即
(1−1m)kn=12\left(1-\frac{1}{m}\right)^{k n}=\frac{1}{2}(1m1)kn=21
此概率为某bit位在插入n个元素后未被置位的概率。因此,想保持错误率低,布隆过滤器的空间使用率需为50%。

关于布隆过滤器使用的哈希函数

MurmurHash算法:高运算性能,低碰撞率的hash算法

  • To be continued
关于布隆过滤器的代码实现
  • To be continued
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值