用逆变换采样消除泊松过程模拟中的轮询
前言
今天早上醒来突然想思考一个问题:有很多随机系统的模拟中,总是要按时间步一遍一遍地随机判定事件是否发生,比如“某个设备是不是损坏了”。直觉上,这种逐次的离散判定是在模拟一种连续系统,因此理应从宏观上直接算出一个随机的发生时刻,而不用反复轮询判断是否发生。
顺着这个念头推下来,就自然走到了逆变换采样。它不只是一个数学技巧,更是一套能彻底改写模拟逻辑的思路:把高频轮询,变成事件驱动;把离散近似,变成连续精确;把性能开销,压到几乎为零。
这篇博客就从头把这套思路、推导和工程应用写清楚。
为什么我们要摆脱轮询式随机模拟
以模拟设备中的部件逐渐老化损坏为例,一个自然地模拟方法是这样的:
- 给定一个时间步(如一帧画面)内,部件保持完好的概率 $p$
- 每个时间步都做一次随机判定,部件以概率 $p$ 继续完好,$1 - p$ 损坏
- 循环执行,直到损坏发生
这种做法直观,但有几个很本质的问题:
性能浪费
绝大多数时间步都在做无意义的随机判定,事件越稀有,浪费越严重。
离散误差
部件只能在“某个时间点”损坏,不能在两个时间点之间连续地发生,和真实物理过程不符。这可能导致寿命相近的两个部件在模拟时分不出先后。
逻辑分散
系统的状态演变依赖于每一步的判定结果,不好暂停、重置、加速、减速。
我的核心直觉是:这种微观上逐次独立的随机过程,在连续时间下一定对应一个确定的概率分布,从而可以一次性算出失效时间,不再轮询。
从离散轮询到连续概率分布
一个时间步的完好概率是 $p$,那么 $n$ 个时间步的完好概率就是 $p^t$。
将这推广到连续情形也应当成立,所以,在 $t$ 时间后($t$ 不一定是整数),仍然完好的概率就是:
\[S(t) = p^t\]对应的,在时刻 $t$ 之前已经损坏的概率(分布函数 CDF)为:
\[F(t) = P(T \le t) = 1 - p^t\]对其求导得到概率密度函数:
\[f(t) = F'(t) = - \ln p \cdot p^t\]这就是一个指数分布。它描述了:在恒定故障率下,连续时间内随机失效的时间分布。
它的特点也非常符合直觉:
- $t \to 0$,损坏概率趋近于 0
- $t \to \infty$,损坏概率趋近于 1
- 时间越久,越容易坏,最终必然损坏
逆变换采样
现在问题变成:手里只有编程语言提供的均匀分布随机数 $U \sim U(0,1)$,怎么构造一个能反映失效时间的随机变量 $T$ ?
答案就是逆变换采样,思路非常干净:
- 设目标分布的分布函数为 $F(t)$
- 令均匀随机变量等于分布函数:$U = F(T)$
- 反解出 $T$,这个 $T$ 就自动服从目标分布
套用到我们的损坏模型:
\[U = 1 - p^T\]移项:
\[p^T = 1 - U\]两边取对数解出 $T$:
\[T = \frac{\ln(1-U)}{\ln p}\]因为 $1-U$ 与 $U$ 同分布,工程上可以直接写:
\[T = \frac{\ln U}{\ln p}\]所以,对于这种模拟:
- 在一开始算一个 $\frac{\ln U}{\ln p}$,这就是部件的损坏时间,到时间直接让它坏掉即可,在此之前都是好的;
- 如果模拟过程中,部件又发生了什么变化,影响到了其寿命,导致 $p$ 变成 $p’$ 了,这时就重算一个损坏时间,由于指数分布有无记忆性,这种“重 roll 失效时间”的做法也是理论上站得住脚的。
因此,这样的话,我们就只需要一次概率判定,直接得到损坏时间,而不需要再在模拟过程中的每一步都反复判定了,可以大幅减少计算开销和随机数消耗。
总结
其实这种部件老化损坏的过程,正是数学里所说的泊松过程,这次又加深了对数学的理解(又重新发明了轮子)。
整个过程没有复杂技巧,只是顺着直觉把“微观重复判定”升格为“宏观一次性结果”。在需要模拟大量泊松过程的系统里,这种思路可以同时实现:更高性能、更高精度、更干净的代码、更可控的随机行为。
讨论 A:为什么求个逆就能得到 $T$ 的表达式?
回顾一下整个思考过程:我们首先离散情形推广到连续情形,得到了 $T$ 的概率分布;然后我们面临的问题是,如何用现有的随机变量 $U \sim U(0,1)$ 去构造这个概率分布;最后我们给出的解法是,直接算 $U = F(T)$ 中 $F(x) = 1 - p^x$ 的逆函数就是。
问题是,为什么这个 $F$ 就是 $T$ 的概率分布函数 $P(T \le t) = 1 - p^t$ 呢?
其根本原因在于,任一随机变量的概率分布函数,其本身就是一个把它变换到 $U(0,1)$ 分布的函数!所以对函数取反就能把 $U(0,1)$ 分布映射回目标分布。
$U(0,1)$ 出现在这里,是一种注定的巧合:因为概率被定义为 $[0, 1]$ 的量,刚好扣在 $U(0,1)$ 上。
但 $U(0,1)$ 本身并没有多“神圣”,它只是我们选的基准。理论上,任意两个连续随机变量都可以互相转化,只是均匀分布最方便做通用中间层。
事实上逆变换采样看起来像魔法,但它的成立有明确边界:
- 必须能写出显式的 CDF
- CDF 必须严格单调、可逆
- 不可逆变换(平方、取绝对值、常函数)会造成信息坍缩,无法还原分布
所以,总结来说:
- 数学上,所有连续随机变量地位平等,只是被不同函数扭曲而成;
- 工程上,我们选均匀分布当基准,只是因为它最简单;
- 物理上,很多底层噪声(热噪声、量子噪声)更接近正态分布,因此物理设备的研制可能反而应当以正态为基准,去生成其他一切分布。
讨论 B:实际的应用场景举例
考虑到游戏也是一种随机系统的模拟,这套思路可以直接用于游戏优化:
从轮询变为事件驱动
只在生成时计算一次触发时间,到时触发,性能极大提升。
连续时间更真实
不再受帧率、时间步离散化影响,不同帧率下行为一致。
完美利用指数分布无记忆性
维修、强化、重置时,只需重新采样一个新的失效时间即可,统计上完全等价。
只是有一个关键取舍:宿命与 SL 大法
预计算失效时间会带来一个重要副作用:一旦生成,损坏时刻就已确定,玩家读档 SL 无法改变结果。想要兼顾性能与“非宿命”,就得要在每次读档时,重新 roll 所有未来随机事件。不过由于读档操作非常低频,这仍然可以节省平时模拟的开销。