随机采样——蓄水池采样算法
2019-05-30 本文已影响0人
复旦猿
问题引入
有一个机器按自然数序列的方式吐出球,1号球,2号球,3号球等等。你没有更多的空间,一个球一旦扔掉,就再也不可拿回。设计一种选择方式,使得当机器吐出第N号球的时候,你袋子中的球数是K个,同时可以保证从1号球到N号球中的每一个,被选进袋子的概率都是K/N。
问题分析
对该问题进行数学建模,即从1-N的自然数序列中等概率抽取K个数,但N是很大或者未知的。对于这种问题最有效的方法就是我们本次要讲的蓄水池采样算法。
蓄水池采样算法
算法描述
- 使用一个长度为
的数组array保存抽取的数字。
- 对输入序列进行遍历,对于第
次遍历,其中
,则:
(1)当时,全部进行抽取,即
。
(2)当时,以
概率抽取,并且随机(等概率)替换掉已经抽取了的
个数中其中的一个数。
- 遍历结束后,数组array中的元素就是最终抽取的数字。
算法证明
-
对于第
个数(
)。在
步之前,所有数都会被选中,即被选中的概率为 1。当走到第
步时,第
个数被第
个元素替换的概率为第
个元素被选中的概率 * 第
被选中替换的概率,即为
。则
被保留的概率为
。依此类推,不被第
个元素替换的概率为
。则运行到第
步时,第
个数被保留的概率 = 被选中的概率 * 不被替换的概率,即:
-
对于第
个数(
)。在第
步被选中的概率为
。不被
个元素替换的概率为
。则运行到第
步时,第
个数被保留的概率 = 被选中的概率 * 不被替换的概率,即:
综上所述,对于每个元素被保留的概率均为 。
算法实现
本文使用java语言实现蓄水池采样算法,如下:
import java.util.*;
public class RadomSelect {
private int[] selected = null;
private static Random rand = new Random(12345);
// 每次拿一个球都会调用这个函数,N表示第i次调用
public int[] carryBalls(int N, int k) {
if (selected == null) {
selected = new int[k];
}
if (N <= k) {
selected[N - 1] = N;
} else {
if (rand.nextInt(N) < k) {
int p = rand.nextInt(k);
selected[p] = N;
}
}
return selected;
}
public static void main(String[] args) {
Bag bag = new Bag();
int N = 1000;
int k = 10;
for (int i = 1; i <= N; i++) {
bag.carryBalls(i, k);
}
System.out.println(Arrays.toString(bag.selected));
}
}
总结
蓄水池采样算法适用于在总的样本数未知或者很大的情况下,对样本进行采样的问题。因此,蓄水池采样算法常用于大数据领域。
写在最后
欢迎大家关注我的个人博客复旦猿。