简体   繁体   English

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

[英]More efficient simluation of 2 dice rolls - Python

I wrote a program that records how many times 2 fair dice need to be rolled to match the probabilities for each result that we should expect.我写了一个程序来记录需要掷多少次 2 个公平的骰子来匹配我们应该期望的每个结果的概率。

I think it works but I'm wondering if there's a more resource friendly way to solve this problem.我认为它有效,但我想知道是否有更资源友好的方法来解决这个问题。

 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)

There are several problems here.这里有几个问题。

Firstly, there is no guarantee that this will ever terminate, nor is it particularly likely to terminate in a reasonable amount of time.首先,不能保证这将永远终止,也不太可能在合理的时间内终止。 Ignoring floating point arithmetic issues, this should only terminate when your numbers are distributed exactly right.忽略浮点算术问题,这应该只在您的数字完全正确分布时终止。 But the law of large numbers does not guarantee this will ever happen.但是大数定律并不能保证这会发生。 The law of large numbers works like this:大数定律的工作原理是这样的:

  1. Your initial results are (by random chance) almost certainly biased one way or another.您的初始结果(随机)几乎肯定会以某种方式存在偏见。
  2. Eventually, the trials not yet performed will greatly outnumber your initial trials, and the lack of bias in those later trials will outweigh your initial bias.最终,尚未执行的试验数量将大大超过您的初始试验,并且这些后续试验中没有偏倚的情况将超过您的初始偏倚。

Notice that the initial bias is never counterbalanced .请注意,初始偏差永远不会被抵消 Rather, it is dwarfed by the rest of the results.相反,它与其他结果相形见绌。 This means the bias tends to zero, but it does not guarantee the bias actually vanishes in a finite number of trials.这意味着偏差趋于零,但并不能保证偏差在有限次数的试验中实际上消失。 Indeed, it specifically predicts that progressively smaller amounts of bias will continue to exist indefinitely.事实上,它特别预测了逐渐减少的偏见将无限期地继续存在。 So it would be entirely possible that this algorithm never terminates, because there's always that tiny bit of bias still hanging around, statistically insignificant, but still very much there.所以这个算法完全有可能永远不会终止,因为总是有那么一点点偏见仍然存在,在统计上微不足道,但仍然存在。

That's bad enough, but you're also working with floating point, which has its own issues ;这已经够糟糕了,但您也在使用浮点数,它有其自身的问题 in particular, floating point arithmetic violates lots of conventional rules of math because the computer keeps doing intermediate rounding to ensure the numbers continue to fit into memory, even if they are repeating (in base 2) or irrational.特别是,浮点算术违反了许多传统的数学规则,因为计算机不断进行中间舍入以确保数字继续适合内存,即使它们是重复的(以 2 为底)或无理数。 The fact that you are rounding the empirical percents to three decimal places doesn't actually fix this, because not all terminating decimals (base 10) are terminating binary values (base 2), so you may still find mismatches between your empirical and expected values.您将经验百分比四舍五入到小数点后三位这一事实实际上并不能解决这个问题,因为并非所有终止小数(基数为 10)都终止二进制值(基数为 2),因此您可能仍会发现经验值和预期值之间不匹配. Instead of doing this:而不是这样做:

if emp_percent == expected:
    break

...you might try this (in Python 3.5+ only): ...你可以试试这个(仅在 Python 3.5+ 中):

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

This solves both problems at once.这一次解决了这两个问题。 By default, math.is_close() requires the values to be within (about) 9 decimal places of one another, so it inserts the necessary give for this algorithm to actually have a chance of working.默认情况下, math.is_close()要求值彼此相差(大约)9 位小数,因此它插入了必要的给定,使该算法真正有机会工作。 Note that it does require special handling for comparisons involving zero, so you may need to tweak this code for your use case, like this:请注意,它确实需要对涉及零的比较进行特殊处理,因此您可能需要针对您的用例调整此代码,如下所示:

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

math.is_close() also removes the need to round your empiricals, since it can do this approximation for you: 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

If you really don't want these approximations, you will have to give up floating point and work with fractions exclusively.如果您真的不想要这些近似值,您将不得不放弃浮点数并专门使用fractions They produce exact results when divided by one another.当它们彼此相除时,它们会产生精确的结果。 However, you still have the problem that your algorithm is unlikely to terminate quickly (or perhaps at all), for the reasons discussed above.但是,由于上述原因,您仍然存在算法不太可能快速终止(或者可能根本终止)的问题。

Rather than trying to match floating point numbers -- you could try to match expected values for each possible sum.而不是尝试匹配浮点数 - 您可以尝试匹配每个可能总和的预期值。 This is equivalent to what you are trying to do since (observed number)/(number of trials) == (theoretical probability) if and only if the observed number equals the expected number.这等效于您正在尝试做的事情,因为(观察到的数量)/(试验次数)==(理论概率)当且仅当观察到的数量等于预期的数量时。 These will always be an integer exactly when the number of rolls is a multiple of 36. Hence, if the number of rolls is not a multiple of 36 then it is impossible for your observations to equal expectations exactly.当滚动数是 36 的倍数时,这些将始终是整数。因此,如果滚动数不是 36 的倍数,那么您的观察结果不可能完全等于预期。

To get the expected values, note that the numerators that appear in the exact probabilities of the various sums (1,2,3,4,5,6,5,4,3,2,1 for the sums 2,3,..., 12 respectively) are the expected values for the sums if the dice are rolled 36 times.要获得预期值,请注意出现在各种和的精确概率中的分子(1,2,3,4,5,6,5,4,3,2,1 表示和 2,3,. ..,分别12)骰子滚动 36 次时总和的预期值。 If the dice are rolled 36i times then multiply these numerators by i to get the expected values of the sums.如果掷骰子 36i 次,则将这些分子乘以 i 以获得总和的预期值。 The following code simulates repeatedly rolling a pair of fair dice 36 times, accumulating the total counts and then comparing them with the expected counts.以下代码模拟将一对公平骰子重复掷 36 次,累加总计数,然后将它们与预期计数进行比较。 If there is a perfect match, the number of trials (where a trial is 36 rolls) needed to get the match is returned.如果存在完美匹配,则返回获得匹配所需的试验次数(其中试验为 36 卷)。 If this doesn't happen by max_trials , a vector showing the discrepancy between the final counts and final expected value is given:如果max_trials没有发生这种情况, 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)]

Here is some typical output:下面是一些典型的输出:

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

Not only have the exact expected values never been observed in 36 million simulated rolls of a pair of fair dice, in the final state the discrepancies between observations and expectations have become quite large (in absolute value -- the relative discrepancies are approaching zero, as the law of large numbers predicts).不仅在一对公平骰子的 3600 万个模拟卷中从未观察到确切的期望值,在最终状态下,观察值和期望值之间的差异变得非常大(绝对值——相对差异接近于零,如大数定律预测)。 This approach is unlikely to ever yield a perfect match.这种方法不太可能产生完美的匹配。 A variation that would work (while still focusing on expected numbers) would be to iterate until the observations pass a chi-squared goodness of fit test when compared with the theoretical distribution.一个可行的变体(同时仍然关注预期数字)是迭代,直到与理论分布相比,观察通过卡方拟合优度检验。 In that case there would no longer be any reason to focus on multiples of 36.在那种情况下,将不再有任何理由关注 36 的倍数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM