第三章:随机数与概率分布——伪随机数生成原理、均匀分布、指数分布、正态分布、泊松分布及其在仿真中的应用

各位好,我是老张。今天咱们聊聊仿真里最基础也最要命的东西——随机数。

你可能会想,随机数有什么好讲的?不就是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 知识体系总览

下面这张图总结了本章的核心逻辑:

随机数与概率分布知识体系 伪随机数生成 均匀分布 U(0,1) 指数分布 逆变换法 事件间隔时间 正态分布 Box-Muller / Ziggurat 测量误差、自然现象 泊松分布 事件计数法 固定时间内事件次数 仿真应用:排队系统、库存管理、可靠性分析

3.7 仿真中的应用场景

说了这么多理论,到底怎么用?我举几个实际例子:

分布类型 仿真场景 参数设置
均匀分布 随机生成零件尺寸偏差 U(-0.1, 0.1) mm
指数分布 客户到达时间间隔 λ = 0.5 人/分钟
正态分布 加工时间波动 μ = 10 min, σ = 2 min
泊松分布 每小时故障次数 λ = 3 次/小时

我的经验:刚开始做仿真时,我总喜欢用正态分布描述一切。后来发现,很多实际数据其实更符合指数分布或泊松分布。比如设备故障间隔时间,用指数分布拟合的效果比正态分布好得多。所以,一定要先看看真实数据的分布形态,别想当然。

好了,这一章的内容就到这里。随机数这东西,看似简单,但用不好会让整个仿真结果失真。记住一句话:随机数生成是仿真的基石,基石不稳,楼盖得再高也是白搭


专注资料整理