用python验证蒙提霍尔问题

最初看到这个问题是初中的时候买了一本有关数学谜题的书里面概率论的一张的课后拓展就是说到三门问题,当时作为一个扩展阅读看了一下,里面说到了一个世界智商最高的女人秒杀了美国一大群的数学高材生的精彩故事(比较夸张),当时对这个问题也是似懂非懂。

什么是蒙提霍尔问题?

蒙提霍尔
蒙提霍尔

蒙提霍尔问题,亦称为蒙特霍问题或三门问题(英文:Monty Hall problem),是一个源自博弈论的数学游戏问题,大致出自美国的电视游戏节目Let's Make a Deal。问题的名字来自该节目的主持人蒙提·霍尔(Monty Hall)。

最初的表述是:

参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门就可以赢得该汽车,而另外两扇门后面则各藏有一只山羊。当参赛者选定了一扇门,但未去开启它的时候,节目主持人开启剩下两扇门的其中一扇,露出其中一只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。
问题是:换另一扇门会否增加参赛者赢得汽车的机会率?

这个古老的问题一经提出就引起了剧烈的争论,有人认为换与不换最终得到车的概率都是1/2,有人认为换门之后得到车的概率更大,应该选择换门之后得到车的概率为2/3在撰写这篇文章的时候在果壳上还有人在为此争吵,知乎上也有许多关于这方面的讨论,其实这些争论很多情况下都是因这个问题的模糊表述所引起的,关键点在于主持人对于门后的情况是否了解

  1. 如果主持人事先知道哪个门里有山羊并且他特意选择了有山羊的门打开了,那么参赛者应该换另一扇门,这可以将他胜利的概率从1/3升到2/3
  2. 如果主持人事先不知道哪个门里有山羊或者他只是随机的选择了一个门,但事实发现里面恰好是山羊。这时候参赛者没有换门的必要,胜利概率总是1/2

为了后续的讨论,这里采用维基百科上对于这一个问题的不含糊的定义

严格的表述如下:

  • 参赛者在三扇门中挑选一扇。他并不知道内里有什么。
  • 主持人知道每扇门后面有什么。
  • 主持人必须开启剩下的其中一扇门,并且必须提供换门的机会。
  • 主持人永远都会挑一扇有山羊的门。
    • 如果参赛者挑了一扇有山羊的门,主持人必须挑另一扇有山羊的门。
    • 如果参赛者挑了一扇有汽车的门,主持人随机在另外两扇门中挑一扇有山羊的门。
  • 参赛者会被问是否保持他的原来选择,还是转而选择剩下的那一道门。

那么这个问题这可以很好的理解了,引用维基的一幅图片解析:


蒙提霍尔解答
蒙提霍尔解答

有三种可能的情况,全部都有相等的可能性(1/3):

  • 参赛者挑汽车,主持人挑两头羊的任何一头。转换将失败。
  • 参赛者挑A羊,主持人挑B羊。转换将赢得汽车。
  • 参赛者挑B羊,主持人挑A羊。转换将赢得汽车。

所以玩家选择换门之后获胜的概率应为2/3

证明?

蒙提霍尔解答
蒙提霍尔解答

定义:

  • 事件A为一开始玩家选择的一扇门
  • 事件H为最后门后的结果
  • 如果是选择不换门的策略

因为选择的是不交换的策略,所有只有一开始选中的是汽车,最后才能选中汽车。

  • 选择交换门的策略

因为选择的是交换的策略,所有只有一开始选中的是羊,最后才能选中汽车。

程序验证

实践是检验真理的唯一标准,在流言终结者看到他们人工重复这个实验区验证,发现这样很浪费时间。何通过计算机去去模拟这一段过程呢?
下面使用python程序来模拟这一段过程:

from __future__ import division
import logging
from matplotlib import pyplot as plt
import numpy as np
import random


