别名采样(Alias Method)
参考资料:
维基百科Alias method
解决的问题
从一个离散的概率分布中采样:给定n个对象o1,...on,分别具有权重w1,...,wn,对n个对象进行加权采样,采样概率wi=∑j=1nwjwi.
整篇文章以下述例子为例:
假设有3个对象o1,o2,o3,其权重为w=[1,2,3],总权重和为6,归一化权重为p=[61,62,63],要求分别以61、31、21的概率采样o1,o2,o3。
算法步骤
Alias方法相当于有n个桶,每个桶内最多有两个元素oi和oi的别名。优先处理小权重对象,用大权重对象补齐。确保每个桶的权重相同,采样时先均匀采样桶,再在桶内采样两个元素。
预处理
预处理阶段构造两张表:概率表Ui和别名表Ki(1<=i<=n)。
首先将概率表初始化为Ui=npi,由此将表中的元素分为三类:
- “溢满”组:Ui>1
- “不满”组:Ui<1且Ki尚未初始化
- “恰好”组:Ui=1或Ki已被初始化
若Ui=1,则令Ki=i,即oi的别名就是自己。
只要不是所有的元素都在“恰好”组,就重复执行以下步骤:
- 任意从“溢满”组和“不满”组各选择一个元素Ui>1和Uj<1。(若其中一个存在,则另一个也必然存在。)
- 设置Kj=i,即oj的别名设置为oi。
- 将Ui更新为Ui−(1−Uj)。
- oj现在应该归为“恰好”组。
- 对于oi,根据更新后的Ui,将其分在恰当的组中。
例子中首先将概率表初始化为:
| U1 | U2 | U3 |
|---|
| 0.5 | 1 | 1.5 |
o1属于“不满”组,o2属于“恰好”组,o3属于“溢出”组。因此,令K2=2。
从“溢满”组和“不满”组各选择一个元素o3和o1,设置K1=3,更新U3=U1+U3−1=1。
| U1 | U2 | U3 |
|---|
| 1 | 1 | 1 |
此时,o1应该归为“恰好组”,o3也归为“恰好”组。此时所有元素都在“恰好”组,算法结束,构建的概率表和别名表如下:
| U1 | U2 | U3 |
|---|
| 0.5 | 1 | 1 |
| K1 | K2 | K3 |
|---|
| 3 | 2 | 3 |
预处理过程每一轮迭代至少使一个元素进入“恰好”组。因此,至多经过n−1轮迭代后,该过程必然终止。每一轮迭代可在O(1)时间内完成,从而生成表的过程可在O(n)时间内完成。
采样
根据概率表Ui和别名表Ki进行采样。首先均匀采样表内索引,然后根据概率表索引位置进行一次有偏硬币抛投实验,从而确定结果是i还是Ki。
具体来说:
- 输出一个均匀的随机变量0<=x<1。
- 记i=⌊nx⌋+1及y=nx+1−i。(此时i均匀分布于{1,2,...,n},y均匀分布于[0,1)。)
- 如果y<Ui,则返回i。
- 否则,返回Ki。
例子中:若x=0.8,则i=3,直接返回o3(o3的别名是它自己);
若x=0.5,则i=2,直接返回o2(o2的别名是它自己);
若x=0.8,则i=3,直接返回o3(o3的别名是它自己);
若x=0.1,则i=1,y=0.3<U1,返回o1;
若x=0.3,则i=1,y=0.9>=U1,返回o3(o1的别名是它o3)。
每次采样时间复杂度为O(1)。
其他采样算法
朴素方法:直接累加权重并线性搜索
步骤:
- 计算归一化权重的前缀和[w1,w1+w2,w1+w2+w3]=[1,3,6]
- 生成一个随机数r∈[0,w1+w2+w3)
- 在前缀和中线性查找r所在区间:若r<1,则采样o1;若1<=r<3,则采样o2;若3<=r<6,则采样o3
时间复杂度:
预处理前缀和的时间复杂度为O(n),每次采样线性搜索时间复杂度为O(n),对于m次采样,总时间复杂度为O(n+mn)
二分搜索法
步骤:
- 预处理前缀和数组,和朴素方法一样。
- 使用二分搜索快速找到随机数r所在区间
时间复杂度:
预处理O(n),每次采样O(logn),对于m次采样,总时间复杂度为O(n+mlogn)
优势
对于m次采样,别名采样法总时间复杂度为O(n+m)。当n很大且采样次数较多时,Alias方法比朴素方法和二分搜索法快很多。