随机根本就不是随机的吗?


79

我这样做是为了测试randint的随机性:

>>> from random import randint
>>>
>>> uniques = []
>>> for i in range(4500):  # You can see I was optimistic.
...     x = randint(500, 5000)
...     if x in uniques:
...         raise Exception('We duped %d at iteration number %d' % (x, i))
...     uniques.append(x)
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
Exception: We duped 887 at iteration number 7

我尝试了10次以上,获得的最佳结果是在中继器之前进行了121次迭代。这是您可以从标准库中获得的最佳结果吗?


56
“实用程序程序员”,规则26。“选择”未中断。在OS或编译器,甚至第三方产品或库中都很少发现错误。该错误最有可能在应用程序中。或者在这种情况下,应用概率论。

11
只是挑剔:uniques = set()和uniques.add(x)会更合适(有效)。
Eric O Lebigot

22
生日悖论的主要特征之一是它违反直觉。除非您了解它或具有概率论的背景知识,否则您不一定有任何理由对其进行关键字搜索。USP的问答网站之一是,如果您在不知道搜索内容的情况下进行了纯关键字搜索,则您可以用与实际答案不匹配的术语来提问。
ConcernedOfTunbridgeWells,2010年

7
@okoku :(关于您对ConcernedOfTunbridge的答复):您所说的是完全不同的问题。一个是连续两次获得同一张卡的概率;另一个是在N个选秀权之后获得之前N-1张牌中任何一张的概率。完美RNG处理第二个问题的平均卡数应为67。考虑到您的分数介于8到121之间,这听起来不错。
BlueRaja-Danny Pflughoeft

5
您将“随机”与“均匀分布”混淆了。随机生成器多次返回完全相同的值是完全有效的。如果要使用完全不同的均匀分布随机数生成器,则它是改组问题,而不是生成器问题。

Answers:


287

生日悖论,或为什么PRNG产生重复的次数超出您的想象。


OP的问题有两个问题。一是生日悖论如上所述,第二个是您生成的内容的性质,它不能固有地保证不会重复给定的数字。

生日悖论适用于在生成器周期内给定值可能多次出现的情况,因此在值样本中可能出现重复项。生日悖论的影响在于,获得此类重复的真实可能性非常大,并且两次重复之间的平均时间比原本可能认为的要短。感知概率与实际概率之间的这种不一致使“生日悖论”成为认知偏差的一个很好的例子,在这种情况下,单纯的直观估计很可能是错误的。

伪随机数发生器(PRNG)的快速入门

问题的第一部分是您要获取随机数生成器的公开值并将其转换为更小的数字,因此可能值的空间减少了。尽管某些伪随机数生成器在其周期内不重复值,但此转换将域更改为更小的域。较小的域会使“不重复”条件无效,因此您可以预期出现重复的可能性很大。

一些算法,例如线性同余PRNGA'=AX|M确实可以保证整个周期的唯一性。在LCG中,生成的值包含累加器的整个状态,并且不保留任何其他状态。生成器是确定性的,不能在周期内重复一个数字-任何给定的累加器值都只能表示一个可能的连续值。因此,每个值只能在生成器周期内出现一次。但是,这种PRNG的周期相对较小-对于LCG算法的典型实现,约为2 ^ 30-并且不可能大于不同值的数量。

并非所有的PRNG算法都具有此特征。有些可以在一段时间内重复给定的值。在OP的问题中,Mersenne Twister算法(在Python的随机模块中使用)的周期非常长-远大于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页面上找到)中,我们可以看到房间中只需要23个人,就有50%的机会找到以这种方式匹配的两个人。

有关该主题Wikipedia条目中,我们可以获得一个不错的摘要。 在OP的问题,我们有4500可能的“生日”,而不是365。生成的给定数量的随机值(等同于“人”),我们想知道的概率任何序列中出现两个相同的值。

计算生日悖论对OP问题的可能影响

对于100个数字的序列,我们有可能匹配的(100 * 99)/ 2 = 4950 对(请参阅了解问题)(即第一个可以与第二个,第三个等匹配,第二个可以与第三个,第四个等匹配,依此类推),等等可能匹配的组合数量不止100个。

