Skip to content

第 11 章 随机数与蒙特卡洛模拟

学习目标

  • 掌握 np.random.default_rng 创建随机数生成器
  • 理解种子(seed)与可复现性
  • 掌握 integersuniformnormalchoice
  • 会用 shuffle 打乱数组
  • 会用蒙特卡洛方法做数值估算

11.1 随机数生成器:default_rng

NumPy 2.x 推荐用 np.random.default_rng(种子) 创建一个随机数生成器对象(简称 rng),再通过它的方法生成随机数:

python
import numpy as np

rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=5))    # 5 个 1~6 的整数(掷骰子)

输出:

[1 5 4 3 3]

integers(最小, 最大, size=个数) 生成整数,包含最小值、不包含最大值——和 Python 的 range 规则一致。

size 可以是整数或元组,生成多维随机数组:

python
import numpy as np

rng = np.random.default_rng(42)
print(rng.integers(0, 2, size=(3, 4)))    # 3×4 的 0/1 矩阵

输出(以你实际运行为准,种子相同则结果相同):

[[1 1 0 0]
 [1 1 1 0]
 [1 1 1 1]]

11.2 种子:让随机可复现

种子(seed)是随机数生成器的初始状态。同一个种子生成完全相同的序列:

python
import numpy as np

r1 = np.random.default_rng(42)
r2 = np.random.default_rng(42)
print(r1.integers(1, 7, size=5))
print(r2.integers(1, 7, size=5))    # 与上一行完全相同

输出:

[1 5 4 3 3]
[1 5 4 3 3]

为什么要种子?

  • 调试:随机导致的 bug 难以复现;固定种子后每次运行一样,可以定位问题。
  • 实验可重复:别人的结果能复现,结果可验证。
  • 不给种子时,每次运行序列不同(真正的「随机」)。

本书所有随机示例都给出种子,方便你复现。

11.3 常用分布

均匀分布 uniform(最小, 最大, size):区间内每个值等可能。

正态分布 normal(均值, 标准差, size):钟形曲线,均值附近概率最高。

python
import numpy as np

rng = np.random.default_rng(7)
print(rng.uniform(0, 1, size=3))    # 0~1 均匀分布
print(rng.uniform(0, 1, size=3))    # 继续生成,序列不同

输出:

[0.62509547 0.8972138  0.77568569]
[0.22520719 0.30016628 0.87355345]

注意:同一个 rng 连续调用会依次产生不同随机数;只有「重新用同一个种子创建 rng」才会从头复现。

正态分布模拟人群身高(均值 170 cm、标准差 5 cm):

python
import numpy as np

rng = np.random.default_rng(3)
heights = rng.normal(170, 5, size=1000)
print("模拟平均身高:", round(heights.mean(), 2))
print("模拟标准差:", round(heights.std(), 2))

输出:

模拟平均身高: 170.19
模拟标准差: 5.03

1000 个样本的平均≈170、标准差≈5,接近设定参数;样本越多越接近——这正是「大数定律」的体现。

11.4 choice:按概率抽取

rng.choice(数组, size=个数) 从数组中随机抽取,replace=False 表示不重复抽取;p=概率列表 指定每个元素的概率:

python
import numpy as np

rng = np.random.default_rng(1)
items = np.array(["苹果", "香蕉", "橘子", "葡萄"])

print(rng.choice(items, size=3, replace=False))            # 不重复抽 3 个
print(rng.choice(items, size=6, p=[0.4, 0.3, 0.2, 0.1]))   # 按概率抽 6 次

输出:

['香蕉' '苹果' '葡萄']
['葡萄' '苹果' '香蕉' '橘子' '香蕉' '香蕉']

第二个例子中,苹果概率 0.4 最高,抽到最多;葡萄概率 0.1 最低。概率列表必须与数组等长且和为 1。

rng.shuffle(数组) 原地打乱顺序(返回 None):

python
import numpy as np

rng = np.random.default_rng(9)
deck = np.arange(1, 11)     # 10 张牌
rng.shuffle(deck)
print(deck)

输出:

[ 8  3  6 10  9  4  7  5  1  2]

11.5 蒙特卡洛方法:用随机模拟估算

蒙特卡洛方法(Monte Carlo method) 用大量随机样本来估计数值结果。经典例子:估算圆周率 π。

思路:在边长为 2 的正方形(范围 -1~1)内随机撒点,统计落在单位圆内的比例。圆面积/正方形面积 = π/4,所以 π ≈ 4 × 圆内点数/总点数:

python
import numpy as np

rng = np.random.default_rng(2026)
N = 100_000                        # 撒 10 万个点
x = rng.uniform(-1, 1, size=N)
y = rng.uniform(-1, 1, size=N)

inside = (x**2 + y**2 <= 1).sum()  # 布尔掩码计数
pi_est = 4 * inside / N
print("估算 π:", pi_est)

输出:

估算 π: 3.14404

真实 π≈3.14159,误差约 0.08%。点越多,估计越准——把 N 改成 100 万试试,误差会进一步缩小。

