用python验证蒙提霍尔问题

作者: 知曰 | 来源:发表于2015-05-10 14:12 被阅读2194次

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

    什么是蒙提霍尔问题?

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

    • 选择交换门的策略

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

    程序验证

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

    结果:


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


    设玩家第一次选择的门为事件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 第一次撰写

    相关文章

      网友评论

      • fd539a50cbf4:问题:如果我把门先打开,剩下两道让你选,再问你换不换,那换了之后概率有没有提升?为什么呢?那与原问题的区别在哪里?

      本文标题:用python验证蒙提霍尔问题

      本文链接:https://www.haomeiwen.com/subject/bxonfttx.html