通过计算概率,我们得到的表达式1-(4500!/(4500 ** 100 *(4500-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可能很昂贵(也许需要大数运算),或者可能没有良好的几何特性。在这种算法的情况下,主要要求是通过观察值序列来推断生成器的内部状态在计算上是不可行的。


一项更正:正确使用基于LCG的PRNG,不能保证整个周期的唯一输出。例如,传统的Turbo Pascal LCG具有(IIRC)31位内部状态,但是它仅生成15位数字,该数字可以并且确实在单个周期内重复。
Porculus

46

在指责Python之前,您应该真正掌握一些概率和统计理论。首先阅读有关生日悖论的信息

顺便说一下,randomPython中的模块使用了Mersenne扭曲器PRNG,这被认为是非常好的,具有很长的时间,并且已经过广泛测试。因此请放心,您一切都好。




15

真正的随机性肯定包括在耗尽所有可能值之前重复值。否则它将不会是随机的,因为您将能够预测一个值将不会重复多长时间。

如果您曾经掷骰子,那么您肯定经常会连续获得3个6 ...




4

您正在4500从范围生成随机数500 <= x <= 5000。然后,您检查每个数字是否之前已生成。在生日的问题告诉我们的概率就是两个那些数字匹配给定的n尝试了一系列的d

您还可以对公式求反,以计算必须生成多少个数字,直到生成重复项的机会大于50%。在这种情况下,您有>50%机会在79迭代后找到重复的数字。


1

您定义了一个4501个值(500-5000)的随机空间,并且要迭代4500次。从根本上保证您会在编写的测试中发生冲突。

以另一种方式思考:

  • 当结果数组为空时P(dupe)= 0
  • 数组P(dupe)中的1个值= 1/4500
  • 数组P(dupe)中的2个值= 2/4500
  • 等等

因此,当您达到45/4500时,该插入片段有1%的机会成为重复片段,并且此概率随随后的每个插入片段而增加。

要创建一个真正测试随机函数能力的测试,请增加可能的随机值的范围(例如:500-500000)。您可能会或可能不会被骗。但是平均而言,您将获得更多的迭代。


3
由于生日问题,您的数学不正确。查看其他答案。在进行45次插入后,您有1%的机会重复了第一次插入,但是您也有44次可能重复的其他不同插入。
jcdyer

0

对于任何其他有此问题的人,我都使用了uuid.uuid4(),它的工作原理就像一个魅力。


3
那么问题可能会更好地表述为:“我想生成一系列非重复的数字,Python的randint()似乎没有这样做-做什么?” 而不是“ Python的随机数生成器不好” :-)假设uuid4()确实是随机的,它可能仍会重复-真的不太可能。您想从数字中获得哪些实际属性?不重复吗?随机?(选择一个。)不经常重复吗?(使用一个更大的范围INT,切实所有uuid4是,它似乎。)你想使用的数字究竟是什么是真正的问题。
agnoster 2010年

@agnoster我真的不打算侮辱Python,但是随机:缺乏可预测性,没有任何系统模式,而重复模式:一组重复重复的项目模式。请参见,随机生成器如果重复则不是随机的,因为它具有模式。
orokusaki 2010年

9
您对“随机”的定义是错误的。认真地,回去重新阅读关于生日悖论的内容。一个真正随机数生成器的重复频率仍然会比您凭直觉所期望的要高得多。正如@ConcernedOfTunbridgeW所指出的那样,我相信在前100个数字中重复出现500-5000范围的概率约为66%,这与您所观察到的并不矛盾。随机性确实不是意味着“不重复”,它只是意味着...好,随机的。实际上,如果您保证没有重复,则生成器必须具有较小的随机性才能实施。
agnoster 2010年

1
关于您希望这些数字用于什么的问题仍然存在。如果您特别想要非重复数字,为什么呢?uuid4()(如果是真正随机的)与randint()的范围没有很大的不同。如果您希望序列难以猜测,那么消除重复实际上会伤害您,因为一旦我看到数字(例如33),我就知道接下来不会出现33。因此,强制执行非重复实际上会使您的序列更具可预测性-您知道吗?
agnoster 2010年

0

有生日悖论。考虑到这一点,您将意识到您在说的是找到“ 764、3875、4290、4378、764”或类似内容并不是很随机,因为该序列中的数字重复出现。真正的方法是将序列相互比较。我写了一个python脚本来做到这一点。

from random import randint
y = 21533456
uniques = []
for i in range(y):  
    x1 = str(randint(500, 5000))
    x2 = str(randint(500, 5000))
    x3 = str(randint(500, 5000))
    x4 = str(randint(500, 5000))
    x = (x1 + ", " + x2 + ", " + x3 + ", " + x4)
if x in uniques:
    raise Exception('We duped the sequence %d at iteration number %d' % (x, i))
else:
    raise Exception('Couldn\'t find a repeating sequence in %d iterations' % (y))
uniques.append(x)

这个答案是几年前给出的(请参阅上面的选定答案)。它不是所谓的生日悖论,因为它不是悖论,而只是生日问题。
orokusaki 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.