用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閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 今天看文章時無意間看到了一個詞,“終生皆苦”。這個充滿佛家意味的詞,就像深山寺廟里厚重深邃的鐘聲,穿過我的胸膛,沖...
    十里云月閱讀 396評論 0 0
  • ——產品例會有感 很多人在這個創業大潮中都顯得躍躍欲試。認為就憑平時工作這股勁,再吃多一兩倍的苦,成功指日可待。 ...
    三目楊戩閱讀 610評論 1 2