一个集合里有 n 个元素,每个元素有不同的权重,现在要不放回地随机抽取 m 个元素,每个元素被抽中的概率为元素的权重占总权重的比例。要怎么做呢?

简单的解法

现在考虑只抽取一个元素,假设权重之和为 1。我们可以从 [0, 1] 中随机得到一个权重,假设为 0.71,而后从第一个元素开始,不断累加它们的权重,直到有一个元素的累加权重包含 0.71,则选取该元素。下面是个示意图:

要选取 m 个元素,则可以按上面的方法先选取一个,将该元素从集合中去除,再反复按上面的方法抽取剩余的元素。这种方法的复杂度是 O(mn),并且将元素从集合中删除其实不太方便实现。

当然,最重要的是这个算法需要多次遍历数据,不适合用在流处理的场景中。

Algorithm A

Algorithm A 是论文 Weighted Random Sampling 中提出的,步骤如下:

  1. 对于集合  中的元素 ,选取均匀分布的随机数  ,计算元素的特征 
  2. 将集合按  排序,选取前  大的元素。

算法的正确性在作者 2006 年的论文 Weighted random sampling with a reservoir 里给了详细的证明。论文中给出了算法的两个变种 A-Res 与 A-ExpJ,它们都能在一次扫描中得到 m 个样本。非常适合在流处理的场合中。

A-Res 算法

A-Res(Algorithm A With a Reservoir) 是 Algorithm 的“蓄水池”版本,即维护含有 m 个元素的结果集,对每个新元素尝试去替换结果集中权重最小的元素。步骤如下:

  1. 将集合  的前  个元素放入结果集合 
  2. 对于结果集里的每个元素,计算特征值 ,其中 
  3. 对  重复步骤 4 ~ 6
    1. 将结果集中最小的特征  作为当前的阈值 
    2. 对于元素 ,计算特征 ,其中 
    3. 如果  则将  中拥有最小  值的元素替换成 

论文证明了如果权重  是一般连续分布上的随机变量,则上面的算法中插入  的次数为 

该算法用 Python 实现如下:

import heapq
import randomdef a_res(samples, m):""":samples: [(item, weight), ...]:k: number of selected items:returns: [(item, weight), ...]"""heap = [] # [(new_weight, item), ...]for sample in samples:wi = sample[1]ui = random.uniform(0, 1)ki = ui ** (1/wi)if len(heap) < m:heapq.heappush(heap, (ki, sample))elif ki > heap[0][0]:heapq.heappush(heap, (ki, sample))if len(heap) > m:heapq.heappop(heap)return [item[1] for item in heap]

A-ExpJ 算法

A-Res 需要对每个元素产生一个随机数,而生成高质量的随机数有可能会有较大的性能开销,,所以论文中给出了 A-ExpJ 算法,能将随机数的生成量从  减少到 。从步骤上看,很像我们最开始提出的简单版本,设定一个阈值并跳过一些元素。具体步骤如下:

  1. 将集合  的前  个元素放入结果集合 
  2. 对于结果集里的每个元素,计算特征值  ,其中 
  3. 将  中小最的特征值记为阈值 
  4. 对剩下的元素重复步骤 5 ~ 10
    1. 令  且 
    2. 从当前元素  开始跳过元素,直到遇到元素 ,满足
    3. 使用  替换  中特征值最小的元素。
    4. 令  的特征 
    5. 令新的阈值  为此时  中的最小特征值。

Python 实现如下:

def a_expj(samples, m):""":samples: [(item, weight), ...]:k: number of selected items:returns: [(item, weight), ...]"""heap = [] # [(new_weight, item), ...]Xw = NoneTw = 0w_acc = 0for sample in samples:if len(heap) < m:wi = sample[1]ui = random.uniform(0, 1)ki = ui ** (1/wi)heapq.heappush(heap, (ki, sample))continueif w_acc == 0:Tw = heap[0][0]r = random.uniform(0, 1)Xw = math.log(r)/math.log(Tw)wi = sample[1]if w_acc + wi < Xw:w_acc += wicontinueelse:w_acc = 0tw = Tw ** wir2 = random.uniform(tw, 1)ki = r2 ** (1/wi)heapq.heappop(heap)heapq.heappush(heap, (ki, sample))return [item[1] for item in heap]

验证

