なんとなく直感に合うんだか合わないんだか分からないので、確率クイズの問題で定番になっているモンティ・ホール問題。
けれど、あなたは果たして、本当に理解しているか。
#モンティ・ホール問題
A, B, Cの3つのドアがある。3つのドアのうち、どれか1つだけが当たりで、残り2つははずれだ。
次の手順で、ゲームが行われる。
- プレイヤーは、3つのドアから1つを選ぶ
- 司会者のモンティさんは、ゲームを盛り上げるために、プレイヤーが選ばなかったドアをひとつ開く。モンティさんは事前に答えを知っていて、必ず、はずれのドアを開く
- 司会者のモンティさんは、プレイヤーに尋ねる「今なら、残りの1つのドアに変えることができます。変えますか? 変えませんか?」
問題:プレイヤーは、ドアを変えるべきか、変えるべきでないか。より具体的には、ドアを変えることにより、当たりを引く確率は上がるか、下がるか、変わらないか。
答え:プレイヤーはドアを変えるべきである。プレイヤーは、3つのドアから1つのドアを選んだのだから、選んだドアが当たりである確率は1/3である。また、選ばなかったドアのいずれかが当たりである確率は2/3である。
そして、モンティさんは、選ばなかったドアの片方を開けた。なので、残ったドアが当たりである確率は2/3である。
つまり、ドアを変えることで、当たりを引く確率は1/3から2/3に上がる。
#突風が吹いた場合のモンティ・ホール問題
では、モンティさんが開けたのではなくて、突風が吹いてドアが開いた場合はどうだろうか?
突風は、ゲームを盛り上げる目的ではなく、アクシデントなので、プレイヤーが選んだドアや、当たりのドアを開けてしまう可能性もある。
また、どのドアが開く確率も、同じであるものと仮定する。
- プレイヤーは、3つのドアから1つを選ぶ
- 司会者のモンティさんがゲームを盛り上げようとしたその時、突風が吹いて、いずれかのドアが開いてしまう
- もしも、1でプレイヤーが選んだドアが開いた場合、または、当たりのドアが開いた場合は、このゲームは、続行できないため中止となる
- もしも、プレイヤーが選ばなかったドアが開き、それがはずれだった場合は、ゲームは続行され、モンティさんは気を取り直して、プレイヤーに尋ねる「今なら、残りの1つのドアに変えることができます。変えますか? 変えませんか?」
問題:プレイヤーは、ドアを変えるべきか、変えるべきでないか。より具体的には、ドアを変えることにより、当たりを引く確率は上がるか、下がるか、変わらないか。
答え:ドアを変えても変えなくても、当たりを引く確率は同じ。え? 変えたら確率が2/3? 一体なんの話?
#お分かりいただけただろうか
モンティ・ホール問題をなんとなくで理解していた人は、突風が吹いた場合の結果を聞いて、納得いかないのではないだろうか。
開けたのがモンティさんだろうが、突風だろうが、プレイヤーが選ばなかった、はずれのドアが開いたんだから、結論は同じに決まってる。そう思ったのではないか。
#空飛ぶモンティ・パイソン
ここで、確率論について、ゴタゴタと語ってもいいだろう。
けれど、みなさんご存知の通り、モンティといえばパイソンだ。
ここでは、Pythonで、実際に、モンティ・ホール問題と、突風モンティ・ホール問題をシミュレーションすることで、強引に納得いただきたい。
import random
N = 100000
first_choice_is_atari = 0
second_choice_is_atari = 0
for _ in range(N):
# どれかが正解
atari = random.choice(["A", "B", "C"])
# 最初に、どれかを選ぶ
first_choice = random.choice(["A", "B", "C"])
# 当たりでも、選んだやつでもないものを、モンティさんが開ける
monty_opened = random.choice(list({"A", "B", "C"}.difference(atari).difference(first_choice)))
# そうすると、まだ開いてなくて、選んでないものは1つだけだよね?
second_choice_candidates = list({"A", "B", "C"}.difference(first_choice).difference(monty_opened))
assert len(second_choice_candidates) == 1
# 変えたいなら、変えてもいいよ
second_choice = second_choice_candidates[0]
if first_choice == atari:
first_choice_is_atari += 1
elif second_choice == atari:
second_choice_is_atari += 1
else:
raise ValueError("Unreachable")
# 変えなかった場合に当たりを引く確率, 変えた場合に当たりを引く確率
print(first_choice_is_atari / N, second_choice_is_atari / N)
効率などは考えずに、ひたすら、単純で分かりやすい書き方をした。
乱数を使っているので、正確ではないが、何度実行しても、だいたい、変えなかったら1/3、変えたら2/3くらいになるはずだ。
import random
N = 100000
first_choice_is_atari = 0
second_choice_is_atari = 0
game_canceled = 0
for _ in range(N):
# どれかが正解
atari = random.choice(["A", "B", "C"])
# どれかを選ぶ
first_choice = random.choice(["A", "B", "C"])
# 突風が吹いて、どれかが開く
wind_opened = random.choice(["A", "B", "C"])
# もしも、正解が開くか、最初に選んだのが開いた場合は、このゲームは中止となる
if wind_opened == atari or wind_opened == first_choice:
game_canceled += 1
continue
# ゲーム続行になった場合、まだ開いてなくて、選んでないものは1つだけだよね?
second_choice_candidates = list({"A", "B", "C"}.difference(first_choice).difference(wind_opened))
assert len(second_choice_candidates) == 1
# 変えたいなら、変えてもいいよ
second_choice = second_choice_candidates[0]
if first_choice == atari:
first_choice_is_atari += 1
elif second_choice == atari:
second_choice_is_atari += 1
else:
raise ValueError("Unreachable")
# 変えなかった場合に当たりを引く確率, 変えた場合に当たりを引く確率, ゲームが続行不可になる確率
print(first_choice_is_atari / N, second_choice_is_atari / N, game_canceled / N)
これもまた、やはり乱数なので正確ではないが、今度は、変えた場合も変えなかった場合も、だいたい22%くらいになるはずだ。
もう少しPythonに頑張ってもらおう。
今度は、乱数ではなく、全てのパターンを列挙することで、この22%がどこから出てくるのかを探ることにする。
from itertools import product
first_choice_is_atari = 0
second_choice_is_atari = 0
game_canceled = 0
print("|当たり|最初の選択|突風|結果 |")
print("|------|----------|----|--------|")
for atari, first_choice, wind_opened in product("ABC", repeat=3):
if atari == wind_opened or first_choice == wind_opened:
result = "中止 "
game_canceled += 1
elif atari == first_choice:
result = "変えない"
first_choice_is_atari += 1
else:
result = "変える "
second_choice_is_atari += 1
print("| {}| {}| {}|{}|".format(atari, first_choice, wind_opened, result))
print("")
total = first_choice_is_atari + second_choice_is_atari + game_canceled
print(first_choice_is_atari / total, second_choice_is_atari / total, game_canceled / total)
Markdown形式の表が出力される。
変えない方が当たりの場合(「変えない」)と、変えた方が当たりの場合(「変える」)とを数えてみると、6個ずつあることが分かる。変えても変えなくても、全部で27通りのうち、6個あるので、6/27 = 2/9 = 22.2222...%だ。
当たり | 最初の選択 | 突風 | 結果 |
---|---|---|---|
A | A | A | 中止 |
A | A | B | 変えない |
A | A | C | 変えない |
A | B | A | 中止 |
A | B | B | 中止 |
A | B | C | 変える |
A | C | A | 中止 |
A | C | B | 変える |
A | C | C | 中止 |
B | A | A | 中止 |
B | A | B | 中止 |
B | A | C | 変える |
B | B | A | 変えない |
B | B | B | 中止 |
B | B | C | 変えない |
B | C | A | 変える |
B | C | B | 中止 |
B | C | C | 中止 |
C | A | A | 中止 |
C | A | B | 変える |
C | A | C | 中止 |
C | B | A | 変える |
C | B | B | 中止 |
C | B | C | 中止 |
C | C | A | 変えない |
C | C | B | 変えない |
C | C | C | 中止 |
#確率は難しい。だから手を動かそう
確率の問題は難しい。そして、確率の問題を解いた答えが本当に合っているか間違っているかの確証を得るのは、もっと難しい。
けれど、そんなときこそ、プログラミング言語は心強いパートナーになってくれる。
実際に、ドアとモンティさんを用意して、ドアが開くまで突風を吹かせるのは、なかなか大変な作業になるが、コンピュータで乱数を振って、適当にシミュレーションをするのは、規模のあまり大きくない問題なら、すぐに試せる。
また、全パターンを列挙して数え上げる、というのは、さらに規模の小さい問題に限られるが、それでも、モンティ・ホール問題程度なら全然問題にならない。
分かった気分になったり、騙された気分になる前に、自分でやってみよう。違った世界が見えてくる、かもしれない。
ところで、余力のある人は、風が吹かないモンティ・ホール問題では、なぜドアを変えると確率が2/3になるのか、全パターン列挙で試してみてはいかがだろうか。