class MontyHall(object):
    """docstring for MontyHall"""

    def __init__(self, num=3):
        """
        创建一个door列表
        0 代表关门
        1 表示后面有车
        -1 代表门被打开
        """
        super(MontyHall, self).__init__()
        self.doors = [0] * num
        self.doors[0] = 1
        self.choice = -1
        self.exclude_car = False
        self.shuffle()

    def shuffle(self):
        """  
        开始新游戏
        重新分配门后的东西
        """
        if self.exclude_car == True:
            self.doors[0] = 1
            self.exclude_car = False
        for i in xrange(len(self.doors)):
            if self.doors[i] == -1:
                self.doors[i] = 0
        random.shuffle(self.doors)

    def make_choice(self):
        """
        player随机选择一扇门
        """
        self.choice = random.randint(0, len(self.doors) - 1)
        logging.info("choice: %d" % self.choice)
        logging.info("original: %s" % self.doors)

    def exclude_doors(self):
        """
        主持人知道门后的情况排除门
        直到剩余两扇门
        """
        to_be_excluded = []
        for i in xrange(len(self.doors)):
            if self.doors[i] == 0 and self.choice != i:
                to_be_excluded.append(i)  
        random.shuffle(to_be_excluded)
        for i in xrange(len(self.doors) - 2):
            self.doors[to_be_excluded[i]] = -1
        logging.info("final: %s" % self.doors)

    def random_exclude_doors(self):
        """
        主持人并不知道门后面的情况随机的开门
        直到剩余两扇门
        """
        to_be_excluded = []
        for i in xrange(len(self.doors)):
            if self.doors[i] != -1 and i != self.choice:
                to_be_excluded.append(i)  
        random.shuffle(to_be_excluded)
        for i in xrange(len(self.doors) - 2):
            if self.doors[to_be_excluded[i]] == 1:
                self.exclude_car = True
            self.doors[to_be_excluded[i]] = -1
        logging.info("final: %s" % self.doors)

    def change_choice(self):
        """
        player改变选择
        """
        to_change = []
        for i in xrange(len(self.doors)):
            if self.doors[i] != -1 and i != self.choice:
                to_change.append(i)
        self.choice = random.choice(to_change)
        logging.info("choice changed: %d" % self.choice)

    def random_choice(self):
        """
        player 第二次随机选择门
        """
        to_select = []
        for i in xrange(len(self.doors)):
            if self.doors[i] != -1:
                to_select.append(i)
        self.choice = random.choice(to_select)
        logging.info("random choice : %d" % self.choice)


    def show_answer(self):
        """
        展示门后的情况
        """
        logging.info(self.doors)

    def check_result(self):
        """
        验证结果
        """
        got_it = False
        if self.doors[self.choice] == 1:
            got_it = True
        return got_it

模拟1000轮,每一轮重复试验1000次

  • 不改变选择:
def unchange_choice_test(n):
    """
    不改变初始的选择
    """
    result = {}
    game = MontyHall()
    for i in xrange(n):
        game.shuffle()
        game.make_choice()
        game.exclude_doors()
        if game.check_result():
            result["yes"] = result.get("yes", 0) + 1
        else:
            result["no"] = result.get("no", 0) + 1
    for key in result:
        print "%s: %d" % (key, result[key])
    return result["yes"] / n

if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
    results = []
    test_num = 1000
    round_num = 1000
    for x in xrange(0,round_num):
        results.append(change_random_test(test_num) )

    y_mean = np.mean(results)
    y_std = np.std(results)
    x = range(0,round_num)
    y = results
    plt.figure(figsize=(8,4))
    
    plt.xlabel("round")
    plt.ylabel("frequency")
    plt.title("The frequency of the success")
    tx = round_num / 2
    ty = y_mean
    label_var = "$\sigma \left( X \\right)=$%f" % y_std
    label_mean = "$ X =$%f" % y_mean
    p1_label = "%s and %s" % (label_var,label_mean)
    p1 = plt.plot(x,y,"-",label=p1_label,linewidth=2)
    plt.legend(loc='upper left')
    

    pl2 = plt.figure(2)
    plt.figure(2)
    plt.hist(results,40,normed=1,alpha=0.8)
    plt.show()

结果:


此处输入图片的描述
此处输入图片的描述

概率分布:


此处输入图片的描述
此处输入图片的描述

成功的概率均值在 1/3 附近
  • 改变选择:
def change_choice_test(n):
    """
    交换选择的门
    """
    result = {}
    game = MontyHall()
    for i in xrange(n):
        game.shuffle()
        game.make_choice()
        game.exclude_doors()
        game.change_choice()
        if game.check_result():
            result["yes"] = result.get("yes", 0) + 1
        else:
            result["no"] = result.get("no", 0) + 1
    for key in result:
        print "%s: %d" % (key, result[key])
    return result["yes"] / n

同样的方法绘图得到结果:


此处输入图片的描述
此处输入图片的描述

概率分布:


此处输入图片的描述
此处输入图片的描述

成功的概率均值在 2/3 附近

通过上面的分析与模拟可知最佳的策略当然就是换门。

更加深入的讨论

  • 如果门的数量不止是3个,如果是50扇门呢?
此处输入图片的描述
此处输入图片的描述

这种情况下,主持人打开48扇都是羊的门后,再给你选择,很多人这个时候应该就不会固守那1/2,而会选择换门
把门的数据增大到100,1000,这种情况会更加明显。
还是通过一段程序模拟说明:

def change_choice_test_large(n,m):
    """
    交换选择的门
    """
    result = {}
    game = MontyHall(m)
    for i in xrange(n):
        game.shuffle()
        game.make_choice()
        game.exclude_doors()
        game.change_choice()
        if game.check_result():
            result["yes"] = result.get("yes", 0) + 1
        else:
            result["no"] = result.get("no", 0) + 1
    for key in result:
        print "%s: %d" % (key, result[key])
    return result["yes"] / n
    
    
if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
    results = []
    test_num = 1000
    round_num = 1000
    for x in xrange(0,round_num):
        results.append(change_choice_test_large(test_num,50) )

结果:



这时候就要选择交换门

  • 遇到这种情况我很困惑,我决定抛硬币决定,这个时候成功的概率?

这是第3种策略,成功的概率和硬币有关,也就是1/2,这种情况就是从剩下的门中随机选择一扇,这个策略从上面分析来看不是最好的,但是比不改变的策略要好。
程序的模拟结果:


此处输入图片的描述
此处输入图片的描述

此处输入图片的描述
此处输入图片的描述
  • 比如门意外打开的情况呢,也就是上面描述的第二种情况(主持在不知门后的情况下打开门呢)?

这种情况下其实就是一个条件概率,事件A是玩家最后开到的是车,事件B是主持人打开的门是羊。

因为只有主持人开到是羊的情况下,玩家才有可能开到车所以


设玩家第一次选择的门为事件C

  • 不交换策略下的条件概率是:
QQ截图20150510140602.png
  • 交换策略下的条件概率是:

因此在主持人不知道门后的情况下打开一扇,然后发现门后是羊的情况下,换门与不换门最终的概率都是1/2
还是可以通过程序进行模拟:

def unknown_doors_choice_test(n):
    """
    主持人并不知道门后面的情况随机的开门
    交换选择的门
    """
    result = {}
    game = MontyHall()
    continue_count = 0
    for i in xrange(n):
        game.shuffle()
        game.make_choice()
        game.random_exclude_doors()
        game.change_choice()
        if game.exclude_car == False:
            continue_count += 1
        if game.check_result():
            result["yes"] = result.get("yes", 0) + 1
        else:
            result["no"] = result.get("no", 0) + 1
    #for key in result:
    #    print "%s: %d" % (key, result[key])
    logging.info("continue_count: %d" % continue_count)
    if continue_count == 0:
        return 0.0
    return result["yes"] / continue_count   
此处输入图片的描述
此处输入图片的描述

此处输入图片的描述
此处输入图片的描述

在这种情况下交换门也没有提升成功的概率


总结

今天写的这篇东西也算是了解我童年的一个遗憾,人的直觉有时候是很不可靠,要摆脱个人局限的认知才能拥抱更大的世界。
什么?看完这些解析,你还觉得不满意那么你还可以从下面的参考中寻找更好的解析,本文撰写过程有部分的图片引用自一下的参考,如果你还有疑问欢迎你联系我进一步的讨论。

练习

下面是三门问题的两个翻版,引用自三门问题及相关

女孩的概率

  • 你结交一位新朋友,问她是否有孩子。她说有,有两个。你问,有女孩吗?她说有。那么,两个都是女孩的概率是多少?

答:三分之一。因为生两个孩子的可能性有四种等可能:BB、GG、BG、GB(即男男、女女、男女、女男)。 因为我们已知至少有一个女儿,所以BB是不可能的。因此GG是可能出现的三个等可能的结果之一,所以两个孩子都是女儿的概率为三分之一。这对应了三门问题的第一种情况。

  • 你结交一位新朋友,问她是否有孩子。她说有,有两个。你问,有女孩吗?她说有。第二天,你看见她带了一个小女孩。你问她,这是你女儿吗?她说,是。她的两个孩子都是女孩的概率是多少?

这个概率和生女孩的概率相同,二分之一。这似乎非常奇怪,因为我们所拥有的信息看起来并不比第一种情况时多,但概率却不同。但是这里的问题其实是,那个你没>见过的孩子是女孩的概率是多少?这个概率和生女孩的概率相同,二分之一。
这对应了三门问题的第二种情况。当然这里也有语言问题,必须假定这位母亲不是特定带出一个小女孩来给你看的。也就是说你只是碰巧发现了它是位小女孩。这取决于是判断选择 或q 随机选择。如果是被你碰巧撞见这是属于随机选择。这就对应了三门问题的第二种情况。这其实是增加了信息的。否则如果她主动带一个小女孩过来给你,则属于判断选择。
你得到的答案依赖于所讲的故事;它依赖于你是如何得知至少一个孩子是女孩的。

