4、随机数生成:伪随机数生成器、种子设置、常见分布抽样方法(逆变换法、Box-Muller等)

蒙特卡洛模拟的本质,是用大量随机样本来逼近复杂系统的真实概率分布。因此,随机数的质量直接决定了模拟结果的可靠性与稳定性。如果随机数生成器存在周期性过短、统计偏差或种子设置不当等问题,即使模型构建得再精巧,输出结果也可能产生系统性偏差。本章将系统讲解伪随机数生成器(PRNG)的工作原理、种子设置的最佳实践,以及从均匀分布出发构造常见概率分布的抽样方法,帮助你在风险管理中建立扎实的随机数基础。

随机数生成与分布抽样知识框架 种子设置 固定种子 → 可复现实验 随机种子 → 独立运行 推荐:系统时间/硬件熵 伪随机数生成器 MT19937(梅森旋转) 周期 2^19937-1 PCG / xoshiro256++ U(0,1) 均匀分布 所有抽样方法的基础 质量检验:KS检验、 自相关检验、频谱分析 逆变换法 X = F⁻¹(U) 适用:CDF有解析逆函数 指数、威布尔、柯西 Box-Muller 变换 Z₀ = √(-2lnU₁)·cos(2πU₂) Z₁ = √(-2lnU₁)·sin(2πU₂) 生成标准正态分布 其他方法 接受-拒绝法、Ziggurat 组合法、卷积法 泊松、二项、t分布等 风险管理应用 VaR / CVaR 计算 压力测试场景生成 资产价格路径模拟 核心原则:均匀分布 → 逆变换/变换 → 目标分布 → 风险指标

4.1 伪随机数生成器(PRNG)的核心原理

真正的随机数来源于物理过程(如放射性衰变、热噪声),但在计算机模拟中,我们使用的是伪随机数生成器——一种通过确定性算法产生看似随机序列的程序。PRNG 并非真正随机,而是从一个初始状态(种子)出发,通过数学递推公式生成序列。只要种子相同,生成的序列就完全一致,这为实验的可复现性提供了基础。

在风险管理中,最常用的 PRNG 是 梅森旋转算法(MT19937),其周期长达 219937-1,远超过任何实际模拟所需的样本量。Python 的 random 模块和 NumPy 的默认生成器均基于此算法。近年来,PCG(Permuted Congruential Generator) 和 xoshiro256++ 等新一代生成器因其更快的速度和更好的统计特性,也逐渐被广泛采用。

生成器 周期 速度 统计质量 适用场景
MT19937 219937-1 中等 优秀(通过 TestU01) 通用模拟、金融建模
PCG32 264 快 优秀 大规模并行模拟
xoshiro256++ 2256-1 极快 良好 高性能计算、GPU模拟
线性同余(LCG) 231-1 快 较差(低维偏差) 不推荐用于风险管理
风险提示:切勿使用 Excel 的 RAND() 或 C 语言旧版 rand() 进行蒙特卡洛模拟。这些生成器的周期短(通常不超过 231),且在高维抽样时会出现明显的相关性,可能导致 VaR 估计偏差高达 10%-20%。

4.2 种子设置:可复现性与独立性的平衡

种子(Seed)是 PRNG 的初始状态。在风险管理实践中,种子设置需要根据场景灵活选择:

  • 固定种子(如 seed=42):用于模型验证、回归测试和教学演示。固定种子确保每次运行结果一致,便于调试和审计。
  • 随机种子(基于系统时间或硬件熵):用于生产环境中的独立模拟。每次运行产生不同结果,反映模拟的随机性本质。
  • 并行种子(每个进程/线程独立种子):在分布式计算中,为每个工作节点分配不同的种子,避免子序列重叠。

在 Python 中,推荐使用 NumPy 的 Generator 接口(而非旧的 RandomState),因为它支持更现代的算法和更好的并行特性:

import numpy as np

# 固定种子(可复现)
rng = np.random.default_rng(seed=42)
samples = rng.normal(0, 1, 10000)

