実際にシミュレーションによる計算をしてみることで答えを確信できます。
実際に男の子/女の子をランダムに計算し、どちらかが男の子だったときにもう一方の性別を調べます。2人とも男の子だったとき、どちらを選んだ場合でももう一方が男の子であることは確かなのですが、問題の条件に忠実に従うように、ランダムに一方を選んでからもう一方の性別を調べるようにしています。
実行結果は次のようになります。
シミュレーションでも約33%(1/3)という答えを得ることができました。
火曜日に生まれた男の子問題
先の例を少し複雑にしたものとして、「2人の子どもの一方が男の子で火曜日生まれの場合、もう一方も男の子である確率は?」という問題を考えてみましょう。「火曜日生まれ」という条件が意味不明なので、この問題の答えは1/2か1/3だと考えるのが普通ですが、あらゆる可能性をきちんと考えて計算すると確率は13/27=0.48148になることがわかります。
実際にシミュレーションするRubyプログラムは次のようになります。
実行結果は次のようになります。
確率は1/2より少しだけ小さな値になることがわかります。
モンティ・ホール問題
確率の見積りを誰もが間違えやすい問題として、「モンティ・ホール問題」が有名です。これはモンティ・ホールという司会者のゲームショー番組に由来するもので、次のようなものです。
「プレイヤーの前に3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろにはヤギ(はずれを意味する)がいる。プレイヤーは新車のドアを当てると新車がもらえる。プレイヤーが1つのドアを選択した後、モンティが残りのドアのうちヤギがいるドアを開けてヤギを見せる。
ここでプレイヤーは最初に選んだドアを、残っている開けられていないドアに変更しても良いと言われる。プレイヤーはドアを変更すべきだろうか?」
──「モンティ・ホール問題」『Wikipedia日本語版』
2011年1月24日 12:12(UTC)
直感的には、ドアを変更してもしなくても、当たりの確率は1/2だと思えます。
この問題は非常に勘違いしやすいものであり、有名な数学者でも間違えたという逸話が残っています。こういう問題を間違えずに解くためには、理屈を考えるだけでは不十分で、乱数を使ったシミュレーションで確認するのが確実だと思われます。
この問題を忠実に再現するRubyプログラムを書くと次のようになります。
これを実行すると、選択を変更しない場合は当たりを引く確率がおよそ1/3である(選択を変更すれば当たりを引く確率が2/3に高まる)ことが明らかになります。
プログラムを見ると、モンティの行為はプレイヤーの当たりハズレに何の効果も与えていないことが明白なので、モンティが何をしても当たりの確率が1/3のまま変化しないことも明らかですが、頭の中だけで考えると確率が1/2になったように勘違いしてしまうことが多いようです。
確率の計算をするとき、少しでも根拠に疑問がある場合は、乱数を用いたシミュレーションによって確認することが有効でしょう。
乱数とランダム感
「ランダムなもの」と「ランダムに感じられるもの」では微妙な違いがあります。
ランダムなのにランダムではなく感じる
完全にランダムな数値列であっても、パターンがあるように感じてしまう場合があります。
円周率
たとえば円周率の数字列は乱数列であるはずですが、小数点以下30桁以上も「0」が出現しませんし、次のような繰り返しパターンが見られるので、「本当にランダムなのか?」と疑ってしまうかもしれません。
ランダムな数字
次のRubyプログラムを実行して200個の数字をランダムに出力します。
すると、たとえば次のような数字列が得られます。同じ数字が続いたり集中的に出現したりしている場所が意外と多いことがわかります。
ランダムな二次元座標
また、次のようなプログラムでランダムに二次元座標を生成してプロットすると図2のようになりますが、座標の分布には偏りがあるように感じてしまいます。
ランダムなスライドショー
10枚の写真をランダムにスライドショー表示しようとする場合、現在の写真と同じ写真が次も表示される確率は1/10ですが、同じ写真が続けて表示されることが多いと、ランダム性が低いように感じられてしまいます。ランダムに写真が表示されるように感じるためには、同じような写真が続けて表示されないような工夫が必要です。iPodのような音楽再生機器には「シャッフル再生」という機能がありますが、本当の乱数を使ってシャッフル再生を行うと同じ曲が連続して再生されることが多々あることになり、選曲がランダムでないと感じられてしまうので、そうならない工夫がされているようです。本当にランダムな値は一様に分布することはなく、片寄りが見られる場合も多いので、人間にとってランダムに感じられるようにするためには本当の乱数を利用するのではなく、ランダムに感じられるような数字列を使うのが効果的です。
N枚のスライドをランダムに再生するとき、すべてのスライドが表示されるにはかなり時間がかかるのが普通です。何枚表示したときすべてのスライドが表示されるのかを調べるRubyプログラムは次のようになります。
100枚の写真のスライドショーを行う場合、500枚ぐらいランダムに表示しても、なかなかすべての写真は表示されないことがわかります(図3)。
ニセ乱数
本当にランダムなものではなくランダムっぽく感じられる値を得るためには、最近出ていない値が出やすいようなニセ乱数を作るとよいでしょう。たとえば次のような工夫をします。
- 最近出た値は出さない
- 最近一度も出ていない値は出現確率を上げる
このような性質を持つニセ乱数関数rand2()を用いて、先ほどのスライドショープログラムを書き換えます。
rand()の代わりにrand2()を使うと、図4のようなヒストグラムが得られます。160回あたりにピークがあり、300回すればほぼ確実にすべてのスライドを表示できることがわかります。
二次元ニセ乱数
ランダムに感じる二次元表示を行いたい場合も、本当の乱数を使うよりもニセ乱数を利用するほうがランダム感が出ます。
図5の「文字列探しパズル」では普通の乱数を使って文字を並べているため、同じ文字が3個以上縦や横に並んでいる場所がいくつも存在し、ランダム感があまり感じられません。
一方、前述のニセ乱数と同じような方法を使って上下と同じ文字が出にくいようにすると、図6のようになります。図5よりもランダムさが大きいように見えるでしょう。
乱数の精度
乱数で簡単にいろいろなシミュレーションができることはわかりましたが、その結果はどの程度信用できるものでしょうか?
最近のプログラミング言語には優秀な乱数生成ライブラリがあるのが普通です。たとえばRuby 1.8以降のrand()関数はメルセンヌツイスターという優秀な乱数生成手法を利用していますが、古いライブラリでは問題が出ることもあります。たとえばCの古いrand()関数でランダムな座標を生成しようとすると、条件によっては乱数として利用できません。
上のプログラムをSnowLeopard上で実行して生成した座標をプロットすると、図7のように平行線上に点が並んでしまいます。
ここではrand()が小さな値を返したときだけプロットするという変な条件を付けているために目に見える形でこのような不具合が発生しています。今回の記事で紹介したような簡単なシミュレーションではこういう問題が出ることはめったにありませんが、複雑なシミュレーションを行うときは、予想外の問題が発生するのを避けるため、性質の良い乱数を使うように注意するのがよいでしょう。
この問題の場合はrand()をrandom()に変更するとこのようなことは起こりません。乱数を使って複雑なシミュレーションを行う場合は注意しましょう。
連載のおわりに
連載「データ発見隊」は今回が最終回になります。1年にわたって、ちょっと変わったデータの扱いに関する話題をいろいろ紹介してきました。ふだん普通に使っているファイルやデータでも、見方を変えることによって新たな活用ができる機会はまだまだ多いと思いますので、いろんな視点でデータを活用していただければと願っています。