算法 | 别名采样法

367 阅读3分钟

别名采样(Alias Method)

参考资料: 维基百科Alias method

解决的问题

从一个离散的概率分布中采样:给定nn个对象o1,...ono_1,...o_n,分别具有权重w1,...,wnw_1,...,w_n,对nn个对象进行加权采样,采样概率wi=wij=1nwjw_i=\frac{w_i}{\sum_{j=1}^{n}w_j}.

整篇文章以下述例子为例:

假设有3个对象o1,o2,o3o_1,o_2,o_3,其权重为w=[1,2,3]w=[1,2,3],总权重和为6,归一化权重为p=[16,26,36]p=[\frac{1}{6},\frac{2}{6},\frac{3}{6}],要求分别以16\frac{1}{6}13\frac{1}{3}12\frac{1}{2}的概率采样o1,o2,o3o_1,o_2,o_3

算法步骤

Alias方法相当于有nn个桶,每个桶内最多有两个元素oio_ioio_i的别名。优先处理小权重对象,用大权重对象补齐。确保每个桶的权重相同,采样时先均匀采样桶,再在桶内采样两个元素。

预处理

预处理阶段构造两张表:概率表UiU_i和别名表Ki(1<=i<=n)K_i(1<=i<=n)

首先将概率表初始化为Ui=npiU_i=np_i,由此将表中的元素分为三类:

  • “溢满”组:Ui>1U_i>1
  • “不满”组:Ui<1U_i<1KiK_i尚未初始化
  • “恰好”组:Ui=1U_i=1KiK_i已被初始化

Ui=1U_i=1,则令Ki=iK_i=i,即oio_i的别名就是自己。

只要不是所有的元素都在“恰好”组,就重复执行以下步骤:

  1. 任意从“溢满”组和“不满”组各选择一个元素Ui>1U_i>1UjU_j<1。(若其中一个存在,则另一个也必然存在。)
  2. 设置Kj=iK_j=i,即ojo_j的别名设置为oio_i
  3. UiU_i更新为Ui(1Uj)U_i-(1-U_j)
  4. ojo_j现在应该归为“恰好”组。
  5. 对于oio_i,根据更新后的UiU_i,将其分在恰当的组中。

例子中首先将概率表初始化为:

U1U_1U2U_2U3U_3
0.511.5

o1o_1属于“不满”组,o2o_2属于“恰好”组,o3o_3属于“溢出”组。因此,令K2=2K_2=2

从“溢满”组和“不满”组各选择一个元素o3o_3o1o_1,设置K1=3K_1=3,更新U3=U1+U31=1U_3=U_1+U_3-1=1

U1U_1U2U_2U3U_3
111

此时,o1o_1应该归为“恰好组”,o3o_3也归为“恰好”组。此时所有元素都在“恰好”组,算法结束,构建的概率表和别名表如下:

U1U_1U2U_2U3U_3
0.511
K1K_1K2K_2K3K_3
323

预处理过程每一轮迭代至少使一个元素进入“恰好”组。因此,至多经过n1n-1轮迭代后,该过程必然终止。每一轮迭代可在O(1)O(1)时间内完成,从而生成表的过程可在O(n)O(n)时间内完成。

采样

根据概率表UiU_i和别名表KiK_i进行采样。首先均匀采样表内索引,然后根据概率表索引位置进行一次有偏硬币抛投实验,从而确定结果是ii还是KiK_i。 具体来说:

  1. 输出一个均匀的随机变量0<=x<10<=x<1
  2. i=nx+1i=\lfloor nx \rfloor+1y=nx+1iy=nx+1-i。(此时ii均匀分布于{1,2,...,n},yy均匀分布于[0,1)[0,1)。)
  3. 如果y<Uiy<U_i,则返回ii
  4. 否则,返回KiK_i

例子中:若x=0.8x=0.8,则i=3i=3,直接返回o3o_3o3o_3的别名是它自己);

x=0.5x=0.5,则i=2i=2,直接返回o2o_2o2o_2的别名是它自己);

x=0.8x=0.8,则i=3i=3,直接返回o3o_3o3o_3的别名是它自己);

x=0.1x=0.1,则i=1i=1y=0.3<U1y=0.3<U_1,返回o1o_1

x=0.3x=0.3,则i=1i=1y=0.9>=U1y=0.9>=U_1,返回o3o_3o1o_1的别名是它o3o_3)。

每次采样时间复杂度为O(1)O(1)

其他采样算法

朴素方法:直接累加权重并线性搜索

步骤

  1. 计算归一化权重的前缀和[w1,w1+w2,w1+w2+w3]=[1,3,6][w_1, w_1+w_2, w_1+w_2+w_3]=[1,3,6]
  2. 生成一个随机数r[0,w1+w2+w3)r\in[0,w_1+w_2+w_3)
  3. 在前缀和中线性查找rr所在区间:若r<1r<1,则采样o1o_1;若1<=r<31<=r<3,则采样o2o_2;若3<=r<63<=r<6,则采样o3o_3

时间复杂度

预处理前缀和的时间复杂度为O(n)O(n),每次采样线性搜索时间复杂度为O(n)O(n),对于mm次采样,总时间复杂度为O(n+mn)O(n+mn)

二分搜索法

步骤

  1. 预处理前缀和数组,和朴素方法一样。
  2. 使用二分搜索快速找到随机数rr所在区间

时间复杂度

预处理O(n)O(n),每次采样O(logn)O(logn),对于mm次采样,总时间复杂度为O(n+mlogn)O(n+mlogn)

优势

对于mm次采样,别名采样法总时间复杂度为O(n+m)O(n+m)。当nn很大且采样次数较多时,Alias方法比朴素方法和二分搜索法快很多。