生日悖论,或者为什么 PRNG 比你想象的更频繁地产生重复。
OP的问题有几个问题在起作用。一是上面提到的生日悖论,二是你正在生成的东西的性质,这并不能固有地保证给定的数字不会重复。
生日悖论适用于给定值在生成器期间可能多次出现的情况 - 因此重复可能在值样本中发生。生日悖论的影响是,获得此类副本的真正可能性非常大,而且它们之间的平均周期比人们原本可能想象的要小。感知概率和实际概率之间的这种不协调使生日悖论成为认知偏差的一个很好的例子,其中一个天真的直觉估计可能会大错特错。
伪随机数生成器 (PRNG) 的快速入门
问题的第一部分是您正在获取随机数生成器的公开值并将其转换为更小的数字,因此可能值的空间减少了。尽管一些伪随机数生成器在其周期内不重复值,但这种转换将域更改为更小的域。较小的域使“无重复”条件无效,因此您可以预期重复的可能性很大。
一些算法,例如线性同余 PRNG ( A'=AX|M
)确实保证了整个周期的唯一性。在 LCG 中,生成的值包含累加器的整个状态,并且不保存任何其他状态。生成器是确定性的,不能在周期内重复一个数字 - 任何给定的累加器值只能暗示一个可能的连续值。因此,每个值在生成器的周期内只能出现一次。然而,这种 PRNG 的周期相对较小——对于 LCG 算法的典型实现来说约为 2^30——并且不可能大于不同值的数量。
并非所有 PRNG 算法都具有此特性;有些可以在周期内重复给定值。在 OP 的问题中,Mersenne Twister算法(在 Python 的random模块中使用)的周期很长——远大于 2^32。与线性同余 PRNG 不同,结果不纯粹是先前输出值的函数,因为累加器包含附加状态。对于 32 位整数输出和 ~2^19937 的周期,它不可能提供这样的保证。
Mersenne Twister 是一种流行的 PRNG 算法,因为它具有良好的统计和几何特性以及非常长的周期——这是用于仿真模型的 PRNG 的理想特性。
良好的统计特性意味着算法生成的数字是均匀分布的,没有数字的出现概率明显高于其他数字。较差的统计特性可能会在结果中产生不必要的偏差。
良好的几何特性意味着 N 个数的集合不位于 N 维空间中的超平面上。较差的几何特性会在仿真模型中产生虚假的相关性并扭曲结果。
长周期意味着您可以在序列环绕到开始之前生成大量数字。如果模型需要大量迭代或必须从多个种子运行,那么典型 LCG 实现中可用的 2^30 左右的离散数字可能不够用。MT19337算法的周期很长——2^19337-1,也就是10^5821左右。相比之下,宇宙中的原子总数估计约为 10^80。
MT19337 PRNG 产生的 32 位整数不可能代表足够的离散值,以避免在如此大的周期内重复。在这种情况下,如果样本足够大,可能会出现重复值并且不可避免。
简而言之,生日悖论
这个问题最初被定义为房间里任意两个人生日相同的概率。关键是房间里的任何两个人都可以共享一个生日。人们往往天真地把这个问题误解为房间里某人与特定个人共享生日的概率,这是认知偏差的来源,往往导致人们低估概率。这是不正确的假设 - 没有要求匹配特定个人,任何两个人都可以匹配。
任何两个人之间发生匹配的概率远高于与特定个人匹配的概率,因为匹配不一定是特定日期。相反,您只需找到两个生日相同的人。从这张图(可以在该主题的维基百科页面上找到),我们可以看到,我们只需要房间里的 23 人,就有 50% 的机会找到两个以这种方式匹配的人。
从有关该主题的 Wikipedia 条目中,我们可以得到一个很好的总结。 在 OP 的问题中,我们有 4,500 个可能的“生日”,而不是 365 个。对于生成的给定数量的随机值(等于“人”),我们想知道任何两个相同值出现在序列中的概率。
计算生日悖论对 OP 问题的可能影响
对于 100 个数字的序列,我们有可能匹配的
对(请参阅了解问题)(即第一个可以匹配第二个、第三个等,第二个可以匹配第三个、第四个等,依此类推),所以可能匹配的组合数量不仅仅是 100 个。
通过计算概率,我们得到 的表达式
。下面的 Python 代码片段对匹配对出现的概率进行了简单的评估。
# === birthday.py ===========================================
#
from math import log10, factorial
PV=4500 # Number of possible values
SS=100 # Sample size
# These intermediate results are exceedingly large numbers;
# Python automatically starts using bignums behind the scenes.
#
numerator = factorial (PV)
denominator = (PV ** SS) * factorial (PV - SS)
# Now we need to get from bignums to floats without intermediate
# values too large to cast into a double. Taking the logs and
# subtracting them is equivalent to division.
#
log_prob_no_pair = log10 (numerator) - log10 (denominator)
# We've just calculated the log of the probability that *NO*
# two matching pairs occur in the sample. The probability
# of at least one collision is 1.0 - the probability that no
# matching pairs exist.
#
print 1.0 - (10 ** log_prob_no_pair)
对于从 4500 个可能值的总体中抽样的 100 个数字内发生的匹配,这会产生p=0.669的合理外观结果。(也许有人可以验证这一点并在错误时发表评论)。由此我们可以看出,OP 观察到的匹配数字之间的运行长度似乎是相当合理的。
脚注:使用洗牌获得唯一的伪随机数序列
请参阅下面来自 S. Mark 的答案,以获取获得保证的唯一随机数集的方法。张贴者所指的技术采用一组数字(您提供,因此您可以使它们独一无二)并将它们随机排列。从混洗后的数组中按顺序绘制数字将为您提供一系列保证不会重复的伪随机数。
脚注:加密安全 PRNG
MT 算法在密码学上并不安全,因为通过观察数字序列来推断生成器的内部状态相对容易。其他算法(例如Blum Blum Shub)用于加密应用,但可能不适合模拟或一般随机数应用。加密安全的 PRNG 可能很昂贵(可能需要 bignum 计算)或者可能没有良好的几何特性。在这种类型的算法的情况下,主要要求是通过观察一系列值来推断生成器的内部状态在计算上应该是不可行的。