モンティ・ホール問題をPythonで検証してみる

こんにちはMIYACHIN(@_38ch)です。

突然ですが、みなさん、「モンティ・ホール問題」という確率論問題をご存知でしょうか。僕が新卒のころ、先輩のデータサイエンティストの方に教えてもらって面白いなと思った問題です。

この記事では、モンティ・ホール問題の概要と、実際にPythonを用いての実証方法に関してご紹介していきます。

「モンティ・ホール問題」とは?

もともとの起源は、モンティ・ホールという男性が司会を務める昔のアメリカのゲーム番組「Let’s make a deal」です。

その中でとあるゲームがあり、そのルールは以下のようになっています。

  • 挑戦者の前には3つの扉がある。
  • 3つのうち1つの向こう側には、景品の新車がある。
  • 3つのうち2つの向こう側には、はずれのヤギがいる。
  • 挑戦者は3つの扉のうち1つを選択する。
  • その扉を開く前に、モンティがはずれのヤギの扉を1つ開いて、挑戦者に見せる。
  • それを見て挑戦者は、はじめに選択した扉を変更できる

このような状況において、挑戦者は最初に選んだ扉を変更するべきかというのが「モンティ・ホール問題」の内容です。

直感と異なる実際の答え

さて、これを直感的に考えると、変更しても変更しなくても新車に当たる確率は1/3。だから変更する必要はない、という答えになるのではないでしょうか。

3つの扉があって、その中に1つだけ新車が入っており、それを当てるというゲームなので、それだけで考えると確率は1/3になるのは明白です。

しかし、驚いたことに、このゲームにおいてモンティ・ホールがハズレの扉を見せたあとに、扉を変更すると、挑戦者が新車を当てる可能性は2倍の2/3に跳ね上がります。

このことに関しては、1990年にMarilyn vos Savantというアメリカのコラムニストが提唱したのですが、多くの学者から反論を受けました。しかし、結局コンピュータによるシミュレーション実験により彼女の主張が正しいということが立証されました。

Pythonでゲームを300回プレイしてみる

この直感と異なる実際の答えを理解するための様々な考え方がいろんなWebサイトに公開されているので、ぜひ読んでみてください。

しかし、それでも本当なのと思う方もいるので、今回はこのゲームをPythonでプレイできるようなスクリプトを書いてみました。

挑戦者が最初にどの扉を選ぶのかをランダムで決定し、

(A)最後に扉を変更する戦略
(B)そのまま変更しない戦略

上記の戦略それぞれ300回ずつプレイし、どちらの戦略の方が勝率(新車を当てた回数 / 300)が高いのかを計測します。

使用するライブラリはこんな感じ

import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import random

ゲームをプレイして、結果をチェック。新車を見事新車を当てることができれば1を、ハズレの羊を引けば0をリターンする関数を書きます。

def playGame(is_change_door):
    # 扉を初期化
    doors = [{'is_car':0, 'is_your_choise': 0}, {'is_car':0, 'is_your_choise': 0}, {'is_car':0, 'is_your_choise': 0}]
    
    # 車の入っている扉を決定
    car_door_index = random.randint(0, 2)
    doors[car_door_index]['is_car'] = 1
    
    # 自分が選ぶ扉を決定
    your_door_index = random.randint(0, 2)
    doors[your_door_index]['is_your_choise'] = 1
    
    # モンティが羊の扉のうち、ひとつの扉を開ける(→選択肢として削除される)
    for index, door in enumerate(doors):
        if index != your_door_index and door['is_car'] != 1:
            del doors[index]
            break
            
    # 引数 is_change_doorがTrueの場合、残ったふたつの扉のうち、もう片方の扉へ変更する
    if is_change_door:
        for door in doors:
            door['is_your_choise'] = 1 if door['is_your_choise'] == 0 else 0
            
    # 車の入っている扉が、自分の選んだ扉と一致しているか確認
    result = 0
    for door in doors:
        if door['is_your_choise'] == 1 and door['is_car'] == door['is_your_choise']:
            result = 1
    return result    

それでは上記の関数を使って、戦略(A)戦略(B)をそれぞれ300回プレイし、新車を当てた回数をカウントします。

a_win_count = 0
b_win_count = 0
trial_count = 300

x = np.array([num+1 for num in range(trial_count)])
ay = np.array([])
by = np.array([])

for index in range(trial_count):
    a_win_count += playGame(True)
    ay = np.append(ay, a_win_count / (index+1))
    b_win_count += playGame(False)
    by = np.append(by, b_win_count / (index+1)) 

そして最後に、matplotlibを使って勝率の推移を折れ線グラフで描画してあげます。

sns.set_style('whitegrid')
fig = plt.figure(figsize=(15, 8))
plt.plot(x, ay, label="A")
plt.plot(x, by, label="B")
plt.xlabel('trial count', fontsize = 16)
plt.ylabel('win rate', fontsize = 16)
plt.tick_params(labelsize=14)
plt.legend(bbox_to_anchor=(1, 1), loc='right', borderaxespad=1, fontsize=18)
plt.show()

すると結果はこうなりました。

青線が扉を変更する戦略、赤線が扉を変更しない戦略

やはり、扉を変更する戦略Aの方が変更しない戦略に比べて、2倍勝率が高いことがわかります。300回試行した後には、最終的に、Aは66%、Bは33%あたりに勝率が収束しました。

直感的に理解できないことでも、こうやってプログラミングして、試行回数を多くして実験してみるとわかりやすいです。今回は「モンティ・ホール問題」について、実際にPythonでシミュレーションしてみました。