您的位置:首页 > 其它

bloomfilter 原理及实现

2016-09-03 16:10 78 查看
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">参考文章:</span> http://blog.csdn.net/u013402746/article/details/28414901
原理:

使用一组hash函数将key映射为一组整数(如3、5、10),再将一个大的bit数组的相应位置置1

最终使用这个bit数组作为keys的集合

查询时:

对输入x进行同样的一组hash,假设结果为3、5、10,则查看bit数组的相应位置,若都为1,则说明x有很大概率在集合内

(注意,可能a->(1,3,7)与b->(2,5,10)的叠加导致3、5、10都为1,从而产生误判 False positive)

但若3个位置有1个不为1,则x肯定不在集合中

总结:

bloomfilter是一个集合,用于添加和快速检索,最大优势是省内存(相比于hash);并且有一定的误判率

实现:

1、python中实现bit数组,可以借助现有工具bitvector,也可以自己用array实现,我的实现如下:

#%% 自定义bitset数组
import array
class my_bitset(object):
'''
自定义bitset数组,共sz位
占用_array_sz个byte array
用于bloom过滤器使用
'''
def __init__(self, sz=1):
self.sz = sz
self._array_sz = sz/8 if sz%8==0 else sz/8+1
# 'B'	unsigned char,以此模拟bitset
self.array = array.array('B', [0]*self._array_sz)

def __contains__(self, v):
return True if self.__getitem__(v) else False

## 获取bieset的第i位是否为1(从左往右数)
def __getitem__(self, i):
i_arr,i_byte = i/8,i%8
k = 128>>i_byte
return (self.array[i_arr] & k)!=0

## 设置bieset的第i位为1
def __setitem__(self, i, value=1):
i_arr,i_byte = i/8,i%8
k = 128>>i_byte
# 如果该位为1,则不操作;为0则加上k
if not self.__getitem__(i):
self.array[i_arr] += k


只需实现几个简单功能即可,获取第i位的状态0-1,将第i位置1;使用unsigned char array实现

2、实现hash函数
#%% 自定义hash函数
class Bkdr_hash(object):
def __init__(self, cap, seed):
self.cap = cap
self.seed = seed

def bkdr_hashs(self, inputstr):
ret = 0
for ch in inputstr:
ret = ret*self.seed + ord(ch)
# 控制输出的范围<cap
return ret % self.cap


给出不同的种子,将string映射为整数,并且用%操作来控制输出值小于cap,即小于bitset的长度
hash的实现可以参考这篇: http://blog.csdn.net/mycomputerxiaomei/article/details/7641221
3、bloom过滤器实现
#%% 布隆过滤器
import math
import BitVector as bv

class Bloomfilter(object):
def __init__(self, num=1e8, err=1e-6):
self.num = num
self.err = err
# m/n的值,k=ln2*m/n,err=0.5^k
self.n_hashs = int(math.log(self.err, 0.5))
self.bit_sz = int(self.n_hashs/math.log(2)*self.num)
if 0:
self.bitset = my_bitset(sz=self.bit_sz) # 我自己定义的数据结构
else:
# 使用现成的 BitVector,实际上速度还没我的快
self.bitset = bv.BitVector(size=self.bit_sz)
self.generate_hash_seeds()
self.generate_hash_funs()

def show(self):
print 'n_hashs: %s, bit_sz: %s' % (self.n_hashs, self.bit_sz)

## 生成 self.n_hashs 个质数作为hash种子
def generate_hash_seeds(self):
self.hash_seeds = [2]
candi = 3
while len(self.hash_seeds)<self.n_hashs:
flag = 1
for p in self.hash_seeds:
if candi%p==0:
flag = 0
break
if flag:
self.hash_seeds.append(candi)
candi += 1

## 产生hash函数
def generate_hash_funs(self):
self.hash_funs = []
for se in self.hash_seeds:
tmp_hf = Bkdr_hash(self.bit_sz, se).bkdr_hashs
self.hash_funs.append(tmp_hf)

## 添加hash后的值
def add(self, value):
for h_f in self.hash_funs:
hashed_value = h_f(value)
#            print value, hashed_value
self.bitset[hashed_value] = 1

## 检查值是否在bloomfilter中
def __contains__(self, value):
for h_f in self.hash_funs:
hashed_value = h_f(value)
#            print hashed_value
if self.bitset[hashed_value]==0:
return False
return True


根据误差和输入个数的计算公式,可以控制误差率

4、效果验证

首先定义一个随机字符串生成函数:
## 随机生成一些字符串
def generate_random_str(num):
res = set()
for i in range(num):
length = random.randint(1,10)
tmpv = ''
for j in range(length):
offset = random.randint(0,25)
tmpv += chr(0x41+offset)
res.add(tmpv)
return res


随后给出测试函数:
def test_bloom_filter():
random.seed(0)
bf = Bloomfilter(num=1e5, err=1e-3)
bf.show()
num = 10**4
# 将add集的元素加入bloomfilter
addset = generate_random_str(num)
#    print testset
for v in addset:
bf.add(v)

# 测试集,其中的元素都不在add集中
testset = generate_random_str(num) - addset
st = time.clock()
FP = 0
for v in testset:
if v in bf:
FP += 1
print FP, num, FP/float(num)
print 'time cost:', time.clock()-st

# 传统集合测试(由于实现的原因,传统集合比我这个快得多。。)
FP = 0
st = time.clock()
for v in testset:
if v in addset:
FP += 1
print FP, num, FP/float(num)
print 'time cost:', time.clock()-st

test_bloom_filter()


控制误差在千分之一,结果如下:



可以看到,使用9个hash函数来进行映射,存储1w个字符串,bit长度为1298425,大小为1298425.0/8/1024 = 158k
其误差率为0.0048,即10000个中有48个不在集合中,但被误判在集合内。

相比于python内建的set类型,我们的方法速度很慢,这个是由实现导致的。
hash的实现,每个字符串对应为一个8字节的信息指纹,大小为8*10000/1024*2 = 156k

注意,实际上我的bitset是按照容纳10**5个数来设计的,如果改为10**4,则容量bitset长度可以缩小一倍,同时误差率在2%左右
这样来看,用bloomfitler实现的set()容量仅为传统hashset的1/10左右
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: