用python驗證蒙提霍爾問題

最初看到這個問題是初中的時候買了一本有關數學謎題的書裏面機率論的一張的課後拓展就是說到三門問題,當時做爲一個擴展閱讀看了一下,裏面說到了一個世界智商最高的女人秒殺了美國一大羣的數學高材生的精彩故事(比較誇張),當時對這個問題也是似懂非懂。html

什麼是蒙提霍爾問題?

蒙提霍爾

蒙提霍爾問題,亦稱爲蒙特霍問題或三門問題(英文:Monty Hall problem),是一個源自博弈論的數學遊戲問題,大體出自美國的電視遊戲節目Let's Make a Deal。問題的名字來自該節目的主持人蒙提·霍爾(Monty Hall)。python

最初的表述是:編程

參賽者會看見三扇關閉了的門,其中一扇的後面有一輛汽車,選中後面有車的那扇門就能夠贏得該汽車,而另外兩扇門後面則各藏有一隻山羊。當參賽者選定了一扇門,但未去開啓它的時候,節目主持人開啓剩下兩扇門的其中一扇,露出其中一隻山羊。主持人其後會問參賽者要不要換另外一扇仍然關上的門。
問題是:換另外一扇門會否增長參賽者贏得汽車的機會率?app

這個古老的問題一經提出就引發了劇烈的爭論,有人認爲換與不換最終獲得車的機率都是\(\frac{1}{2}\),有人認爲換門以後獲得車的機率更大,應該選擇換門以後獲得車的機率爲\(\frac{2}{3}\)在撰寫這篇文章的時候在果殼上還有人在爲此爭吵,知乎上也有許多關於這方面的討論,其實這些爭論不少狀況下都是因這個問題的模糊表述所引發的,關鍵點在於主持人對於門後的狀況是否瞭解dom

  1. 若是主持人事先知道哪一個門裏有山羊而且他特地選擇了有山羊的門打開了,那麼參賽者應該換另外一扇門,這能夠將他勝利的機率從\(\frac{1}{3}\)升到\(\frac{2}{3}\)
  2. 若是主持人事先不知道哪一個門裏有山羊或者他只是隨機的選擇了一個門,但事實發現裏面剛好是山羊。這時候參賽者沒有換門的必要,勝利機率老是\(\frac{1}{2}\)

爲了後續的討論,這裏採用維基百科上對於這一個問題的不含糊的定義ide

嚴格的表述以下:post

  • 參賽者在三扇門中挑選一扇。他並不知道內裏有什麼。
  • 主持人知道每扇門後面有什麼。
  • 主持人必須開啓剩下的其中一扇門,而且必須提供換門的機會。
  • 主持人永遠都會挑一扇有山羊的門。
    • 若是參賽者挑了一扇有山羊的門,主持人必須挑另外一扇有山羊的門。
    • 若是參賽者挑了一扇有汽車的門,主持人隨機在另外兩扇門中挑一扇有山羊的門。
  • 參賽者會被問是否保持他的原來選擇,仍是轉而選擇剩下的那一道門。

那麼這個問題這能夠很好的理解了,引用維基的一幅圖片解析:
蒙提霍爾解答spa

有三種可能的狀況,所有都有相等的可能性(\(\frac{1}{3}\)):日誌

  • 參賽者挑汽車,主持人挑兩頭羊的任何一頭。轉換將失敗。
  • 參賽者挑A羊,主持人挑B羊。轉換將贏得汽車。
  • 參賽者挑B羊,主持人挑A羊。轉換將贏得汽車。

因此玩家選擇換門以後獲勝的機率應爲\(\frac{2}{3}\)code

證實?

蒙提霍爾解答
定義:

  • 事件A爲一開始玩家選擇的一扇門
  • 事件H爲最後門後的結果

  • 若是是選擇不換門的策略
    $P \left(H=car \right) = P \left(A=car \right) = \frac{1}{3} $
    由於選擇的是不交換的策略,全部只有一開始選中的是汽車,最後才能選中汽車。

  • 選擇交換門的策略
    $P \left(H=car \right) = P \left(A=sheep \right) = \frac{2}{3} $
    由於選擇的是交換的策略,全部只有一開始選中的是羊,最後才能選中汽車。

程序驗證

實踐是檢驗真理的惟一標準,在流言終結者看到他們人工重複這個實驗區驗證,發現這樣很浪費時間。何經過計算機去去模擬這一段過程呢?
下面使用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()

