水库抽样算法推导及实现

水库抽样算法

​ 水库抽样算法为空间亚线性算法,可以在减少计算内存使用量的同时保证抽样数据的均匀性和准确性。

水库抽样算法的应用场景
  • 输入:一组数据,其大小未知
  • 输出:这组数据的k个均匀抽样
  • 要求:
    • 进扫描一次数据
    • 空间复杂性位O(k)
    • 扫描到数据的前n个数字时(n>k),保存当前已扫描数据的k个均匀抽样
水库抽抽样算法的实现
  1. 申请长度为k的数组A保存抽样;

  2. 首先保存最先接收的k个数据;

  3. 当收到第i个数据t时,生成[1,i]间的随机数j,如若j<=k,则以t替换A[j]。

    ​ 第三步的随机替换保证了数据的均匀性和完整数据的概率特征。

水库抽样算法的原理

​ 对于每个新到来的数据i,收入样本的概率为k/i。当第i+1个数据到来时,第i+1个数据被替换到抽样中的概率Pi=k/(i + 1)。而此时,前一个元素i被从中替换出的概率为Pc=1/k。则第i+1个数据替换出第i个数据的概率为PiPc,第i个数据没有被第i+1个数据替换出的概率为P=1-PiPc=1 - (k/(i + 1) * (1/k))=1 - 1/(i + 1)。

​ 由此可知,当第i+2,i+3…个数据到来时,第i个数据没有被替换出来的概率为1 - 1/(i + 2),1 - 1/(i + 3)…

​ 根据古典概型,当元素i被选入且保留至最后扫描完成时的概率为:
P = k i × ( 1 − 1 i + 1 ) × ( 1 − 1 i + 2 ) × ⋯ × ( 1 − 1 n ) P=\frac k i\times(1-\frac {1}{i+1})\times(1-\frac 1{i+2})\times\cdots\times(1-\frac 1{n}) P=ik×(1i+11)×(1i+21)××(1n1)

P = k i × i i + 1 × i + 1 i + 2 × ⋯ × n − 2 n − 1 × n − 1 n = k n P=\frac k i\times\frac{i}{i+1}\times\frac{i+1}{i+2}\times\cdots\times\frac{n-2}{n-1}\times\frac{n-1}{n}=\frac{k}{n} P=ik×i+1i×i+2i+1××n1n2×nn1=nk

​ 由公式可以推导出对于这组数据的所有元素,选入样本的概率都为k/n,这样便可以得到从A中均匀抽样的数据。

Python代码实现

​ 创建一个类封装,定义feed()方法抽样。

class ReservoirSampling(object):

    def __init__(self, size):
        self._size = size
        self.numlist = []
        self._count = 0
        self._counter = 0

    def __iter__(self):
        return self

    def __next__(self):
        self._count += 1
        return self.numlist[self._count - 1]

    def feed(self, item):
        """
        利用迭代器每传递一个数据参数运算一次节约时间与空间
        :param item: 抽样样本元素
        :return:
        """
        if not item:
            pass
        else:
            num = int(item)
            self._counter += 1
            import random as rd
            if len(self.numlist) == self._size:
                index = rd.randint(0, self._counter - 1)
                if index < self._size:
                    self.numlist[index] = num   # 计判断替换与否以及替换位置
            else:
                self.numlist.append(num)

    def __repr__(self):
        return str(self.numlist)

​ 创建一个数据生成器测试抽样算法

import random


def creator():
    counter = 0
    with open("data.txt", 'a') as file:
        while True:
            counter += 1
            if counter == 100000:
                break
            else:
                file.write(str(random.randint(1, 3)))


if __name__ == '__main__':
    creator()

​ 测试水库抽样算法

def test():
    p = ReservoirSampling(10000)
    with open("data.txt", 'r', encoding='UTF-8') as file:
        flag = True
        while flag:
            num = file.read(1)
            if not num:
                flag = False
            else:
                p.feed(num)
    count_1 = 0
    count_2 = 0
    count_3 = 0
    for i in p.numlist:
        if i == 1:
            count_1 += 1
        elif i == 2:
            count_2 += 1
        else:
            count_3 += 1
    print("出现1的概率是{}".format(count_1 / 10000))
    print("出现2的概率是{}".format(count_2 / 10000))
    print("出现3的概率是{}".format(count_3 / 10000))

​ 测试结果为:

​ 出现1的概率是0.3366
​ 出现2的概率是0.3335
​ 出现3的概率是0.3299

满足1、2、3各占1/3的数据概率特点,抽样算法可以比较好的保留数据的概率特征。

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值