3. 随机数生成与随机抽样
蒙特卡洛仿真的全部随机性都来自一个源头:随机数。但计算机本身是确定性的,它无法凭空产生真正的随机性,只能通过确定性算法生成看起来像随机的数列,这就是伪随机数。理解伪随机数的生成机制、均匀分布抽样方法,以及如何把均匀随机数变换成任意目标分布的样本,是搭建一切蒙特卡洛模型的基础。本节从底层生成器讲到四种常用抽样方法,并给出可直接运行的实现思路。
一、伪随机数生成器:确定性的“随机”
伪随机数生成器(PRNG)本质上是一个状态机:给定一个初始种子(seed),它按固定递推公式不断产生新的状态,并把状态映射为 [0,1) 区间上的数值。只要种子相同,产生的序列就完全相同——这既是缺点(可预测),也是巨大优点(可复现)。仿真调试时,固定种子能让每次运行结果一致,便于定位问题。
最经典的生成器是线性同余发生器(LCG),递推式为:
X_{n+1} = (a * X_n + c) mod m
U_n = X_n / m // 归一化到 [0,1)
其中 m 是模数,a 是乘子,c 是增量,X_0 是种子。例如取 m = 2^31、a = 1103515245、c = 12345,就是早期 C 标准库常用的参数。LCG 实现简单、速度快,但低位随机性差,且周期有限,高维上会出现规律性“晶格结构”,因此现代仿真更推荐使用梅森旋转(Mersenne Twister)或 PCG 等生成器。Python 的 random 模块底层就是梅森旋转,周期长达 2^19937−1,在绝大多数工程场景下足够可靠。
二、均匀分布抽样:一切抽样的起点
PRNG 直接输出的就是 [0,1) 上的均匀随机数 U。由 U 可以轻松得到任意区间 [a,b) 上的均匀样本:
X = a + (b - a) * U
例如要生成 [10, 20] 上的均匀随机数,取 U = 0.37,则 X = 10 + 10×0.37 = 13.7。均匀抽样是所有其他抽样方法的“原材料”,逆变换法和接受拒绝法都建立在它之上。
三、逆变换法:用累积分布函数的反函数抽样
逆变换法(Inverse Transform Method)是理论上最优雅的抽样方法。它的依据是:若 U ~ Uniform(0,1),F 是某目标分布的累积分布函数(CDF),则 X = F⁻¹(U) 服从该分布。直观理解是:U 落在 [0,1) 的哪个位置,就对应 CDF 曲线上的哪个分位点,从而“搬运”到目标分布上。
以指数分布为例,其 CDF 为 F(x) = 1 − e^(−λx),反解得到:
X = -ln(1 - U) / λ
由于 1−U 与 U 同为均匀分布,工程上常简写为 X = −ln(U)/λ。假设 λ = 0.5,取 U = 0.2,则 X = −ln(0.2)/0.5 ≈ 3.22。逆变换法的优点是每个样本只需一次计算,效率高;缺点是很多分布的 F⁻¹ 没有解析式(如正态分布),此时需要数值近似或改用其他方法。
四、接受拒绝法:用“试探—筛选”逼近任意分布
当目标分布 f(x) 难以直接抽样时,接受拒绝法(Acceptance-Rejection Method)提供了一条通用路径。它需要一个容易抽样的建议分布 g(x),以及一个常数 M,使得对定义域内所有 x 都有 f(x) ≤ M·g(x)。算法流程如下:
- 从建议分布 g 中抽取一个候选样本 Y;
- 从 Uniform(0,1) 抽取 U;
- 若 U ≤ f(Y) / (M·g(Y)),则接受 Y 作为样本;否则拒绝,回到第 1 步。
接受率等于 1/M,因此 M 越接近 1,效率越高。若 M 过大,大量候选样本被丢弃,计算成本急剧上升。例如用 Uniform(0,1) 作为建议分布去抽样 Beta(2,3) 分布,其密度最大值为 1.5,取 M = 1.5,则平均接受率约 66.7%,效率尚可。若换成一个形状差异很大的建议分布,M 可能达到 10 以上,接受率不足 10%,此时应更换建议分布。
五、方法对比与实现要点
四种方法各有适用场景:PRNG 是底层引擎,均匀抽样是基础原料,逆变换法适合 CDF 可解析反解的分布(指数、威布尔、逻辑斯蒂等),接受拒绝法适合密度函数已知但难以反解的分布(Beta、截断正态等)。实际工程中,正态分布通常用 Box-Muller 变换或 Ziggurat 算法,它们比朴素接受拒绝法更快。
下面给出一个 Python 实现示例,同时演示逆变换法和接受拒绝法:
import numpy as np
rng = np.random.default_rng(seed=42) # 固定种子,保证可复现
# 逆变换法:指数分布 lambda=0.5
def exp_inverse(n, lam):
u = rng.random(n)
return -np.log(u) / lam
# 接受拒绝法:Beta(2,3),建议分布 Uniform(0,1),M=1.5
def beta_rejection(n, M=1.5):
samples = []
while len(samples) < n:
y = rng.random()
u = rng.random()
f = 12 * y * (1 - y)**2 # Beta(2,3) 密度
if u <= f / M:
samples.append(y)
return np.array(samples)
exp_samples = exp_inverse(10000, 0.5)
beta_samples = beta_rejection(10000)
print("指数均值:", exp_samples.mean()) # 理论值 1/0.5 = 2
print("Beta均值:", beta_samples.mean()) # 理论值 2/(2+3) = 0.4
运行后,指数样本均值应接近 2,Beta 样本均值应接近 0.4。若偏差明显偏大,首先检查种子是否固定、样本量是否足够,再检查接受拒绝法的 M 是否满足上界条件。样本量较小时(如 n=100),均值波动属于正常统计涨落,不能据此判定算法错误。
总结来看,伪随机数生成器提供可复现的均匀随机源,均匀抽样是基础,逆变换法以解析反解换取高效率,接受拒绝法以通用性换取灵活性。选择哪种方法,取决于目标分布的 CDF 是否可反解、密度函数是否已知、以及对计算效率的要求。在后续的蒙特卡洛仿真中,这些抽样方法将直接决定输入随机变量的质量,进而影响整个稳健设计分析的可靠性。