SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

モンティ・ホール問題を自分なりに検証する/Pythonサンプル

f:id:arakan_no_boku:20200128224622p:plain

目次

モンティ・ホール問題を自分なりに検証する/Pythonサンプル

数学の本を読んでて「モンティ・ホール問題」に興味をひかれました。

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

もともとは、こんなゲームです。

3つの箱があり、その内の1つに景品が入ってます。残りは空箱です。

プレイヤーは3つの箱のどれかを選ぶことができ、その中に景品があれば、それをもらえるものとします。

いま、プレーヤーが1つの箱を選択した後、景品がどの箱にはいっているかを知っている司会者(モンティ)が、残りの2箱のうち、1つの空き箱を開けます。

ここでプレーヤーは、最初に選んだ箱のままでもよいし、残っている箱に変更しても良いと告げられます。

さて、どうしますか?

普通に考えれば、変えても変えなくても同じのはずです。

3つの箱のどれかひとつなので、最初の段階では「三分の一」。

このうち、司会者がひとつの箱を開けるので残りは二つ。

変えても、変えなくても確率は「二分の一」になるだけのはずですから。

ところが。

マリリン・ボス・サバント(Marilyn vos Savant)さんという女性が、変えた方が2倍の確率で当たると発言したので、さあ大変。

当時の学者さんとかが「確率の勉強をしなおせ。この素人が・・」みたいに反論して、大論争が巻き起こったけど、結局、彼女が正しかった・・。

とまあ。

そんな問題が「モンティ・ホール問題」です。

答えがわかっているのに、直感的には違和感がある

 既に答えがでている問題ではあります。

でも、直感的には違和感があります。

だって。

以下のようにA,B,Cの箱があったとき。

f:id:arakan_no_boku:20170220220134j:plain

当然ですが、選んだ箱が当たりの確率は「三分の一」です。

それを頭にいれたうえで

  • プレイヤーが選択する箱(どれを選んでも良い)。
  • 当たりの箱(どれか一つ。司会者は知っている。)
  • 司会者がオープンできる箱(プレイヤーの選んだ箱と当たり以外の箱)

の3つに場合分けしてみます。

例えば。

プレイヤーがAを選び、当たりもAだった場合は、司会者はB,Cのどちらからの箱をあけることができますが、その時、当たりがBだったら、司会者はCの箱しかオープンできません・・みたいな感じです。

そのそれぞれのケースで、プレイヤーが最初に選んだ箱を変更した場合としなかった場合の「当たり」と「はずれ」の表を書いてみます。 

すると。

f:id:arakan_no_boku:20200128233939p:plain

こんな感じになります。

これでは変更しても、しなくても当たりの確率は同じに見えます。

でも・・、正解はそうじゃないので、上記のアプローチは何かが間違っているのです。

問いかけ自体が「二択である点がポイントだと考える

上記のアプローチのどこが間違っているのかを、よーく考えてみれました。

それで気づいたのは。

この問いかけ自体が「変更する」か「変更しないか」の二択だということです。

この判断は、例えばプレーヤーが最初に選択した箱が当たりの場合、司会者のオープンできる2通りのどちらをオープンしたとしても変わりません。

なので。

司会者が2つの箱のどちらかを選んだとしても、当たり・はずれの確率に影響をあたえません。

そう考えると、実際の確率は以下の表みたいになります。

f:id:arakan_no_boku:20200128235448p:plain

そうすると。

確かに、変更したら「三分の二」、しなかったら「三分の一」。

変更するほうが2倍の確率になってます。

pythonプログラムの仕様・ソース・結果

上記をpythonで試してみます。

とりあえず、BOXをこんな感じでシュミレート。

box = ['A', 'B', 'C'] 

 司会者が選ぶことが可能なBOXは

openable['AA'] = ['B', 'C']

 みたいに定義して、プレイヤーが「A」を選択、当たりが「A」なら、「B」または「C」が選択可能・・てな感じで全パターンを用意します。

あと、プレーヤーが変更した時に選択できるBOXは

changed['AAB'] = 'C'

 みたいに、プレーヤー選択「A」、当たりが「A」、司会者が「B」をオープンしたらプレーヤーが変更できるのは「C」です・・みたいに、これも全パターン定義します。

あとは、それを10万回くらい、ランダムに選択してやってみるというプログラムがこちらです。

import random as rnd

box = ['A', 'B', 'C']

openable = {}
openable['AA'] = ['B', 'C']
openable['AB'] = ['C']
openable['AC'] = ['B']
openable['BB'] = ['A', 'C']
openable['BA'] = ['C']
openable['BC'] = ['A']
openable['CC'] = ['A', 'B']
openable['CA'] = ['B']
openable['CB'] = ['A']

changed = {}
changed['AAB'] = 'C'
changed['AAC'] = 'B'
changed['ABC'] = 'B'
changed['ACB'] = 'C'
changed['BBA'] = 'C'
changed['BBC'] = 'A'
changed['BAC'] = 'A'
changed['BCA'] = 'C'
changed['CCA'] = 'B'
changed['CCB'] = 'A'
changed['CBA'] = 'B'
changed['CAB'] = 'A'

count = {}
count['change'] = 0
count['stay'] = 0

for i in range(100000):
    hit = rnd.randint(1, 999) % 3
    sel = rnd.randint(1, 999) % 3
    if (hit == sel):
        opn = rnd.randint(1, 99) % 2
    else:
        opn = 0
    keystr = box[sel] + box[hit]
    changedbox = changed[keystr + openable[keystr][opn]]
    if (box[hit] == changedbox):
        count['change'] += 1
    if (sel == hit):
        count['stay'] += 1

print('changed:' + str(count['change']))
print('stay:' + str(count['stay']))

この位なら処理は一瞬で終わります。

結果は。

f:id:arakan_no_boku:20200129214924p:plain

みたいな感じになります。

変更した方が「6605回当たり」で、変更しない場合が「3395回当たり」ということなので、まあ大体2倍くらい変更した方が当たってます。

まあまあ、納得できました。

ではでは。