三囚犯问题

  • 亚当、比尔和查尔斯被关在一个监狱里,只有监狱看守知道谁会被判死刑,另外两位将会获释。有1/3的概率会被处死刑的亚当,给他母亲写了一封信,想要获释的比尔或查尔斯帮忙代寄。当亚当问看守他应当把他的信交给比尔还是查尔斯时,这位富有同情心的看守很为难。他认为如果他把将要获释的人的名字告诉亚当,那么亚当就会有1/2的概率被判死刑,因为剩下的人和亚当这两人中一定有一个人被处死。如果他隐瞒这信息,亚当被处死的概率是1/3。既然亚当知道其他两人中必有一人会获释,那么亚当自己被处死的概率怎么可能会因为看守告诉他其他两人中被获释者的姓名后而改变呢?

正确的答案是:看守不用当心,因为即使把获释人的姓名告诉亚当,亚当被处死的概率仍然是1/3,没有改变。但是,剩下的那位没被点名的人就有2/3的概率被处死(被处死的可能性升高了)。如果这个问题换一种说法,就是看守无意间说出了查尔斯不会死。那么概率就会发生改变。
这个其实和三门问题是一致的。你可以把狱卒当成主持人,被处死当成是大奖,那么这个是对应于三门问题的第一种情况,就是主持人知道门后面的情况。狱卒说出谁会被释放,相当于主持人打开一扇门。但是因为三囚徒问题不能选择,也就相当于三门问题中的不换门的策略。最终的概率还是1/3是没有发生改变的。
为了避免产生歧义,规定一下:
1.如果(亚当,查尔斯)被释放,那么狱卒会告诉亚当:"查尔斯被释放"。
2.如果(亚当,比尔)被释放,那么狱卒会告诉亚当:"比尔被释放"
3.如果(查尔斯,比尔)被释放,那么狱卒会以1/2的概率告诉亚当:"查尔斯被释放"或者"比尔被释放"
意思就很明显了,在狱卒说出比尔被释放的条件下,亚当被释放的概率是?用条件概率算一下。
定义事件:

A :狱卒说出"比尔被释放"
B :代表亚当被释放。


那什么时候才是1/2的概率呢?
规则3更改为:如果(查尔斯,比尔)被释放,那么狱卒会告诉亚当"比尔被释放"
这个时候计算就是:



那如果规则3改为:如果(查尔斯,比尔)被释放,那么狱卒会告诉亚当"查尔斯被释放"
这个时候:亚当被释放的概率就会变为1
问题在于规则2和规则3下说"比尔被释放"不是等概率发生的。

类似的问题还有

  • 抛两枚硬币其中有一枚硬币是正面,问两枚硬币都是正面的概率是?
  • 抛两枚硬币其中第一枚硬币是正面,问两枚硬币都是正面的概率是?

the end.


参考:

  1. 蒙提霍尔问题 - 维基百科,自由的百科全书

  2. 三扇门问题 | 左岸读书

  3. 蒙提霍尔问题(又称三门问题、山羊汽车问题)的正解是什么?

  4. 趣味编程:三门问题

  5. 三门问题及相关

  1. 换还是不换?争议从未停止过的三门问题

  2. 在「三门问题」中,参与者应该选择「换」还是「不换」?主持人是否知道门后情形对结论有何影响?

  3. THE MONTY HALL PROBLEM

  4. 流言终结者第九季

  5. 某个家庭中有 2 个小孩,已知其中一个是女孩,则另一个是男孩的概率是多少?-知乎

  6. 从贝叶斯定律的角度理解“蒙提霍尔问题”和“三个囚犯问题”

  7. 三个囚犯问题,求解?


更新日志:

  • 2015-05-20 增加三囚徒问题的解答
  • 2015-05-09 第一次撰写
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 今天看文章时无意间看到了一个词,“终生皆苦”。这个充满佛家意味的词,就像深山寺庙里厚重深邃的钟声,穿过我的胸膛,冲...
    十里云月阅读 384评论 0 0
  • ——产品例会有感 很多人在这个创业大潮中都显得跃跃欲试。认为就凭平时工作这股劲,再吃多一两倍的苦,成功指日可待。 ...
    三目杨戬阅读 593评论 1 2