【问题标题】:python random.randint vs random.choice: different outcomes usingsame valuespython random.randint vs random.choice:使用相同值的不同结果
【发布时间】:2018-03-12 20:05:36
【问题描述】:

我让我的学生编写一个 python 程序,其中将一对 6 面骰子的 100 次掷骰结果存储在一个列表中,然后绘制在一个直方图中。

我将random.choice(1,2,3,4,5,6) 视为劣于random.randint(1,6),直到我注意到使用random.choice 的学生的直方图更好地反映了预期结果。例如,在几乎所有使用random.randint(1,6) 的学生的直方图中,掷骰 12 (6+6) 的出现率异常高。有人知道发生了什么吗?

【问题讨论】:

  • 实际情况是,您“注意到”了机会波动,并将其归咎于这些波动超出了应有的范围。
  • @JohnColeman:如果这是真的,那么这种倾斜将是随机的,不是吗?但事实并非如此,这是经过多年的多次试验。

标签: python random


【解决方案1】:

From the documentation:

几乎所有模块函数都依赖于基本函数random(), 它在半开放范围 [0.0, 1.0)。 Python 使用 Mersenne Twister 作为核心生成器。它产生 53 位精度浮点数,周期为 2**19937-1。这 C 中的底层实现既快速又线程安全。这 Mersenne Twister 是测试最广泛的随机数之一 存在的发电机。但是,由于是完全确定性的,它 不适合所有用途,并且完全不适合 加密目的。

所以结果不应该有任何真正的差异。但是,我不同意random.choice() 不如randint(),事实上,随机选择实际上在生成随机数方面更快。看源码的时候:

def randint(self, a, b):
    return self.randrange(a, b+1)

def randrange(self, start, stop=None, step=1, _int=int, _maxwidth=1L<<BPF):
    istart = _int(start)
    if istart != start:
        # not executed
    if stop is None:
        # not executed

    istop = _int(stop)
    if istop != stop:
        # not executed
    width = istop - istart
    if step == 1 and width > 0:
        if width >= _maxwidth:
            # not executed
        return _int(istart + _int(self.random()*width))

对于choice()

def choice(self, seq):
    return seq[int(self.random() * len(seq))]

您可以看到randint() 具有使用randrange() 的额外开销

编辑 正如@abarnert 在 cmets 中指出的那样,这里几乎没有性能差异,randint(1,6) 是表示掷骰子的一种清晰直观的方式

我都运行了 10000 卷,但没有发现任何偏差,因此您的输入样本可能太小了:

这是一个掷骰子两次的分布,也很均匀:

我从这两个有用的答案中借用了其中的一部分:Performance of choice vs randint Is Pythons random.randint statistically random?,有助于进一步阅读。

【讨论】:

  • 使用randrange 的额外开销非常小——函数调用不是免费的,但与正在完成的工作相比,它可能不足以产生影响。无论如何,如果它真的很重要,你应该测试而不是猜测。在我的笔记本电脑上进行快速测试,choice(range(1, 6)) 需要 1070ns,choice((1,2,3,4,5,6)) 需要 786ns,randint(1,6) 需要 1099ns,randrange(1, 7) 需要 1070ns。因此,看起来额外的开销足够小,可以在噪音中消失,但 choice 由于其他原因要快一些——只要你有一个元组可以提供它。
  • 是的,对于较大的输入,时间差异似乎没有增长,保持相当小。
  • 尽管如此,差异永远不会超过 30%,并且额外的 30ns 滚动每个骰子在实际问题中不太重要,因此更重要的问题是哪个对于给定的应用程序更清晰.我认为randint(1, 6) 是实现 1d6 滚动的最明显方式,即使它在常量元组上比 randrange(1, 7)choice 稍慢。
  • random.random()leetcode.com 上编码时明显比random.randint() 快,因为运行时间约为300-500ms。
【解决方案2】:

您是正确的,您在学生的直方图中观察到的 12 的数量高于滚动 12 的理论概率,但不是因为您认为的原因。

一个实验:

import random

def roll_dice(method):
    if method == "choice":
        return random.choice([1,2,3,4,5,6]) + random.choice([1,2,3,4,5,6])
    else:
        return random.randint(1,6) + random.randint(1,6)

def est_prob(n,k,method):
    rolls = [roll_dice(method) for _ in range(k)]
    return rolls.count(n)/k

def test12(n,k,method):
    return sum(1 if est_prob(12,n,method) > 1/36 else 0 for _ in range(k))/k

请注意,test12(100,10000,"randint") 会根据randint 估计 100 个骰子的直方图掷骰子的概率过度表示 12 的总和。

典型运行:

>>> test12(100,10000,"randint")
0.5288

这大于 50%,具有统计学意义(10000 次试验是估计概率的相当大的试验次数)。

所以randint() 存在偏见的证据,不是吗?没那么快:

>>> test12(100,10000,"choice")
0.5342

使用random.choice(),您会看到同样的事情。这一切都不足为奇,因为大多数基于 100 次掷骰的掷骰子直方图高估了 12 次的概率。

当您掷一对骰子 100 次时,总和为 12 的预期掷骰数为 100/36 = 2.78。但是——你只能观察到整数个 12。观察到的 12 的数量为 3 或以上(因此导致直方图过度表示 12)的概率是 P(X >= 3) 其中 X 是参数 p = 1/36 和 n = 的二项式随机变量100. 这个概率可以计算为

P(X >= 3) = 1 - P(X<=2) 
          = 1 - P(0) - P(1) - P(2)
          = 1 - 0.0598 - 0.1708 - 0.2416
          = 0.5278

因此,大约 53% 的此类直方图具有“太多”的 12,这在 random.choice()random.randint() 中都会看到。

您似乎在randint 的背景下注意到这种现象,将其解释为偏见(即使不是),并假设这是randint 的缺陷.

【讨论】:

    【解决方案3】:

    在 Python 中生成随机整数的最快方法实际上让我感到惊讶:

    import random
    
    die = int(random.random() * 6) + 1  # The equivalent to 'die = random.randint(1, 6)
    

    单看,它看起来计算起来更复杂,但速度差异非常明显。

    我在 MONTY 算法中测试了这两种方法,使用 int(random.random) 方法的速度提高了大约 200%。

    也比 random.choice 快一个下降幅度。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-24
      • 2020-04-24
      • 2021-07-25
      • 1970-01-01
      • 2019-10-23
      • 2013-01-07
      • 1970-01-01
      • 2016-02-21
      相关资源
      最近更新 更多