06_randomMonteCarlo.py - 随机数生成与蒙特卡洛模拟
学习路径第 6 步 (共 10 步) | 难度:中级
概述
从随机数生成基础出发,逐步深入到蒙特卡洛方法的实际应用:圆周率估算、定积分计算、函数优化、排队模拟。
学习目标
- 掌握
default_rng()现代随机数 API (对比旧版np.random) - 了解常用概率分布:均匀分布、正态分布、二项分布、泊松分布等
- 理解蒙特卡洛方法的核心思想(大数定律)
- 能用蒙特卡洛方法解决实际问题
核心内容 (7 个模块)
| 模块 | 核心知识点 |
|---|---|
| 1. Generator 新旧接口对比 | 现代写法 vs 传统写法、种子复现 |
| 2. 常见概率分布 | 均匀分布、正态、二项、泊松、指数分布 |
| 3. 排列与选择 | shuffle / permutation / 加权随机选取 |
| 4. 蒙特卡洛基本原理 | 大数定律、收敛过程可视化 |
| 5. 圆周率估算 | 经典投针法/飞镖法估算 pi 值 |
| 6. 积分与优化 | 蒙特卡洛积分计算、模拟退火优化 |
| 7. 排队模拟 | M/M/1 排队模型的随机到达模拟 |
CODE
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
=====================================
NumPy 随机数生成与蒙特卡洛模拟 (Random & Monte Carlo)
=====================================
本案例系统介绍 NumPy 随机数体系与蒙特卡洛方法:
1. Random Generator 新接口 (推荐)
2. 各种概率分布的采样
3. 随机排列、选择与洗牌
4. 蒙特卡洛核心思想
5. 实战案例: 圆周率估算 / 积分计算 / 模拟优化
【蒙特卡洛方法简介】
通过大量随机抽样来近似求解数学问题。
核心思想: 大数定律 --- 当试验次数趋近无穷时,
事件频率收敛于其概率。
作者:bloxed
"""
import numpy as np
def separator(title):
print(f"\n{'='*60}")
print(f" {title}")
print('='*60)
# ============================================================
# 第一部分:随机数生成体系
# ============================================================
separator("一、随机数生成器 (新旧接口)")
print("""
【重要】NumPy 推荐使用新的 Generator API (v1.17+)
旧方式 (仍可用, 但不推荐新项目):
np.random.seed(42)
np.random.rand(5)
新方式 (推荐 ★★★):
rng = np.random.default_rng(42)
rng.random(5)
[新接口优势]
- 统计性质更好 (PCG64 算法替代 MT19937)
- 更快的速度
- 支持并行独立流 (distributed computing)
""")
# 创建随机数生成器实例
rng = np.random.default_rng(seed=42)
print("\n--- 基础随机数 ---")
uniform_vals = rng.random(5) # [0,1) 均匀分布
int_vals = rng.integers(0, 100, 8) # [low, high) 整数
float_range = rng.uniform(10, 20, 6)
print(f" uniform [0,1): {np.round(uniform_vals, 3)}")
print(f" integers [0,100): {int_vals}")
print(f" uniform [10,20]: {np.round(float_range, 2)}")
# ============================================================
# 第二部分:常用概率分布
# ============================================================
separator("二、常用概率分布采样")
print("""
┌───────────────┬──────────────────┬─────────────────────────┐
│ 分布 │ 函数 │ 典型应用场景 │
├───────────────┼──────────────────┼─────────────────────────┤
│ 均匀分布 │ uniform() │ 随机采样、初始化 │
│ 正态分布 │ normal() │ 自然现象建模、噪声 │
│ 二项分布 │ binomial() │ 成功/失败次数 │
│ 泊松分布 │ poisson() │ 单位时间事件计数 │
│ 指数分布 │ exponential() │ 等待时间模型 │
│ Beta 分布 │ beta() │ 概率参数的不确定性 │
│ 多元正态 │ multivariate_normal() | 相关变量联合分布 │
└───────────────┴──────────────────┴─────────────────────────┘
""")
n = 10000
# 正态分布
normal_data = rng.normal(loc=100, scale=15, size=n)
print(f"正态分布 N(100,15), n={n}:")
print(f" 均值 ≈ {normal_data.mean():.1f} (理论值 100)")
print(f" 标准差 ≈ {normal_data.std():.1f} (理论值 15)")
print(f" 范围 [{normal_data.min():.1f}, {normal_data.max():.1f}]")
# 二项分布: 抛10次硬币, 正面朝上的次数 (重复10000次)
binom_data = rng.binomial(n=10, p=0.5, size=n)
print(f"\n二项分布 B(n=10,p=0.5), n={n}次实验:")
unique, counts = np.unique(binom_data, return_counts=True)
for u, c in zip(unique, counts):
bar = '#' * int(c / n * 50)
print(f" k={u:<3} 频率={c/n*100:>5.1f}% {bar}")
# 泊松分布: 每小时平均来3个顾客, 观察10000小时
poisson_data = rng.poisson(lam=3.0, size=n)
print(f"\n泊松分布 Pois(lambda=3):")
print(f" 均值 ≈ {poisson_data.mean():.2f} (理论值 3.0)")
print(f" 方差 ≈ {poisson_data.var():.2f} (泊松分布 均值≈方差)")
# 指数分布: 平均等待时间=2秒
exp_data = rng.exponential(scale=2.0, size=n)
print(f"\n指数分布 Exp(scale=2):")
print(f" 均值 ≈ {exp_data.mean():.2f} (理论值 2.0)")
# ============================================================
# 第三部分:随机排列与选择
# ============================================================
separator("三、随机操作: 排列/选择/无放回抽样")
students = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve',
'Frank', 'Grace', 'Henry']
print(f"原始列表 ({len(students)} 人): {students}")
# 随机打乱 (返回副本, 不修改原数组)
shuffled = rng.permutation(students)
print(f"\n1. permutation (打乱): {shuffled}")
# 随机选取 (可重复)
chosen = rng.choice(students, size=3, replace=True)
print(f"2. choice 有放回选3人: {chosen}")
chosen_no_replace = rng.choice(students, size=3, replace=False)
print(f"3. choice 无放回选3人: {chosen_no_replace}")
# 加权随机选择 (概率不同)
weights = [0.3, 0.2, 0.1, 0.15, 0.05, 0.05, 0.1, 0.05]
weighted_chosen = rng.choice(students, size=10, p=np.array(weights)/sum(weights))
from collections import Counter
counts = Counter(weighted_chosen)
print(f"\n4. 加权选择 (Alice权重最高=30%):")
for name, cnt in counts.items():
idx = students.index(name)
print(f" {name}: 出现{cnt}次 (原始权重={weights[idx]})")
# ============================================================
# 第四部分:蒙特卡洛案例一 ------ 估算圆周率 π
# ============================================================
separator("四、MC 案例 1: 估算圆周率 π")
print("""
【原理】在单位正方形 [0,1]x[0,1] 内随机投点:
正方形面积 = 1 x 1 = 1
四分之一圆面积 = π * r² / 4 = π/4 (r=1)
若投了 N 个点, M 个落在圆内:
M/N ≈ (π/4) / 1 = π/4
所以 π ≈ 4M/N
落入圆内的条件: x^2 + y^2 <= 1
""")
def estimate_pi(n_samples):
"""用蒙特卡洛法估算 π"""
points = rng.uniform(0, 1, size=(n_samples, 2))
distances_squared = points[:, 0]**2 + points[:, 1]**2
inside_circle = distances_squared <= 1
pi_estimate = 4 * inside_circle.sum() / n_samples
return pi_estimate, inside_circle
# 不同样本量下的精度变化
sample_sizes = [100, 1000, 10000, 100000, 1000000]
print(f"{'样本量':>12} {'π 估算值':>12} {'误差':>12} {'圆内占比':>10}")
print("-" * 48)
pi_final, inside_final = None, None
for n in sample_sizes:
pi_est, inside = estimate_pi(n)
error = abs(pi_est - np.pi)
ratio = inside.sum() / n
print(f"{n:>12,} {pi_est:>12.6f} {error:>12.6f} {ratio:>9.4f}")
pi_final, inside_final = pi_est, inside
print(f"\n真实 π 值: {np.pi:.6f}")
print(f"最佳估算: {pi_final:.6f}")
# ============================================================
# 第五部分:蒙特卡洛案例二 ------ 数值积分
# ============================================================
separator("五、MC 案例 2: 数值定积分")
print("""
【任务】计算 ∫₀¹ sin(x) dx 的值
解析解: [-cos(x)]₀¹ = 1 - cos(1) ≈ 0.4597
【MC 方法】
在 [0,1] 区间均匀取 N 个随机点 xi
计算 f(xi) = sin(xi) 的平均值
积分值 ≈ (b-a) × mean(f(x)) = 1 × mean(sin(x))
""")
n_integral = 500_000
x_samples = rng.uniform(0, 1, size=n_integral)
y_values = np.sin(x_samples)
integral_mc = y_values.mean() * (1 - 0) # 区间长度=1
analytical = 1 - np.cos(1)
error = abs(integral_mc - analytical)
print(f" 样本量: {n_integral:,}")
print(f" MC 结果: {integral_mc:.6f}")
print(f" 解析解: {analytical:.6f}")
print(f" 绝对误差: {error:.6f}")
print(f" 相对误差: {error/analytical*100:.3f}%")
# 可视化展示 (文字版直方图)
print(f"\n 函数值分布:")
hist, edges = np.histogram(y_values, bins=20)
max_h = max(hist)
for i in range(len(hist)):
lo, hi = edges[i], edges[i+1]
bar_len = int(hist[i] / max_h * 40)
print(f" [{lo:.3f}, {hi:.3f}): {'#' * bar_len} ({hist[i]})")
# ============================================================
# 第六部分:蒙特卡洛案例三 ------ 优化问题
# ============================================================
separator("六、MC 案例 3: 随机搜索优化 (Rastrigin函数)")
print("""
【任务】寻找 Rastrigin 函数的全局最小值 (已知在原点处为0)
f(x,y) = A*n + Σ[xi² - A*cos(2πxi)]
其中 A=10, n=2 (二维)
这是一个多峰优化问题,有很多局部极小值,
梯度下降容易陷入局部最优。
MC随机搜索可以较好地探索全局!
""")
def rastrigin(x, y, A=10):
"""Rastrigin 测试函数"""
return A * 2 + (x**2 - A * np.cos(2*np.pi*x)) + (y**2 - A * np.cos(2*np.pi*y))
# 随机搜索
n_search = 200_000
search_x = rng.uniform(-5.12, 5.12, size=n_search)
search_y = rng.uniform(-5.12, 5.12, size=n_search)
z_values = rastrigin(search_x, search_y)
best_idx = np.argmin(z_values)
best_x, best_y = search_x[best_idx], search_y[best_y]
best_z = z_values[best_idx]
print(f" 搜索空间: [-5.12, 5.12] x 2维")
print(f" 随机采样点: {n_search:,}")
print(f"\n 找到的最优解:")
print(f" x = {best_x:.6f}")
print(f" y = {best_y:.6f}")
print(f" f(x,y) = {best_z:.6f}")
print(f" 全局最优: f(0,0) = 0")
print(f" 误差: {abs(best_z):.6f}")
# 分析搜索质量
thresholds = [0.01, 0.1, 1.0, 5.0]
print(f"\n 解的质量分布:")
for thresh in thresholds:
count = np.sum(z_values < thresh)
pct = count / n_search * 100
print(f" f(x,y) < {thresh:<5} : {count:>6,} 点 ({pct:>5.2f}%)")
# ============================================================
# 第七部分:蒙特卡洛案例四 ------ 模拟排队系统
# ============================================================
separator("七、MC 案例 4: 模拟排队系统 (离散事件模拟)")
print("""
【场景】单服务台排队系统 (M/M/1队列简化版)
参数:
- 到达间隔: 服从指数分布 (平均2分钟一个顾客)
- 服务时间: 服从指数分布 (平均1.5分钟服务一个顾客)
目标: 估计平均等待时间和队列长度
""")
n_customers = 5000
arrival_intervals = rng.exponential(scale=2.0, size=n_customers) # 到达间隔
service_times = rng.exponential(scale=1.5, size=n_customers) # 服务时间
# 模拟过程
arrival_times = np.cumsum(arrival_intervals) # 每个顾客到达时刻
departure_times = np.zeros(n_customers) # 每个顾客离开时刻
wait_times = np.zeros(n_customers) # 等待时间
for i in range(n_customers):
if i == 0:
departure_times[i] = arrival_times[i] + service_times[i]
wait_times[i] = 0
else:
# 如果前一个人还没走完,需要等待
start_service = max(arrival_times[i], departure_times[i-1])
wait_times[i] = max(0, start_service - arrival_times[i])
departure_times[i] = start_service + service_times[i]
print(f" 模拟顾客总数: {n_customers:,}")
print(f" 总运行时间: {arrival_times[-1]:.1f} 分钟")
print(f"\n --- 统计结果 ---")
print(f" 平均等待时间: {wait_times.mean():.2f} 分钟")
print(f" 最大等待时间: {wait_times.max():.2f} 分钟")
print(f" 无需等待比例: {(wait_times == 0).mean()*100:.1f}%")
print(f" 等待 > 5分钟比例: {(wait_times > 5).mean()*100:.1f}%")
print(f" 系统利用率: {service_times.mean()/arrival_intervals.mean()*100:.1f}%")
# ============================================================
# 总结
# ============================================================
separator("总结: 随机数 & 蒙特卡洛速查")
summary = r"""
+------------------------------------------------------------+
| NumPy Random & Monte Carlo |
+------------------------------------------------------------+
| |
| [创建Generator] |
| rng = np.random.default_rng(seed) |
| |
| [基础随机] |
| rng.random(size) → [0,1) 均匀浮点 |
| rng.integers(lo,hi) → [lo,hi) 均匀整数 |
| rng.uniform(a,b) → [a,b) 均匀浮点 |
| |
| [概率分布] |
| rng.normal(mu,sigma) → 正态分布 |
| rng.binomial(n,p) → 二项分布 |
| rng.poisson(lam) → 泊松分布 |
| rng.exponential(scale)→ 指数分布 |
| |
| [随机操作] |
| rng.choice(pop,size) → 随机选取 |
| rng.shuffle(arr) → 原地打乱 |
| rng.permutation(arr) → 返回打乱副本 |
| |
| [Monte Carlo 三步曲] |
| 1. 在定义域内大量随机采样 |
| 2. 计算每个样本的目标函数值 |
| 3. 用统计均值近似期望/积分/概率 |
| |
| 收敛速度: O(1/sqrt(N)), 要精度提高10倍需100倍采样量 |
+------------------------------------------------------------+
"""
print(summary)
print("\n运行完毕!")