我们用多次采样的方式来尝试验证算法的正确性。下面代码1中为 abc 等元素赋予了不同的权重,采样 10 万次后计算被采样的次数与元素 a 被采样次数的比值。

overall = [('a', 10), ('b', 20), ('c', 50), ('d', 100), ('e', 200)]
def test_weighted_sampling(func, k):stat = {}for i in range(100000):sampled = func(overall, k)for item in sampled:if item[0] not in stat:stat[item[0]] = 0stat[item[0]] += 1total = stat['a']for a in stat:stat[a] = float(stat[a])/float(total)print(stat)

首先验证 A-Res 算法:

test_weighted_sampling(a_res, 1)
test_weighted_sampling(a_res, 2)
test_weighted_sampling(a_res, 3)
test_weighted_sampling(a_res, 4)
test_weighted_sampling(a_res, 5)# output
{'e': 19.54951600893522, 'd': 9.864110201042442, 'c': 4.842889054355919, 'a': 1.0, 'b': 1.973566641846612}
{'b': 2.0223285486443383, 'e': 12.17949833260838, 'd': 8.95287806292591, 'c': 4.843410178338408, 'a': 1.0}
{'a': 1.0, 'e': 6.166443722530097, 'd': 5.597171794381808, 'b': 1.9579591056755208, 'c': 4.387922797630423}
{'b': 1.8358898492044953, 'e': 2.5878688779880092, 'c': 2.4081341327311896, 'd': 2.549897479820395, 'a': 1.0}
{'a': 1.0, 'd': 1.0, 'c': 1.0, 'b': 1.0, 'e': 1.0}

看到,在采样一个元素时,b 被采样到的次数约为 a 的 2 倍,而 e 则约为 20 倍,与overall 数组中指定的权重一致。而采样 5 个元素时,所有元素都会被选中。

同理验证 A-ExpJ 算法:

test_weighted_sampling(a_expj, 1)
test_weighted_sampling(a_expj, 2)
test_weighted_sampling(a_expj, 3)
test_weighted_sampling(a_expj, 4)
test_weighted_sampling(a_expj, 5)# output
{'e': 19.78311444652908, 'c': 4.915572232645403, 'd': 9.840900562851782, 'a': 1.0, 'b': 1.9838649155722325}
{'e': 11.831543244771057, 'c': 4.709157716223856, 'b': 1.9720180893159978, 'd': 8.75183719615602, 'a': 1.0}
{'d': 5.496249062265567, 'c': 4.280007501875469, 'e': 6.046324081020255, 'b': 1.9321080270067517, 'a': 1.0}
{'a': 1.0, 'd': 2.5883654175335105, 'c': 2.440760540383957, 'e': 2.62591841571643, 'b': 1.8787559581808126}
{'a': 1.0, 'd': 1.0, 'c': 1.0, 'b': 1.0, 'e': 1.0}

与 A-Res 的结果类似。

小结

文章中介绍了 A-Res 与 A-ExpJ 两种算法,按照步骤用 Python 实现了一个简单的版本,最后用采样的方式验证了算法的正确性。

加权随机采样本身不难,但如果需要在一次扫描中完成就不容易了。难以想像上面的算法直到 2006 年才提出。算法本身如此之简单,也让不感叹数学与概率的精妙。

参考

  • Weighted Random Sampling (2005; Efraimidis, Spirakis) 2015 年论文,大概介绍了本文中提到的算法
  • Weighted random sampling with a reservoir 作者于 2016 年的论文,其中有详细的数学证明
  • 加权随机抽样 有 2005 年论文的翻译
  • 概率加权的随机抽样 (Weighted Random Sampling) – A-Res 蓄水池算法
  • Metrics Core Java 的一个性能监控库,其中的 ExponentiallyDecayingReservoir 用到了 A-Res 算法。

  1. 1.修改自 https://blog.xingwudao.me/2017/09/26/sampling/ ↩

