擬似パラドックスであるところの「モンティ・ホール問題」をRubyで検証する
はじめに
世の中には擬似パラドックスと呼ばれるものがある。擬似パラドックスの定義とは、「直感的な感覚と、理論的な確率にズレがあるように見える事象のこと」で、それの代表としてモンティ・ホール問題というものがある。この問題は、たびたび取り上げられることがあり、例えば映画の中だと『ラスベガスをやっつけろ』という中で、主人公が数学に強いことの演出として使われたりしている。
元々、このパラドックスの問題については知っていたのだけれど、ディスカバリーチャンネルの『怪しい伝説』を見る機会があって、その中で、このモンティー・ホール問題について実験していたので視聴していたところ、試行回数によって歴然とした差が出てきて、俄然興味が沸いてきた。これは自分でも検証してみなければ、と思い、実際にコードを書いてみたのが、今回の記事。
そもそもモンティ・ホール問題とは何か
概要
モンティ・ホール問題の詳細については、上記のリンクを参考にしてもらうとして、ここではキモだけを説明する。
- 前提: ドアが三つある。その中のドアのうち、一つが当たりである。当たりのドアはランダムに決定される。勝利条件は、このドアの中から当たりのドアを見つけることである。
- ルール1: プレイヤーは、ドアを一つ選ぶ。
- ルール2: そのあと、選んだドア以外の、外れのドアが一つ開く。
- ルール3: プレイヤーは、外れのドアが一つ開いたあとに、ドアを変更するかどうかが選べる。
なぜ擬似パラドックスと呼ばれるのか
なぜ擬似パラドックスと呼ばれるかについては、下のことが言える。
- 最初のドアについても、外れのドアが一つ開かれたあとに、次のドアを選ぶにしても、三つのドアの中からドアを選ぶことには変わりがないので、確率は変動しないように思える。
- 最初のドアと外れのドアが一つ開かれたあとに、確率が変化しないように思える。
という二つのよる、というのが、「自分の直感」として、一瞬「変えないほうがいいのではないか」と思ってしまう理由だと思う。
検証用のコードを書いてみる
数学的な解説は上を参考にしてもらうとして、今回は、これを検証するためのRubyのコードを書くことだ。そのほうが、直感的にもわかりやすい筈だ。
class MontiDoor def initialize @door = [false, false, true].shuffle end def choice_door Random::rand(3).to_i end def choice_one @choice = choice_door return self end def open_door @open_door = choice_door while (@open_door == @choice || @door[@open_door]) do @open_door = choice_door end return self end def choice_switch @previous_choice = @choice @door.each_with_index do |door, x| if !(x == @previous_choice || x == @open_door) @choice = x end end return self end def debug_print puts "======================================" puts "Previous Choice :: #{@previous_choice}" puts "Open Door :: #{@open_door}" puts "Choice Door :: #{@choice}" puts "Door :: #{@door}" puts "======================================" return self end def win? @door[@choice] end end class MontiDoorManager attr_accessor :stay_doors, :switch_doors def initialize @stay_doors = [*0..1000].map do doortest = MontiDoor.new doortest.choice_one.win? end.to_a.select {|x| x}.length @switch_doors = [*0..1000].map do doortest = MontiDoor.new doortest .choice_one .open_door .choice_switch .win? end.to_a.select {|x| x}.length end def which_win? if @stay_doors > @switch_doors "Stay" elsif @stay_doors < @switch_doors "Swich" else "Draw" end end end
自分自身によるコードレビュー
細かい点になるけれど、この実装についてのポイントと、本当はこうするべきだろうという一つについて指摘しておく。
「答え」を選んでいた場合は、開くドアは「2つのうち1つ」になるが、外れを選んでいた場合は、「当たり以外」になるので、開くドアが「1つ」に限定される。上のコードでは、それが考慮されておらず、闇雲にランダムに選択することになっている(綺麗にやるならば、この辺りを考慮に入れて実装したほうがいい)。
もう一つとしては、これは検証用のコードなので、文字列を返すメソッドを作り、あとで結果をselect
によって出力している。下のような感じ。
puts wins.select {|x| "Stay" == x}.length puts wins.select {|x| "Swich" == x}.length
現実的には、定数宣言を行ったあとに(つまりSTAY = "Stay"
)、それを参照するようにしたほうがいい。文字列比較にしろ、マジックナンバーにしろ、タイプミスのときに気がつきやすい(エラーになるため)のは、定数を利用してコードを組むことだろう。
あと、MontiDoorManager
のselect
部分、つまりselect { |x| x }
は流石にエレガンスではない。
ちなみに、上の実装において、stay_door
の場合において、open_door
メソッドを経路していないのは、open_door
の部分において、@choice
の変化に影響を与えないので、今回の検証用コードにおいては、このメソッドを経路していない。実験的にopen_door
を経路したほうが良いと感じる場合には、元来的にはopen_door
を経路したほうがいいように感じる。
あと気になるところとしては、インスタンスを無駄に作っている感じもしなくはない。今回は検証用なので、その辺の効率性を考えなかったけれど、本来ならインスタンスを一つ作ってそれを振りなおすみたいな実装のほうがよかったかもしれないとは思う。
結果
結果としては歴然としていて、1000回 * 100回の試行のうち、ドアの選択をそのままにする場合は、ドアを変更するときに比べて、一度も勝利することは無かった。
また数学の検証通り、ドアの選択をそのままにする場合、330回前後の勝率を記録するが、ドアを変更したときには、660回前後の勝率を記録していた。この計測を見た限り、確かに「間違った直感」通りではないことが判明するし、そもそもの確率論的な結論として「勝率が2倍になる」ということが正しかったことがわかる。
参考までに、「ドアの選択を変えなかった場合」と「ドアの選択を変えた場合」の勝率の出力ログについては下のようになった。途中から抜粋する。
… … … Result No.60 >> Stay :: 323 / Switch :: 676 Result No.61 >> Stay :: 322 / Switch :: 666 Result No.62 >> Stay :: 355 / Switch :: 673 Result No.63 >> Stay :: 340 / Switch :: 656 Result No.64 >> Stay :: 328 / Switch :: 677 Result No.65 >> Stay :: 316 / Switch :: 671 Result No.66 >> Stay :: 330 / Switch :: 673 Result No.67 >> Stay :: 343 / Switch :: 664 Result No.68 >> Stay :: 327 / Switch :: 673 Result No.69 >> Stay :: 327 / Switch :: 677 Result No.70 >> Stay :: 318 / Switch :: 678 … … …
まとめ
個人的には、こういった「擬似パラドックス」が好きで、たまに集めたりしているのだけれど、今回のものに関しては、頭の中では「そういうものだ」と知っていても、自分でコードを書いて確かめてみると「なるほど」と納得できて良かった。
プログラマというかエンジニアの格言の一つとして「測定せよ」というのがあるけれども、こういうパラドックスを集めていると、直感的な部分に誤りがあり、ちゃんとデータと照らし合わせてみることの重要性がわかったりする。案外、人間の推測というのは間違うことがあり、そういうのは、確かに創造性にとっては重要な部分であることは認めつつ、こういう問題解決の場合には、直感とデータを照らし合わせるということが重要になると思う。
また、こういう確率的な擬似パラドックスに対して、プログラムを通じて検証ができるのも、プログラムが書ける(この程度ならExcelのマクロでもいいかもしれない)ことの利点なので、色々な擬似パラドックスについてコードを書いていくのも楽しいかもしれない。
というわけで、自分が得意なプログラミング言語で、上記のモンティ・ホール問題について解いてみるのは面白いかもしれないので、是非練習用課題として活用してください。それではまた。