更有效地模拟2个骰子卷 - Python

时间:2015-09-30 17:51:17

标签: python

我写了一个程序,记录需要滚动2个公平骰子的次数,以匹配我们应该期望的每个结果的概率。

我认为它有效,但我想知道是否有更资源友好的方法来解决这个问题。

 import random

 expected = [0.0, 0.0, 0.028, 0.056, 0.083, 
             0.111, 0.139, 0.167, 0.139, 0.111,
             0.083, 0.056, 0.028]

 results = [0.0] * 13  # store our empirical results here

 emp_percent = [0.0] * 13  # results / by count

 count = 0.0  # how many times have we rolled the dice? 

 while True:
     r = random.randrange(1,7) + random.randrange(1,7)  # roll our die
     count += 1 
     results[r] += 1
     emp_percent = results[:]

     for i in range(len(emp_percent)):
         emp_percent[i] /= count
         emp_percent[i] = round(emp_percent[i], 3)

     if emp_percent == expected:
         break

print(count)
print(emp_percent)

2 个答案:

答案 0 :(得分:1)

这里有几个问题。

首先,不保证会终止,也不会在合理的时间内终止。忽略浮点运算问题,这应该仅在您的数字完全正确分配时终止。但law of large numbers并不保证会发生这种情况。大数法则是这样的:

  1. 你的初步结果(通过随机机会)几乎肯定会有某种偏见。
  2. 最终,尚未进行的试验将大大超过您的初步试验,而后期试验中缺乏偏见将超过您最初的偏见。
  3. 请注意,初始偏差从不抵消。相反,它与其他结果相形见绌。这意味着偏差倾向于零,但它并不能保证在有限数量的试验中偏差实际上消失了。实际上,它特别预测逐渐减少的偏见量将无限期地继续存在。所以这个算法永远不会终止是完全有可能的,因为总是那么微小的偏见仍然悬而未决,统计上无关紧要,但仍然非常存在。

    这已经够糟了,但你也在使用浮点数has its own issues;特别是,浮点运算违反了许多传统的数学规则,因为计算机不断进行中间舍入以确保数字继续适合内存,即使它们重复(在基数2中)或无理数。您将经验百分比四舍五入到小数点后三位的事实并没有实际解决这个问题,因为并非所有终止小数(基数为10)都是终止二进制值(基数为2),因此您可能仍会发现经验值与实数之间存在不匹配预期值。而不是这样做:

    if emp_percent == expected:
        break
    

    ...你可以试试这个(仅限Python 3.5+):

    if all(map(math.is_close, emp_percent, expected)):
        break
    

    这可以立即解决这两个问题。默认情况下,math.is_close()要求值在彼此的(大约)9个小数位内,因此它会为此算法插入必要的赋值以实际有机会工作。请注意,它确实需要对涉及零的比较进行特殊处理,因此您可能需要针对您的用例调整此代码,如下所示:

    is_close = functools.partial(math.is_close, abs_tol=1e-9)
    if all(map(is_close, emp_percent, expected)):
        break
    

    math.is_close()也不需要对你的经验进行整理,因为它可以为你做近似:

    is_close = functools.partial(math.is_close, rel_tol=1e-3, abs_tol=1e-5)
    if all(map(is_close, emp_percent, expected)):
        break
    

    如果您真的不想要这些近似值,则必须放弃浮动点并专门使用fractions。它们在彼此分开时产生精确的结果。但是,由于上面讨论的原因,您仍然存在问题,即您的算法不太可能快速终止(或者可能根本不会终止)。

答案 1 :(得分:0)

您可以尝试匹配每个可能总和的预期值,而不是尝试匹配浮点数。这相当于您尝试做的事情(观察数量)/(试验次数)==(理论概率)当且仅当观察到的数字等于预期数量时。当卷数是36的倍数时,这些将始终是一个整数。因此,如果卷的数量不是36的倍数,那么您的观察结果不可能完全等于预期。

要获得预期值,请注意出现在各种总和的确切概率中的分子(1,2,3,4,5,6,5,4,3,2,1为总和2, 3,...,12分别) 如果掷骰子36次,则总和的预期值。如果骰子滚动36次,则将这些分子乘以i得到总和的预期值。下面的代码模拟重复滚动一对公平骰子36次,累计总计数,然后将它们与预期计数进行比较。如果存在完美匹配,则返回获得匹配所需的试验次数(试验为36次试验)。如果max_trials没有发生这种情况,则会显示一个向量,显示最终计数与最终预期值之间的差异:

import random

def roll36(counts):
    for i in range(36):
        r1 = random.randint(1,6)
        r2 = random.randint(1,6)
        counts[r1+r2 - 2] += 1

def match_expected(max_trials):
    counts = [0]*11
    numerators = [1,2,3,4,5,6,5,4,3,2,1]
    for i in range(1, max_trials+1):
        roll36(counts)
        expected = [i*j for j in numerators]
        if counts == expected:
            return i
    #else:
    return [c-e for c,e in zip(counts,expected)]

以下是一些典型的输出:

>>> match_expected(1000000)
[-750, 84, 705, -286, 5783, -3504, -1208, 1460, 543, -1646, -1181]

在一对公平骰子的3600万个模拟卷中,不仅没有观察到确切的预期值,在最终状态下,观察和期望之间的差异变得非常大(绝对值 - 相对差异接近零,正如大数定律所预测的那样。这种方法不太可能产生完美的匹配。可行的变化(同时仍然关注预期的数字)将迭代直到观察通过卡方拟合优度测试与理论分布相比较。在那种情况下,将不再有任何理由专注于36的倍数。