随机数生成:伪随机数原理、Python中的random库、numpy.random模块
做Monte Carlo模拟,说白了就是跟随机数打交道。你想想看,我们要模拟成千上万条资产价格路径,每一步都得靠随机数来驱动。所以随机数生成这件事,是整个模拟的基石。
我个人习惯把随机数生成比作「炒菜的盐」——用量不大,但少了它整道菜就废了。今天我们就来聊聊这个「盐」到底是怎么来的。
伪随机数原理:计算机真的能「随机」吗?
先问个问题:计算机能生成真正的随机数吗?
答案是:不能。至少普通计算机不行。
计算机是确定性的机器。给它同样的输入,它永远给你同样的输出。所以它没法「凭空」产生随机数。那怎么办?
嗯,这里有个巧妙的方法——用算法模拟随机性。这就是伪随机数生成器(PRNG)的核心思想。
最常见的算法叫线性同余生成器(LCG)。公式很简单:
X_{n+1} = (a * X_n + c) mod m
其中:
X_n是当前随机数a是乘数c是增量m是模数
说白了,就是拿上一个数做一顿数学运算,然后取余数。这个余数看起来就像随机数。
关键点:伪随机数不是真随机,但它的统计性质(分布、相关性)可以做到非常接近真随机。对于金融模拟来说,够用了。
我在项目中遇到过一个问题:用默认的随机数生成器跑100万次模拟,结果发现某些极端场景出现的频率异常高。排查了半天,原来是随机数生成器的周期太短,导致序列重复了。所以选对生成器很重要。
Python中的random库:入门级选手
Python自带的random库,用的是梅森旋转算法(Mersenne Twister)。这个算法周期长达2^19937-1,质量相当不错。
常用的函数就这几个:
| 函数 | 说明 | 示例 |
|---|---|---|
random.random() |
返回[0,1)的浮点数 | 0.3745 |
random.uniform(a, b) |
返回[a,b]的均匀分布 | 3.1415 |
random.gauss(mu, sigma) |
返回正态分布 | 0.0234 |
random.seed(n) |
设置随机种子 | random.seed(42) |
举个例子,模拟一次标准正态随机数:
import random
# 设置种子,保证结果可复现
random.seed(42)
# 生成10个标准正态随机数
for _ in range(10):
z = random.gauss(0, 1)
print(f"{z:.4f}")
我的习惯:每次做模拟前,一定先设种子。不然第二天跑同样的代码,结果不一样,你都不知道是代码改了还是随机性在作怪。
但说实话,random库有个缺点——慢。你要生成几百万个随机数,它就不太行了。而且它不支持向量化操作,一次只能生成一个数。
numpy.random模块:专业选手的武器
做金融模拟,我几乎只用numpy.random。为什么?
- 速度快:底层用C实现,批量生成效率极高
- 向量化:一次生成整个数组,配合numpy的广播机制
- 分布全:正态、对数正态、泊松、卡方……应有尽有
来看看怎么用:
import numpy as np
# 设置种子
np.random.seed(42)
# 生成10000个标准正态随机数
z = np.random.standard_normal(10000)
# 生成10000个均值为0.05,标准差为0.2的正态随机数
returns = np.random.normal(0.05, 0.2, 10000)
# 生成1000x252的随机矩阵(模拟1000条路径,每条252天)
paths = np.random.standard_normal((1000, 252))
你看,一行代码就能生成上万个随机数。这在Monte Carlo模拟里太关键了。
注意:numpy 1.17版本之后,推荐使用新的随机数API:np.random.default_rng()。旧的np.random.seed()虽然还能用,但新API在统计性质上更优。
新API的用法:
from numpy.random import default_rng
# 创建生成器
rng = default_rng(42)
# 生成随机数
z = rng.standard_normal(10000)
uniform = rng.uniform(0, 1, 5000)
我曾经踩过一个坑:用旧API在多线程环境下生成随机数,结果不同线程拿到了相同的序列。换成新API的default_rng后,每个线程独立生成器,问题就解决了。
知识体系一览
下面这张图,帮你理清随机数生成的核心脉络:
选哪个?我的建议
简单总结一下:
- 小规模测试(几百个随机数):用
random库就行,简单直接 - 大规模模拟(几万到几百万):必须用
numpy.random,性能差距是数量级的 - 需要可复现:永远记得设种子,
np.random.seed(42)或default_rng(42) - 多线程/多进程:用新API的
default_rng,每个线程独立生成器
核心原则:随机数生成器的质量,直接决定Monte Carlo模拟的可靠性。别在这个环节偷懒。
好了,随机数这块就聊到这儿。记住一句话:伪随机数不是真随机,但用对了,它就是真随机的完美替身。