蒙提霍尔问题

多年来,这个问题让许多人感到困惑,包括数学家在内。让我们看看是否可以通过模拟来解决它。

该场景源自一个名为“Let’s Make a Deal”的电视游戏节目。蒙提霍尔(Monty Hall)在 1960 年代主持了这个节目,此后它衍生出许多版本。节目中最激动人心的部分是,虽然参赛者有机会赢得大奖,但他们也可能最终得到不太受欢迎的“废物奖”。这就是现在被称为“蒙提霍尔问题”的基础。

游戏场景中,参赛者面对三扇关闭的门。其中一扇门后面是一辆豪华汽车,另外两扇门后面各有一只山羊。参赛者不知道汽车在哪里,必须按照以下规则尝试找到它。

  • 参赛者做出最初的选择,但那扇门不会打开。
  • 另外两扇门中至少有一扇后面有山羊。蒙提打开其中一扇门,露出一只山羊,其风采尽显于 Wikipedia

三扇蓝色门在白色背景上。左边和中间的门标有绿色问号。第三扇门打开,露出一只山羊。

  • 剩下两扇门,其中一扇是参赛者最初选择的。一扇门后面有汽车,另一扇后面有山羊。参赛者现在需要选择打开哪扇门。

参赛者面临一个决定。如果她想要汽车,应该打开哪扇门?她应该坚持最初的选择,还是换到另一扇门?这就是蒙提霍尔问题。

[In ]:
from datascience import *
path_data = '../../../assets/data/'
import matplotlib
matplotlib.use('Agg')
%matplotlib inline
import matplotlib.pyplot as plots
plots.style.use('fivethirtyeight')
import numpy as np

解决方案

在任何涉及概率的问题中,关于随机性的假设都很重要。合理假设参赛者最初选中汽车所在门的概率是 1/3。

在这个假设下,问题的解决方案相当直接,虽然直接的解决方案并不能让每个人都信服。不过,答案是这样的。

  • 汽车在最初选择的门后面的概率是 1/3。
  • 汽车要么在最初选择的门后面,要么在剩下的那扇门后面。它不可能在其他任何地方。
  • 因此,汽车在剩下的那扇门后面的概率是 2/3。
  • 因此,参赛者应该换门。

就是这样。故事结束。

还不信服?那么让我们模拟这个游戏,看看结果如何。

模拟

这个模拟将比我们之前做的更复杂。让我们分解一下。

第 1 步:确定模拟什么

对于每次游戏,我们将模拟三扇门后面分别是什么: - 参赛者最初选择的那扇 - 蒙提打开的那扇 - 剩下的那扇门

因此我们将跟踪三个量,而不是一个。

第 2 步:模拟一次游戏

在模拟游戏时,大部分工作通常在于模拟一次游戏。这涉及几个部分。

山羊们: 我们首先设置一个数组 goats,其中包含两只山羊的简单名称。

[In ]:
goats = make_array('first goat', 'second goat')

为了帮助蒙提进行游戏,我们需要识别哪只山羊被选中,以及哪只山羊在打开的门的后面被揭示。函数 other_goat 接受一只山羊并返回另一只。

[In ]:
def other_goat(x):
    if x == 'first goat':
        return 'second goat'
    elif x == 'second goat':
        return 'first goat'
[In ]:
other_goat('first goat'), other_goat('second goat'), other_goat('watermelon')
('second goat', 'first goat', None)

字符串 watermelon 不是山羊的名称之一,因此当输入为 watermelon 时,other_goat 什么都不做。

选项: 数组 hidden_behind_doors 包含门后的三样东西。

[In ]:
hidden_behind_doors = np.append(goats, 'car')
hidden_behind_doors
array(['first goat', 'second goat', 'car'], dtype='<U11')

现在我们准备模拟一次游戏。为此,我们将定义一个不接受参数的函数 monty_hall_game。当函数被调用时,它模拟一次蒙提的游戏并返回一个列表,包含:

  • 参赛者的猜测
  • 蒙提开门时揭示的内容
  • 另一扇门后剩下的东西

游戏开始时,参赛者随机选择一扇门。在此过程中,参赛者从第一只山羊、第二只山羊和汽车中随机选择。

如果参赛者恰好选中了一只山羊,则另一只山羊被揭示,汽车在剩下的那扇门后面。

