水塘采样(Reservoir sampling)算法

最近看了Flink中的rangePartition使用了水塘采样算法,因此参考维基百科详细了解了一下。

采样的关键在于对每个元素的选取需要是等概率的。水塘采样其目的在于从包含n个项目的集合S中选取k个样本,其中n为一很大或未知的数量,尤其适用于不能把所有n个项目都存放到主内存的情况。
适用问题:
1.可否在一未知大小的集合中,随机取出k个元素?
2.在不知道文件总行数的情况下,如何从文件中随机的抽取k行?

采样过程:集合中总元素个数为n,随机选取k个元素
step1.首先将前k个元素全部选取。
step2.对于第i个元素(i>k),以概率k/i来决定是否保留该元素,如果保留该元素的话,则随机丢弃掉原有的k个元素中的一个(即原来某个元素被丢掉的概率是1/k)。
结果:每个元素被最终被选取的概率都是k/n。

举例说明:例子直接翻译自维基百科。
以选取10个元素为例。
1.当总元素为11个时。每个元素保留下来的概率是10/11
证明:
对于前10个元素,一开始就会被选择。
对于第11个元素,被选择的概率是:10/11,即满足。
前10个元素中某个元素t被保留下来的条件为:第11个元素被选中,且不替换掉该t元素+第11个元素不被选中。概率为:10/11x(1-1/10)+1/11=10/11。
结论:所有元素都被10/11的等概率选择是否保留。

2.当总元素为12个时。每个元素保留下来的概率是10/12
证明:
对于前10个元素,一开始就会被选择。
对于第11个元素,最后留下的条件为:被选中,而且不被第12个元素替换掉。概率为:10/11x(1-10/12x1/10)=10/12。其中10/11是第11个元素保留的概率,10/12是第12个元素被选中保留的概率,1/10是正好替换掉第11个元素的概率。
对于第12个元素,最后留下的条件为:被选中,即10/12。
对于前10个元素中某个元素i被保留下来的条件为:第11个元素不替换第t个元素且第12个元素不替换第t个元素。概率为:(1-10/11x1/10)x(1-10/12x1/10)=10/12。

3.当总元素为n个时,每个元素保留下来的概率是10/n
证明:
对于第i个元素,i>10。被保留下来的条件为:被保留,且不被i+1,i+2…n个元素替换掉。概率为:(10/i)x(1-10/(i+1)x 1/10)x…x(1-10/n x 1/10)=10/i x i/i+1 x … x n-1/n =10/n。
对于前十个元素中的某个,被保留下来的条件为,不被第11,12,…,n个元素替换掉。
概率为:(1-10/11 x 1/10)x(1-10/12 x 1/10)x…x(1-10/n x 1/10) = 10/11 x 11/12 x … x n-1/n = 10/n。

代码如下:直接抄的维基百科里的:

(*
  S has items to sample, R will contain the result
 *)
ReservoirSample(S[1..n], R[1..k])
  // fill the reservoir array
  for i = 1 to k
      R[i] := S[i]

  // replace elements with gradually decreasing probability
  for i = k+1 to n
    j := random(1, i)   // important: inclusive range
    if j <= k
        R[j] := S[i]

参考:
https://en.wikipedia.org/wiki/Reservoir_sampling

猜你喜欢

转载自blog.csdn.net/u013036495/article/details/85181840