defnormal_box_muller(): import random from math import sqrt, log, pi, cos, sin u1 = random.random() u2 = random.random() r = sqrt(-2 * log(u1)) theta = 2 * pi * u2 z0 = r * cos(theta) z1 = r * sin(theta) return z0, z1
接下来,我们来看看 Box-Muller 法生成的二维标准正态分布动画吧
拒绝采样极坐标方法
Box-Muller 方法还有一种形式,称为极坐标形式,属于拒绝采样方法。
1. 生成独立的 u, v 和 s
分别生成 [0, 1] 均匀分布 u 和 v。令 \(s =
r^2 = u^2 + v^2\)。如果 s = 0或 s ≥ 1,则丢弃 u 和 v
,并尝试另一对 (u , v)。因为 u 和 v
是均匀分布的,并且因为只允许单位圆内的点,所以 s
的值也将均匀分布在开区间 (0, 1) 中。注意,这里的 s
的意义虽然也为半径,但不同于基本方法中的 s。这里 s 取值范围为
(0, 1) ,目的是通过 s 生成指数分布,而基本方法中的 s 取值范围为 [0,
+∞],表示二维正态分布 PDF 采样点的半径。复用符号 s
的原因是为了对应维基百科中关于基本方法和极坐标方法的数学描述。
我们用代码来验证 s 服从 (0, 1) 范围上的均匀分布。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
defgen_polar_s(): import random whileTrue: u = random.uniform(-1, 1) v = random.uniform(-1, 1) s = u * u + v * v if s >= 1.0or s == 0.0: continue return s
defplot_polar_s(): s = [gen_polar_s() for _ inrange(10000) ] plot_dist_1d(s, title='PDF Polar $s = u^2 + v^2$')
defnormal_box_muller_polar(): import random from math import sqrt, log whileTrue: u = random.uniform(-1, 1) v = random.uniform(-1, 1) s = u * u + v * v if s >= 1.0or s == 0.0: continue z0 = u * sqrt(-2 * log(s) / s) z1 = v * sqrt(-2 * log(s) / s) return z0, z1
那么Inverse Transformation Method 的第一步,随机生成 0-1 之间的数
u,可以直观的认为是在 y 轴上生成一个随机的点
u。注意到5段竖虚线对应了5个离散的取值,它们的长度和为1,并且每一段长度代表了每个值的权重。因此,通过在
y 轴上的均匀采样可以生成给定PMF的 x 的分布。
离散分布的逆变换采样方法用数学公式可以表述为:找到第一个
x,其CDF的范围包括了 u,即
\[
F^{-1}(u)=\min \{x: F(x) \geq u\}
\]
扩展到连续分布
有了离散类别分布的直观感受,扩展到连续分布也就不难理解了。类似于微积分中将连续空间做离散切分,再通过极限的方法,连续光滑函数在
y 轴上可以切分成长度为 \(\Delta u\)
的线段,那么生成的 x 值就是其近似值。随着 $ _{u } $,最终 $ x=F^{-1}(u)
$ 即为满足要求的分布。
defpoisson(lambdda: float) -> int: total = 1.0 i = 0 threshold = exp(-1 * lambdda) while total >= threshold: u = random.random() total *= u i += 1 return i - 1