加权随机采样 (Weighted Random Sampling)相关推荐

  1. python按指定概率抽样_概率加权的随机抽样 (Weighted Random Sampling) – A-Res 蓄水池算法...

    概率加权的随机抽样 (Weighted Random Sampling) – A-Res 蓄水池算法 2017-11-20 18:51:10 旧日重来 最近,Aulddays 遇到一个随机抽样任务.有 ...

  2. 贪心搜索(greedy search)、集束搜索(beam search)、随机采样(random sample)

    当我们训练完成一个自然语言生成模型后,需要使用这个模型生成新的语言(句子),如何生成这些句子,使用如下的方法:贪心搜索,集束搜索,随机搜索. 贪心搜索(greedy search)/采样(Sampli ...

  3. 随机采样和分布式光线追踪

    随机采样和分布式光线追踪 本文主要叙述了论文[3][4]中的两个方法,一种是随即采样,一种是分布式光线追踪方法,在介绍随机采样方法前,由泊松圆盘采样引入. 一.泊松圆盘采样(Poisson Disk ...

  4. 【Stacking改进】基于随机采样与精度加权的Stacking算法

    [Stacking改进]基于随机采样与精度加权的Stacking算法 摘要 近年来,人工智能的强势崛起让我们领略到人工智能技术的巨大潜力,机器学习也被广泛应用于各个领域,并取得不错的成果.本文以Kag ...

  5. 随机采样和随机模拟:吉布斯采样Gibbs Sampling

    2016年05月12日 00:24:21 阅读数:45658 http://blog.csdn.net/pipisorry/article/details/51373090 吉布斯采样算法详解 为什么 ...

  6. 随机森林(Random Forest)为什么是森林?到底随机在哪里?行采样和列采样又是什么东西?

    ensemble.RandomForestClassifier([-]) A random forest classifier. ensemble.RandomForestRegressor([-]) ...

  7. 随机采样池化--S3Pool: Pooling with Stochastic Spatial Sampling

    S3Pool: Pooling with Stochastic Spatial Sampling CVPR2017 https://github.com/Shuangfei/s3pool 本文将常规池 ...

  8. java实现加权随机,负载均衡--加权随机算法(Weight Random)

    加权随机法根据服务器的配置和系统的负载,分配不同的权重,按照权重随机请求后端服务器. 一.算法描述 假设有 N 台服务器 S = {S0, S1, S2, -, Sn},权重为 W = {W0, W1 ...

  9. python 加权随机算法_python的random模块及加权随机算法的python实现方法

    random是用于生成随机数的,我们可以利用它随机生成数字或者选择字符串. •random.seed(x)改变随机数生成器的种子seed. 一般不必特别去设定seed,Python会自动选择seed. ...

  10. 随机采样和随机模拟:吉布斯采样Gibbs Sampling实现高斯分布参数推断

    http://blog.csdn.net/pipisorry/article/details/51539739 吉布斯采样的实现问题 本文主要说明如何通过吉布斯采样来采样截断多维高斯分布的参数(已知一 ...

最新文章

  1. Python基础知识(五)--数据类型
  2. C++最全输入方式总结(cin、get、getchar、getline)
  3. [BUUCTF-pwn]——[ZJCTF 2019]EasyHeap
  4. mysql存中文_mysql数据库存储中文数据的解决办法
  5. django07: 模板语言(旧笔记)
  6. 事务模型与分布式事务总结思考
  7. python读取坐标文本文件_Python 实现文件读写、坐标寻址、查找替换功能
  8. JavaScript权威指南 - 数组
  9. Go调用zlib实现压缩与解压缩
  10. ubuntu设置分辨率
  11. 【信号与系统】Multisim 仿真抽样定理与信号恢复
  12. [BZOJ1163][Baltic2008]Mafia
  13. Docker中什么是宿主机?
  14. 学习笔记14--环境感知传感器技术之毫米波雷达
  15. 深度学习 01 探索深度学习
  16. 爬虫写得好,牢饭吃得早
  17. 搭建企业gitlab私有仓库全过程
  18. Java实现10万条经纬度数据压缩后只有15k
  19. selenium入门教程
  20. android Ble4.0蓝牙开发之搜索慢、startLeScan()过时,6.0以上不需要定位权限也能快速搜索到蓝牙设备

热门文章

  1. Java学习笔记分享之MyBatis篇(中)
  2. Java设计模式——Command模式(容易,次要)
  3. 微信视频号视频如何下载保存?教你批量下载保存视频号视频到手机相册
  4. 郑捷 机器学习算法与编程实践 --ID3决策树 python3 代码
  5. 单变量微积分笔记1——导数1(导数的基本概念)
  6. 一元二次方程的解的程序
  7. 微信搜一搜中的智能问答技术
  8. 如何在Jetson NANO上安装无线WIFI模块
  9. sap采购订单更改记录_SAP采购运费发票处理
  10. 新手学游戏开发必知的一课