如果参赛者恰好选中了汽车,则蒙提揭示一只山羊,另一只山羊在剩下的那扇门后面。

[In ]:
def monty_hall_game():
    """Return 
    [contestant's guess, what Monty reveals, what remains behind the other door]"""
    
    contestant_guess = np.random.choice(hidden_behind_doors)
    
    if contestant_guess == 'first goat':
        return [contestant_guess, 'second goat', 'car']
    
    if contestant_guess == 'second goat':
        return [contestant_guess, 'first goat', 'car']
    
    if contestant_guess == 'car':
        revealed = np.random.choice(goats)
        return [contestant_guess, revealed, other_goat(revealed)]

让我们开始玩!多次运行该单元格,看看结果如何变化。

[In ]:
monty_hall_game()
['car', 'first goat', 'second goat']

第 3 步:重复次数

为了衡量不同结果出现的频率,我们需要多次玩游戏并收集结果。让我们运行 10,000 次重复。

第 4 步:模拟多次重复

是时候运行整个模拟了。但与之前每次模拟只产生一个值的模拟不同,在这个示例中,我们每次模拟一个包含三个值的列表。

我们将把这个模拟列表视为表格的一行。因此,我们不是从一个空数组开始并用每个新的模拟值扩充它,而是从一个空表格开始,并用每个新的模拟行扩充表格。每行将包含一次游戏的完整结果。

通过添加新行来增长表格的一种方法是使用 append 方法。如果 my_table 是一个表格,而 new_row 是一个包含新行条目的列表,那么 my_table.append(new_row) 将新行添加到 my_table 的底部。

注意 append 不会创建新表格。它会使 my_table 增加一行。

我们将从一个具有三个空列的收集表格 games 开始。我们可以通过只需按照 monty_hall_game 返回一次游戏结果的相同顺序指定列标签列表来实现。

现在我们可以向 games 添加 10,000 行。每行将代表一次蒙提游戏的结果。

[In ]:
# empty collection table
games = Table(['Guess', 'Revealed', 'Remaining'])

# Play the game 10000 times and 
# record the results in the table games

for i in np.arange(10000):
    games.append(monty_hall_game())

模拟完成了。注意代码是多么简短。大部分工作都在模拟一次游戏的结果中完成了。

[In ]:
games.show(3)
<IPython.core.display.HTML object>

可视化

为了判断参赛者应该坚持最初的选择还是换门,让我们看看汽车在她两个选项中各自后面出现的频率。

毫不奇怪,三扇门作为参赛者最初猜测出现的频率大致相同。

[In ]:
original_choice = games.group('Guess')
original_choice
Guess       | count
car         | 3319
first goat  | 3311
second goat | 3370

一旦蒙提淘汰了一只山羊,汽车在剩下的门后面的频率如何?

[In ]:
remaining_door = games.group('Remaining')
remaining_door
Remaining   | count
car         | 6681
first goat  | 1676
second goat | 1643

正如我们先前的解决方案所说,汽车在剩下的门后面的时间约占三分之二,近似程度相当好。如果参赛者换门,她得到汽车的可能性是坚持最初选择的两倍。

为了以图形方式显示这一点,我们可以将上面的两个表格合并,并绘制叠加的条形图。

[In ]:
joined = original_choice.join('Guess', remaining_door, 'Remaining')
combined = joined.relabeled(0, 'Item').relabeled(1, 'Original Door').relabeled(2, 'Remaining Door')
combined
Item        | Original Door | Remaining Door
car         | 3319          | 6681
first goat  | 3311          | 1676
second goat | 3370          | 1643
[In ]:
combined.barh(0)
Bar plot with labels 0 to 7000 on the x-axis and 'Item' on the y-axis. Each category has two bars, one is dark blue for 'Original Door' and the other is gold for 'Remaining Door.' Each category has the same length for the dark blue 'Original Door' bar, between 3000 and 4000. The first category is 'car' and the gold bar is twice as long as the dark blue bar, approx 7000. The next two categories are 'first goat' and 'second goat' and have the same length for the gold bars, between 1000 and 2000.

注意三个蓝色条形几乎相等——最初的选择对三个可用物品中的任何一个都同样可能。但对应于 Car 的金色条形是蓝色条形的两倍长。

模拟证实,如果参赛者换门,她获胜的可能性要大一倍。