# 随机种子(基于系统熵)
rng = np.random.default_rng()
samples = rng.normal(0, 1, 10000)

# 并行模拟:为每个任务分配独立种子
seeds = np.random.SeedSequence(12345).spawn(8)
for i, seed in enumerate(seeds):
    rng_i = np.random.default_rng(seed)
    # 每个 rng_i 生成独立的随机序列
最佳实践:在风险管理项目中,建议将种子作为配置参数写入实验记录。例如,在回测报告中注明“使用 seed=20240501 生成 100 万条路径”,这样任何审计人员都可以复现你的结果。

4.3 逆变换法(Inverse Transform Method)

逆变换法是离散和连续分布抽样的基础方法。其核心思想是:如果随机变量 X 的累积分布函数(CDF)为 F(x),且 F 存在逆函数 F-1,则当 U ~ U(0,1) 时,X = F-1(U) 服从 F 所对应的分布。

操作流程:

  1. 生成均匀随机数 U ~ U(0,1);
  2. 计算目标分布的逆 CDF:X = F-1(U);
  3. 输出 X 作为目标分布的一个样本。

以指数分布为例,其 CDF 为 F(x) = 1 - e-λx,求逆得到 F-1(u) = -ln(1-u)/λ。由于 1-U 与 U 同分布,可简化为 X = -ln(U)/λ。在 Python 中:

import numpy as np

def exponential_sample(lambda_, n, rng):
    """逆变换法生成指数分布样本"""
    u = rng.random(n)
    return -np.log(u) / lambda_

# 生成 λ=0.5 的指数分布样本
rng = np.random.default_rng(42)
samples = exponential_sample(0.5, 100000, rng)
print(f"均值: {samples.mean():.4f} (理论值: 2.0)")
print(f"标准差: {samples.std():.4f} (理论值: 2.0)")

逆变换法的优势在于单调性保持——排序后的样本仍然保持排序,这在生成相关随机变量(如 Copula 模型)时非常有用。但其局限是:并非所有分布的 CDF 都有解析逆函数。例如,正态分布的 CDF 无法用初等函数表示,因此需要其他方法。

4.4 Box-Muller 变换与正态分布抽样

Box-Muller 变换是生成标准正态分布样本的经典方法。其原理是:若 U₁, U₂ 独立且均服从 U(0,1),则:

Z₀ = √(-2·ln U₁) · cos(2πU₂)
Z₁ = √(-2·ln U₁) · sin(2πU₂)

Z₀ 和 Z₁ 相互独立且均服从标准正态分布 N(0,1)。这一变换的几何意义是:将单位圆内的均匀点通过极坐标映射到二维正态分布。

在实际应用中,更推荐使用 Ziggurat 算法(NumPy 默认采用),它比 Box-Muller 快 2-3 倍,且数值稳定性更好。但 Box-Muller 在教学和理解上仍有重要价值:

import numpy as np

def box_muller(n, rng):
    """Box-Muller 生成标准正态样本"""
    u1 = rng.random(n)
    u2 = rng.random(n)
    z0 = np.sqrt(-2 * np.log(u1)) * np.cos(2 * np.pi * u2)
    z1 = np.sqrt(-2 * np.log(u1)) * np.sin(2 * np.pi * u2)
    return z0, z1

rng = np.random.default_rng(42)
z0, z1 = box_muller(50000, rng)
# 验证统计性质
print(f"Z0 均值: {z0.mean():.4f}, 标准差: {z0.std():.4f}")
print(f"Z1 均值: {z1.mean():.4f}, 标准差: {z1.std():.4f}")

得到标准正态样本后,通过线性变换 X = μ + σZ 即可得到任意正态分布 N(μ, σ²) 的样本。在风险管理中,正态分布常用于资产收益率建模,但需注意金融收益率常具有厚尾特征,此时可考虑 t 分布或混合正态分布。

4.5 常见分布的抽样方法对比

分布 推荐方法 关键公式/步骤 注意事项
均匀分布 U(a,b) 直接缩放 X = a + (b-a)·U 最基础,注意边界
指数分布 Exp(λ) 逆变换法 X = -ln(U)/λ λ 为速率参数
正态分布 N(μ,σ²) Box-Muller / Ziggurat Z ~ N(0,1), X = μ+σZ 避免用逆变换(无解析式)
对数正态分布 指数变换 X = eY, Y ~ N(μ,σ²) 用于资产价格建模
泊松分布 Poi(λ) Knuth 算法 / 逆变换 累乘直到 e-λ 被超过 λ 较大时用正态近似
t 分布 正态/卡方比值 T = Z / √(V/ν) ν 为自由度,厚尾
威布尔分布 逆变换法 X = λ·(-ln U)1/k 用于极端事件建模

4.6 案例拆解:用随机数生成验证 VaR 模型

背景:某投资组合包含两只股票,日收益率分别服从 N(0.02%, 1.5%) 和 N(0.01%, 2.0%),相关系数为 0.3。风险团队需要计算 95% 置信水平下的日 VaR,并验证不同随机数种子对结果的影响。

初始现象:使用固定种子 seed=42 时,VaR 估计为 3.82%;改用随机种子后,多次运行结果在 3.65%-4.10% 之间波动,团队担心结果不稳定。

分析过程:

  1. 首先确认样本量是否充足。使用 10 万条路径时,蒙特卡洛标准误约为 σ/√N ≈ 2.5%/316 ≈ 0.008%,远小于观察到的波动范围。
  2. 检查相关性是否被正确引入。使用 Cholesky 分解生成相关正态变量:
import numpy as np

# 协方差矩阵
corr = np.array([[1.0, 0.3], [0.3, 1.0]])
sigma = np.array([0.015, 0.02])
cov = np.outer(sigma, sigma) * corr

# Cholesky 分解
L = np.linalg.cholesky(cov)

# 生成相关正态样本
rng = np.random.default_rng(42)
Z = rng.normal(0, 1, (100000, 2))
X = Z @ L.T  # 相关收益率
portfolio_returns = 0.6 * X[:, 0] + 0.4 * X[:, 1]

# 计算 VaR
var_95 = np.percentile(portfolio_returns, 5)
print(f"95% VaR: {-var_95*100:.2f}%")
  1. 对比不同种子下的结果分布:
var_estimates = []
for seed in range(20):
    rng = np.random.default_rng(seed)
    Z = rng.normal(0, 1, (100000, 2))
    X = Z @ L.T
    portfolio_returns = 0.6 * X[:, 0] + 0.4 * X[:, 1]
    var_estimates.append(-np.percentile(portfolio_returns, 5) * 100)

print(f"VaR 均值: {np.mean(var_estimates):.2f}%")
print(f"VaR 标准差: {np.std(var_estimates):.3f}%")
print(f"VaR 范围: [{np.min(var_estimates):.2f}%, {np.max(var_estimates):.2f}%]")

结果对比:20 个不同种子下,VaR 估计的均值为 3.85%,标准差为 0.012%。这说明当样本量足够大时,种子选择对结果的影响可以忽略(<0.5% 的相对偏差)。之前观察到的 3.65%-4.10% 波动,很可能是因为使用了过小的样本量(如 1000 条路径)。

复盘经验:

  • 在蒙特卡洛模拟中,样本量比种子选择更重要。建议至少使用 10 万条路径,并检查收敛性。
  • 固定种子用于调试和审计,随机种子用于生产环境。
  • 当结果对种子敏感时,应怀疑模型本身存在问题(如相关性矩阵不正定、分布假设不合理)。
关键结论:随机数生成是蒙特卡洛模拟的“地基”。选择高质量的 PRNG(如 MT19937)、合理设置种子(固定/随机/并行)、并针对目标分布选择高效的抽样方法(逆变换、Box-Muller、Ziggurat),是确保风险管理模拟结果可靠的三步基石。在实际项目中,务必进行种子敏感性分析和收敛性检验,避免因随机数质量问题导致错误的风险决策。