这是笔者学习 Stanford cs 168 课程的一些学习笔记
lecture 2, 主要讲一个基于 hash 和独立试验思想,设计的一种数据结构 count min sketch,想法非常类似于 bloom filter,都是以牺牲准确率换空间和时间。
先来看一个简单的在面试中经常会遇到的问题,找主要元素
问题如下: 一组数据流: a 1 , … , a n a_1,\dots,a_n a1,…,an, 存在一个元素 a i a_i ai 保证出现次数大于
n / 2 n/2 n/2。 能否设计一个算法在 仅仅扫描一遍的情况下找到它。
这里有一个很 cute 的算法
current = -1
counter = 0
for e in stream:
if counter == 0:
current = e
counter += 1
elif e == current:
counter += 1
else counter -= 1
return current
可以证明这个算法一定是正确的。
证明
数学归纳法
证毕
这个问题是这样的,
给一个数据流 a 1 , … , a n a_1,\dots,a_n a1,…,an, 以及一个整数 k k k , 找出其中所有 出现次数(记作 frequency) 大于等于 n k \frac{n}{k} kn 的元素
朴素的做法是用一个 hash 表统计一下 每个元素出现的次数,然后再输出次数 ≥ n k \ge \frac{n}{k} ≥kn 的元素,这个算法能够解决这个问题,但是最大的问题是它需要的空间依赖于不同元素的个数,因此我们希望能找一个空间 亚线性(sublinear) 的算法,同时只需要遍历一次整个流,换句话说,希望如下代码能够解决这个问题
for e in stream:
# code
对每个元素只看一次,同时保证空间需求 O ( s i z e O f A n s ) O(sizeOfAns) O(sizeOfAns)
可以证明精确求解算法的亚线性辅助空间算法是不可能的。见 [1] 1.3, 因此我们希望找到一个近似做法
问题同上,不过我们仅仅希望最终找到的答案,满足如下需求:
- 每一个出现次数 ≥ n k \ge \frac{n}{k} ≥kn 的元素都在里面
- 每一个出现次数 ≥ n k − ϵ n \ge \frac{n}{k} - \epsilon n ≥kn−ϵn 的元素也可以在里面
我们允许使用的空间随着 O ( 1 ϵ ) O(\frac{1}{\epsilon}) O(ϵ1) 当 ϵ = 0 \epsilon = 0 ϵ=0 就和原来的问题一样了。算法的空间使用情况为 O ( 1 ϵ ) = O ( k ) O(\frac{1}{\epsilon})=O(k) O(ϵ1)=O(k)
下面就来介绍这篇博客的主角 count-min-sketch 频率统计利器
和 bloom filter 类似的hash 做法,结构如下:
其中 b < < n b << n b<<n,支持两个操作:
add(x),将 x 的频率 +1
frq(x),统计 x 的频率
add(x):
for i in range(l):
cms[i][hash_i(x)] += 1
frq(x):
return min(cms[i][hash(x)]) for i in range(l)
很显然 f r q ( x ) frq(x) frq(x) 一定会高估 c n t ( x ) cnt(x) cnt(x)(元素 x x x 的真实频率), 下面我们来分析一下会高估的概率是多少,同时我们希望 Pr [ m i n ( c m s [ i ] [ h a s h i ( x ) ] ) ≥ c n t ( x ) ] \Pr[min(cms[i][hash_i(x)]) \ge cnt(x)] Pr[min(cms[i][hashi(x)])≥cnt(x)] 的概率足够小,
先给出如下结论:
Pr [ m i n ( c m s [ i ] [ h a s h i ( x ) ] ) ≥ c n t ( x ) ] ≤ e − l \Pr[min(cms[i][hash_i(x)]) \ge cnt(x)] \le e^{-l} Pr[min(cms[i][hashi(x)])≥cnt(x)]≤e−l
证明
定义:
f x f_x fx 元素 x x x 在stream 中的真实频率,这是一个常数
Z i = c m s [ i ] [ h a s h i ( x ) ] Z_i=cms[i][hash_i(x)] Zi=cms[i][hashi(x)]
假设
hash 函数是独立的,同时 Pr [ h a s h ( x ) = h a s h ( y ) ∣ y ≠ x ] ≤ 1 b \Pr[hash(x)=hash(y)|y\ne x]\le \frac{1}{b} Pr[hash(x)=hash(y)∣y̸=x]≤b1
Z i = f x + ∑ h a s h ( y ) = = h a s h ( x ) , x ≠ y f y E [ Z i ] = f x + E [ ∑ h a s h ( y ) = = h a s h ( x ) , x ≠ y f y ] = f x + ∑ x ≠ y f y E [ I ( h a s h ( y ) = h a s h ( x ) ) ] = f x + ∑ y ≠ x f y Pr [ h a s h ( y ) = h a s h ( x ) ] ≤ f x + n b \begin{aligned} Z_i&=f_x+\sum_{hash(y)==hash(x),x\ne y} f_y\\ E[Z_i]&=f_x + E[\sum_{hash(y)==hash(x),x\ne y} f_y]\\ &= f_x+\sum_{x\ne y}f_yE[I(hash(y)=hash(x))]\\ &= f_x+\sum_{y\ne x}f_y\Pr[hash(y)=hash(x)]\\ &\le f_x + \frac{n}{b} \end{aligned} ZiE[Zi]=fx+hash(y)==hash(x),x̸=y∑fy=fx+E[hash(y)==hash(x),x̸=y∑fy]=fx+x̸=y∑fyE[I(hash(y)=hash(x))]=fx+y̸=x∑fyPr[hash(y)=hash(x)]≤fx+bn
fix x 以及 row i令 b = e ϵ , X = Z i − f x b=\frac{e}{\epsilon}, X = Z_i- f_x b=ϵe,X=Zi−fx 由于 E [ X ] ≤ n ϵ e E[X] \le \frac{n\epsilon}{e} E[X]≤enϵ
由 markov’s inequality
Pr [ X > c E [ X ] ] ≤ 1 c \Pr[X > cE[X]] \le \frac{1}{c} Pr[X>cE[X]]≤c1
Pr [ Z i − f x ≥ n ϵ e ∗ e ] = Pr [ Z i ≥ ϵ n + f x ] ≤ 1 e Pr [ m i n ( Z i ) ≥ ϵ n + f x ] ≤ 1 e l \begin{aligned} \Pr[Z_i-f_x\ge \frac{n\epsilon}{e}*e]&=\Pr[Z_i\ge \epsilon n + f_x]\\ &\le \frac{1}{e}\\ \Pr[min(Z_i)\ge \epsilon n + f_x] &\le \frac{1}{e^l} \end{aligned} Pr[Zi−fx≥enϵ∗e]Pr[min(Zi)≥ϵn+fx]=Pr[Zi≥ϵn+fx]≤e1≤el1
完
设错误概率为 σ \sigma σ, 要达到这个概率,大概需要 l = ln σ − 1 l = \ln \sigma^{-1} l=lnσ−1
同时,通常, b = e ϵ = > O ( ϵ − 1 ) = O ( k ) = e k b=\frac{e}{\epsilon} =>^{O(\epsilon^{-1})=O(k)}=e k b=ϵe=>O(ϵ−1)=O(k)=ek
作业中探讨了一种 Conservative Update 的 简单优化方式
这是说,对于操作 add(x)
仅仅将 c m s [ i ] [ h a s h i ( x ) ] = m i n ( c m s [ i ] [ h a s h i ( x ) ] ) cms[i][hash_i(x)] = min(cms[i][hash_i(x)]) cms[i][hashi(x)]=min(cms[i][hashi(x)]) 的那些“格子” 更新,可以很容易的证明这不会低估 f x f_x fx
很容易将操作扩展到 add(x,cnt) 上
这是一份我的作业中的代码 python 实现
这是一份开源代码 java 实现
版权声明
本作品为作者原创文章,采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
作者: taotao
转载请保留此版权声明,并注明出处