布隆过滤器的原理、实现和探究

本文深入探讨布隆过滤器的原理与实现,通过实例解析其在存储大量数据时的空间节省优势,同时分析误识别率的影响因素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

布隆过滤器的原理、实现和探究

2014年06月22日 08:30:20 打工是不可能打工滴 阅读数:5766更多

所属专栏: 机器学习理论与实践

 

1.布隆过滤器的使用价值

有时候我们需要判断一个元素是否在一个集合中。比如,在字处理软件中,需要检查一个单词是否拼写正确(也就是要判断它是否在已知的字典里);在警察系统中,一个嫌疑人的名字是否出现在嫌疑名单上;在网络爬虫里,一个网址是否已经被访问过,等等。

最直接的方法就是讲集合中的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(Hash Table)来存储的。它的好处是快速准确,缺点是耗费存储空间。

为什么说耗费存储空间呢?其根本原因是哈希表方法需要把实实在在的具有特定长度(每个Email地址对应成一个8字节的信息指纹)的元素的信息指纹存储在内存或硬盘中的哈希表中,这个存储量在实际应用中一般是相当大的。比如每存储一亿个Email地址,需要0.8G大小的数字指纹存储空间,考虑到哈希表的存储空间利用率一般只有一半,所以需要1.6G的存储空间。如果存储几十亿上百亿的Email地址,那就需要百亿字节的内存存储空间。

而布隆过滤器只需要哈希表1/8到1/4的大小就能解决同样的问题,它实际上是一个很长的二进制向量和一系列的随机映射函数。

下面以WEB页面地址的存储为例来说明布隆过滤器的工作原理。

假定存储一亿个WEB页面地址,先建立一个16亿二进制(比特),即2亿字节的向量,然后将这16亿个二进制位清零。对于每一个WEB页面地址X,用8个随机数产生器(f1,f2,...,f8)。再用一个随机数产生器G把这8个信息指纹映射到1-16亿中的8个自然数g1,g2,...g8。现在把这8个位置的二进制位都置为1。对着一亿个WEB页面地址都进行这样的处理后,一个针对WEB页面的布隆过滤器就建成了,见下图。

                                图1 布隆迪过滤器的映射方法

现在,让我们看看如何用布隆过滤器来检测一个WEB网页地址Y是否已经被我们收录。用相同的8个随机数生成器(f1,f2,...,f8)对这个WEB网页地址产生8个信息指纹s1,s2,...s8,然后将这8个指纹对应到布隆过滤器的8个二进制位,分别是t1,t2,...,t8。如果Y已被收录,显然t1,t2,...,t8对应的8个二进制位一定是1。通过这样的方式我们能够很快地确定一个WEB页面是否已被我们收录。

2.布隆过滤器的实现

布隆过滤器实现代码:

 

 
  1. #encoding=UTF-8

  2. '''

  3. Created on 2014年6月21日

  4.  
  5. @author: jin

  6. '''

  7. import BitVector

  8. class MyHash():#哈希类,根据不同参数初始化后作为不同的哈希函数

  9.  
  10. def __init__(self, cap, seed):

  11. self.cap = cap

  12. self.seed = seed

  13.  
  14. def hash(self, value): #计算哈希值得过程

  15. ret = 0

  16. for i in range(len(value)):

  17. ret += self.seed*ret + ord(value[i]) #ord()函数计算传入的url字符串中每一个字符在ASCII码表中对应的顺序值

  18. return (self.cap-1) & ret #返回哈希值,即在比特序列中的位置

  19.  
  20. class BloomFilter():

  21.  
  22. def __init__(self, BIT_SIZE=1<<31):

  23. self.BIT_SIZE = 1 << 31 #不拢过滤器的比特数,

  24. self.seeds = [5, 7, 11, 13,19, 31, 37, 61] #8个种子,用于产生hash函数

  25. self.bitset = BitVector.BitVector(size=self.BIT_SIZE)

  26. self.hashFuncList = []

  27. for i in range(len(self.seeds)):

  28. self.hashFuncList.append(MyHash(self.BIT_SIZE, self.seeds[i])) #对每个种子,创建一个MyHash对象,一共8个

  29. def insert(self, value): #插入值,这里并非真正地插入并存储,而是把该值对应的8个位置的比特位置为1

  30. for function in self.hashFuncList:

  31. locationBit = function.hash(value) #计算应该置为1的比特位

  32. self.bitset[locationBit] = 1

  33. def isContaions(self, value):

  34. if value == None:

  35. return False

  36. ret = True

  37. for f in self.hashFuncList:

  38. locationBit = f.hash(value)

  39. ret = ret & self.bitset[locationBit] #可以看出,对8个哈希函数,只要有一个为0,那么将返回0,即该值尚未存在

  40. return ret

  41.  
  42. def Main(): #主函数

  43.  
  44. fd = open("urls.txt") #有重复的网址 http://www.kalsey.com/tools/buttonmaker/

  45. bloomfilter = BloomFilter()

  46. while True:

  47. url = fd.readline()

  48. if cmp(url, 'exit') == 0:

  49. print 'complete and exit now'

  50. break

  51. elif bloomfilter.isContaions(url) == False:

  52. bloomfilter.insert(url)

  53. else:

  54. print 'url :%s has exist' % url

  55. Main()

url.txt内部存储有一系列网址,最后一行是‘exit’,内容如下:

 

 

http://sourceforge.net/robots.txt
http://sourceforge.net/
http://sourceforge.net
http://sourceforge.net and https://sourceforge.net
http://sourceforge.net/sitemap.xml
http://sourceforge.net/allura_sitemap/sitemap.xml
http://sourceforge.net/directory_sitemap.xml
http://a.fsdn.com
http://a.fsdn.com/con/img/sftheme/favicon.ico
http://a.fsdn.com/con/js/min/sf.head.js
http://a.fsdn.com/con/js/sftheme/dd_belatedpng.js
http://fonts.googleapis.com
http://fonts.googleapis.com/css
http://a.fsdn.com/con/css/sf.css
http://sourceforge.net/blog/feed/
http://email.playtime.uni.cc/ 
http://services.nexodyne.com/email/ 
http://gizmo967.mgs3.org/Gmail/ 
http://www.hkwebs.net/catalog/tools/gmail/ 
http://sagittarius.dip.jp/~toshi/cgi-bin/designmail/designmail.html 
http://www.eoool.com/
http://sourceforge.netand
https://sourceforge.net
http://a.fsdn.com/con/js/adframe.js
http://sourceforge.net/directory/
http://kalsey.com/tools/buttonmaker/ 
http://www.lucazappa.com/brilliantMaker/buttonImage.php 
http://www.feedforall.com/public/rss-graphic-tool.htm  
http://www.yugatech.com/make.php 
http://www.hkwebs.net/catalog/tools/buttonmaker/index.php
http://phorum.com.tw/Generator.aspx 
http://www.logoyes.com/lc_leftframe.htm 
http://cooltext.com/Default.aspx
exit

运行效果如下,可以看到未发生存储地址冲突:

 

 

complete and exit now

往url.txt里面再增加一个原来未有的网址

http://www.kalsey.com/tools/buttonmaker/

,再次运行,竟然发生了冲突,如下:

 

 

url :http://www.kalsey.com/tools/buttonmaker/
 has exist
complete and exit now

这说明此网址和另外一个网址对应的8个信息指纹相同,虽然它们本身的值是不同的,这就产生了冲突。

 

可以看到布隆过滤器有一定的误识别率。下面我们对其进行分析。

 

3.误识别率的问题

假定布隆过滤器有m比特,里面有n个元素,每个元素对应k个信息指纹的哈希函数,当然m个比特里有0也有1。我们假定某个比特为0,在这个布隆过滤器里插入一个元素,他的第一个哈希函数会把过滤器中的某个比特置为1,理想情况下,任一比特位被置1的概率是1/m,它依然为0的概率则是1-1/m。

 

对于过滤器中的一个特定位置,如果这个元素的k个哈希函数都没有把它设置成1,其概率是                                                                                                            。如果过滤器插入第二个元素,这个特定位置仍不被置1的概率是,类似的,插入n个元素其仍为0的概率是。反过来,一个比特在插入n个元素后,被置1的概率则是

现在假定这n个元素都放到布隆过滤器中了,新来的一个不在集合中的元素,由于它的信息指纹的哈希函数都是随机的,因此,它的第一个哈希函数正好命中某个值为1的比特的概率就是上述概率。一个不再集合中的元素被误识别为已经在集合中,需要所有的哈希函数对应的比特值均为1,其概率为

我们下面对简化的误识别率的公式进行研究。


                                                                        图2 误识别率与m/n的关系

图2的代码:

 

 
  1. k=8

  2. r = np.linspace(1,50,1000)

  3. p = np.power(1-np.exp(-k/r),k)

  4. plt.title('misjudgment & m/n')

  5. plt.plot(r,p)

  6. plt.xlabel('m/n')

  7. plt.ylabel('misjudgment')

  8. plt.show()

 

                                                               图3 误识别率与k的关系

图3的代码:

 

 
  1. r = 30;

  2. k = np.linspace(1,10,100)

  3. p = np.power(1-np.exp(-k/r),k)

  4. plt.title('misjudgment & k')

  5. plt.xlabel('k')

  6. plt.ylabel('misjudgment')

  7. plt.plot(k,p)

  8. plt.show()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值