結果:
此處輸入圖片的描述
機率分佈:
此處輸入圖片的描述
成功的機率均值在 \(\frac{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

一樣的方法繪圖獲得結果:
此處輸入圖片的描述
機率分佈:
此處輸入圖片的描述
成功的機率均值在 \(\frac{2}{3}\) 附近

經過上面的分析與模擬可知最佳的策略固然就是換門。

更加深刻的討論

  • 若是門的數量不止是3個,若是是50扇門呢?

此處輸入圖片的描述
這種狀況下,主持人打開48扇都是羊的門後,再給你選擇,不少人這個時候應該就不會固守那\(\frac{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種策略,成功的機率和硬幣有關,也就是\(\frac1 2\),這種狀況就是從剩下的門中隨機選擇一扇,這個策略從上面分析來看不是最好的,可是比不改變的策略要好。
程序的模擬結果:
此處輸入圖片的描述
此處輸入圖片的描述

  • 好比門意外打開的狀況呢,也就是上面描述的第二種狀況(主持在不知門後的狀況下打開門呢)?

這種狀況下其實就是一個條件機率,事件A是玩家最後開到的是車,事件B是主持人打開的門是羊。
\[ P(A|B) = \dfrac{P(B|A) \cdot P(A) }{P(B)} \]
由於只有主持人開到是羊的狀況下,玩家纔有可能開到車因此 \(P(B|A) = 1\)
設玩家第一次選擇的門爲事件C

  • 不交換策略下的條件機率是:
    \[ P(B) = P(C='汽車') + P(C='羊') \times \frac {1}{2} \Rightarrow P(B)= \frac{1}{3} + \frac{1}{3} = \frac{2}{3} \]

\[ P(A) = P(C='汽車') = \frac{1}{3} \]

\[ P(A|B) = \dfrac{P(A) }{P(B) } = \dfrac{\frac{1}{3}}{\frac{2}{3}}= \frac{1}{2} \]

  • 交換策略下的條件機率是:
    \[ P(B) = P(C='汽車') + P(C='羊') \times \frac {1}{2} \Rightarrow P(B)= \frac{1}{3} + \frac{1}{3} = \frac{2}{3} \]

\[ P(A) = P(C='羊') \times \frac{1}{2} = \frac{1}{3} \]

\[ P(A|B) = \dfrac{P(A) }{P(B) } = \dfrac{\frac{1}{3}}{\frac{2}{3}}= \frac{1}{2} \]
所以在主持人不知道門後的狀況下打開一扇,而後發現門後是羊的狀況下,換門與不換門最終的機率都是\(\frac{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 :表明亞當被釋放。

\[ P(A) = \frac{1}{2} \]
\[ P(A \cap B) = \frac{1}{3} \]
\[ P(B|A)=\frac{P(A \cap B)}{P(A)}= \frac{2}{3} \]
那何時纔是1/2的機率呢?
規則3更改成:若是(查爾斯,比爾)被釋放,那麼獄卒會告訴亞當"比爾被釋放"
這個時候計算就是: \[ P(B|A)=\frac{P(A \cap B)}{P(A)}= \frac{\frac{1}{3}}{\frac{2}{3}} =\frac{1}{2} \]

那若是規則3改成:若是(查爾斯,比爾)被釋放,那麼獄卒會告訴亞當"查爾斯被釋放"
這個時候:亞當被釋放的機率就會變爲1
問題在於規則2和規則3下說"比爾被釋放"不是等機率發生的。

相似的問題還有

  • 拋兩枚硬幣其中有一枚硬幣是正面,問兩枚硬幣都是正面的機率是?
  • 拋兩枚硬幣其中第一枚硬幣是正面,問兩枚硬幣都是正面的機率是?

the end.


參考:

  1. 蒙提霍爾問題 - 維基百科,自由的百科全書

  2. 三扇門問題 | 左岸讀書

  3. 蒙提霍爾問題(又稱三門問題、山羊汽車問題)的正解是什麼?

  4. 趣味編程:三門問題

  5. 三門問題及相關

  6. 換仍是不換?爭議從未中止過的三門問題

  7. 在「三門問題」中,參與者應該選擇「換」仍是「不換」?主持人是否知道門後情形對結論有何影響?
  8. THE MONTY HALL PROBLEM
  9. 流言終結者第九季
  10. 某個家庭中有 2 個小孩,已知其中一個是女孩,則另外一個是男孩的機率是多少?-知乎
  11. 從貝葉斯定律的角度理解「蒙提霍爾問題」和「三個囚犯問題」
  12. 三個囚犯問題,求解?


更新日誌:

  • 2015-05-20 增長三囚徒問題的解答
  • 2015-05-09 第一次撰寫
相關文章
相關標籤/搜索