再举一例:模拟掷 1000 次骰子,验证「每个点数出现约 1/6」:

python
import numpy as np

rng = np.random.default_rng(5)
rolls = rng.integers(1, 7, size=1000)
print("平均点数:", rolls.mean())           # 理论上 3.5
print("掷出 6 的比例:", (rolls == 6).mean())   # 理论上 ≈0.167

输出:

平均点数: 3.451
掷出 6 的比例: 0.159

平均点数 3.451 接近理论值 3.5,6 的比例 0.159 接近 1/6≈0.167。

11.6 旧式随机接口(了解)

老教程常见 np.random.rand()np.random.randint()np.random.seed() 这套旧接口。NumPy 2.x 仍然兼容,但官方推荐用 default_rng(新版接口类型更安全、速度更快、可复现性更好)。遇到老代码能看懂即可,新代码用 default_rng

动手实践

  1. default_rng(100) 生成 10 个 1~100 的整数,再重新创建同一个种子的 rng,验证两次结果一致。
  2. normal(身高均值, 5) 模拟 10000 人的身高,计算平均与标准差,观察是否接近设定值。
  3. choice 按概率模拟「猜硬币」:硬币两面概率各 0.5,抛 10000 次,统计正面比例。
  4. 用蒙特卡洛方法估算 π,分别取 N=1000N=100000N=1000000,记录三次估计值,观察误差变化。

常见错误

错误写法现象原因
直接用 np.random.default_rng() 生成随机数TypeError: ... object is not callabledefault_rng 返回生成器对象,要用 rng.integers(...) 调用
期望「同一 rng 重新生成相同序列」每次调用结果不同复现需要重新用同一个种子创建 rng,不是继续调用
integers(1, 7) 想包含 7最大只到 6integers 不包含上界
choice(..., replace=False) 抽取个数超过元素数ValueError: Cannot take a larger sample than population不重复抽取的个数不能超过数组长度
p 概率之和不是 1ValueError: probabilities do not sum to 1概率列表必须归一化
rng.shuffle(a) 后打印返回值输出 Noneshuffle 原地修改,返回 None

章末练习

基础

  1. default_rng(1) 生成 5 个 1~10 的整数并打印;再用 default_rng(1) 重复一次,验证相同。
  2. 生成 100 个 0~1 均匀分布的数,计算平均值,解释为什么接近 0.5。
  3. np.array(["红", "绿", "蓝"]) 中不重复抽取 2 个,打印结果。

提高

  1. 模拟掷两枚骰子 10000 次,统计点数之和的平均值(理论上 7),并计算「和为 7」出现的比例。
  2. normal(100, 15, 100000) 模拟 IQ 分布,统计大于 130 的比例(理论上约 2.3%)。

挑战

  1. 用蒙特卡洛方法估算定积分 ∫₀¹ x² dx(理论值 1/3):在 [0,1]×[0,1] 正方形内撒点,统计落在曲线 y=x² 下方的比例,解释它与 π 例子的相似之处。
  2. 写一个函数 simulate_dice(seed, n),返回 n 次掷骰子的平均点数;用多个不同种子运行,观察结果围绕 3.5 波动的规律。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. 创建随机数生成器的推荐写法是?
    • A. np.random.rand()
    • B. np.random.default_rng(42)
    • C. np.random.seed(42)
    • D. rng = np.random()
  2. rng.integers(1, 7, size=3) 可能生成?
    • A. [7, 1, 3]
    • B. [1, 6, 5]
    • C. [0, 1, 2]
    • D. [1.5, 2.5]
  3. 种子(seed)的作用是?
    • A. 让随机序列更快
    • B. 让相同种子生成相同序列(可复现)
    • C. 让随机数更均匀
    • D. 没有作用
  4. 同一个 rng 连续调用两次 uniform,两次结果?
    • A. 一定相同
    • B. 通常不同
    • C. 一定不同
    • D. 报错
  5. rng.choice(items, size=3, replace=False)replace=False 表示?
    • A. 允许重复抽取
    • B. 不重复抽取
    • C. 按顺序抽取
    • D. 只抽一次
  6. rng.shuffle(a) 的返回值是?
    • A. 打乱后的数组
    • B. None
    • C. a 的副本
    • D. 布尔数组
  7. 蒙特卡洛估算 π 的正确比例关系是?
    • A. π = 4 × 圆内点数/总点数
    • B. π = 圆内点数/总点数
    • C. π = 总点数/圆内点数
    • D. π = 4 × 总点数/圆内点数
  8. 蒙特卡洛方法的估计误差随样本量增加?
    • A. 增大
    • B. 减小
    • C. 不变
    • D. 随机波动
  9. 模拟「掷骰子」应使用哪个方法?
    • A. rng.uniform(1, 6)
    • B. rng.integers(1, 7, size=n)
    • C. rng.normal(3.5, 1)
    • D. rng.shuffle
  10. 概率参数 p 的要求是?
    • A. 必须都是 0.5
    • B. 与数组等长且和为 1
    • C. 必须是整数
    • D. 与数组无关