第三章:随机数与概率分布——伪随机数生成原理、均匀分布、指数分布、正态分布、泊松分布及其在仿真中的应用
各位好,我是老张。今天咱们聊聊仿真里最基础也最要命的东西——随机数。
你可能会想,随机数有什么好讲的?不就是rand()一下嘛。嗯,我当年也是这么想的。直到有一次,我用同一个种子跑了十次仿真,结果每次结果都一模一样,我还以为是系统出bug了。后来才明白,计算机压根儿就不会产生真正的随机数。
3.1 伪随机数生成原理
说白了,计算机里的随机数都是「假装」随机的。它靠一个确定的算法,从一个初始值(种子)出发,算出一串看起来杂乱无章的数字。
最常见的算法叫线性同余生成器(LCG)。公式很简单:
X_{n+1} = (a * X_n + c) mod m
其中:
X_n是当前值a是乘数c是增量m是模数
举个例子,我常用的参数是 a=1664525, c=1013904223, m=2^32。这套参数在不少工业软件里都在用。
关键点:种子相同,随机数序列就相同。这在调试时是好事——你可以复现问题。但在正式仿真时,一定要用不同的种子,否则结果没有统计意义。
我的习惯:每次仿真前,用当前时间戳做种子。这样每次跑都不一样。但要注意,如果两次仿真在同一毫秒启动,种子可能撞车。我一般会加一个随机偏移量。
3.2 均匀分布
均匀分布是所有随机数的「母体」。其他分布都要靠它来转换。
在 [0,1) 区间上的均匀分布,记作 U(0,1)。它的概率密度函数是一条水平线:
f(x) = 1, 0 ≤ x < 1
在Python里生成很简单:
import random
# 生成一个 [0,1) 的随机数
x = random.random()
# 生成 [a,b) 的均匀分布
def uniform(a, b):
return a + (b - a) * random.random()
我在项目中遇到过一个问题:用random.random()生成大量随机数时,性能会下降。后来改用NumPy的numpy.random.rand(),速度提升了近10倍。嗯,大数据量时一定要用向量化操作。
3.3 指数分布
指数分布用来描述「事件发生的时间间隔」。比如:
- 顾客到达收银台的间隔时间
- 机器故障的间隔时间
- 网络数据包的到达间隔
它的概率密度函数:
f(x) = λ * e^(-λx), x ≥ 0
其中 λ 是速率参数。λ 越大,事件发生越频繁,间隔时间越短。
生成指数分布的方法叫逆变换法:
import random
import math
def exponential(lambda_param):
u = random.random()
return -math.log(1 - u) / lambda_param
避坑指南:我曾经直接用 -math.log(u) / lambda,结果发现当 u=0 时,log(0) 会报错。虽然概率极低,但在仿真跑几百万次时,一定会撞上。所以一定要用 1 - u 来保证 u 永远不等于 0。
3.4 正态分布
正态分布,也叫高斯分布,是自然界最常见的分布。它的概率密度函数像个钟形:
f(x) = (1 / (σ * sqrt(2π))) * exp(-(x-μ)^2 / (2σ^2))
生成正态分布最经典的方法是Box-Muller变换:
import random
import math
def normal(mean=0, std=1):
u1 = random.random()
u2 = random.random()
z0 = math.sqrt(-2 * math.log(u1)) * math.cos(2 * math.pi * u2)
return mean + z0 * std
你想想看,为什么用两个均匀分布就能生成正态分布?其实原理不复杂:把两个独立的正态分布画在二维平面上,它们的联合分布是旋转对称的。用极坐标一转换,就得到了上面的公式。
我个人习惯用NumPy的numpy.random.normal(),它底层用的是更高效的Ziggurat算法,比Box-Muller快不少。
3.5 泊松分布
泊松分布描述「在固定时间或空间内,事件发生的次数」。比如:
- 一小时内某网站收到的访问请求数
- 一平方米布匹上的瑕疵点数
- 一天内某路口发生的交通事故数
它的概率质量函数:
P(X = k) = (λ^k * e^(-λ)) / k!
生成泊松分布的方法:
import random
import math
def poisson(lambda_param):
L = math.exp(-lambda_param)
k = 0
p = 1
while p > L:
k += 1
p *= random.random()
return k - 1
注意:当 λ 很大时(比如 λ > 20),这个方法的循环次数会很多,性能很差。我建议用拒绝采样法或直接查表法。NumPy的 numpy.random.poisson() 已经处理好了这些边界情况。
3.6 知识体系总览
下面这张图总结了本章的核心逻辑:
3.7 仿真中的应用场景
说了这么多理论,到底怎么用?我举几个实际例子:
| 分布类型 | 仿真场景 | 参数设置 |
|---|---|---|
| 均匀分布 | 随机生成零件尺寸偏差 | U(-0.1, 0.1) mm |
| 指数分布 | 客户到达时间间隔 | λ = 0.5 人/分钟 |
| 正态分布 | 加工时间波动 | μ = 10 min, σ = 2 min |
| 泊松分布 | 每小时故障次数 | λ = 3 次/小时 |
我的经验:刚开始做仿真时,我总喜欢用正态分布描述一切。后来发现,很多实际数据其实更符合指数分布或泊松分布。比如设备故障间隔时间,用指数分布拟合的效果比正态分布好得多。所以,一定要先看看真实数据的分布形态,别想当然。
好了,这一章的内容就到这里。随机数这东西,看似简单,但用不好会让整个仿真结果失真。记住一句话:随机数生成是仿真的基石,基石不稳,楼盖得再高也是白搭。