PHPでモンティ・ホール問題をシミュレーション

(1/1)
モンティ・ホール問題
モンティ・ホール問題とは、条件付き確率に関する有名な問題であるが、これがアメリカのテレビ番組「Let's Make A Deal」の中でゲームとして行われたことから、番組司会者モンティ・ホールの名前をとって、そう名付けられた。

(2023年5月5日)グラフにフッタを追加した。
ゲームのルールは次の通り――
  1. プレイヤーの前にA・B・Cの3つのドアがあり、3つとも扉は閉じている。
  2. そのうち1つのドアの奥には景品の自動車があり、残りはハズレのヤギがいる。
  3. プレイヤーはドアを1つ選択する。ここではドアを開けない。
  4. 正解のドアを知っているモンティは、残ったドアのうちハズレのドアを開ける。
  5. モンティは「今なら選択を変更してもいいですよ」とプレイヤーに問いかける。
――プレイヤーは最初の選択を変更した方が得だろうか、変更しない方が得だろうか、どちらでも結果は変わらないだろうか。

目次

サンプル・プログラムの実行例

モンティ・ホール問題
サンプル・プログラムを実行すると、最初の選択を変更した方が得策であることが分かる。
ランダムに1万回のドア開け試行を行い、ドアを変えない場合のアタリ確率は  mimetex  に、変えた場合のアタリ確率は  mimetex  に収束することが分かる。試行回数は横軸に対数目盛として示している。
この結論については。いくつかの数学的解説があるが、条件付き確率の問題として、機械学習で紹介したベイズの定理を用いるのが王道である。

サンプル・プログラム

圧縮ファイルの内容
MontyHall.phpサンプル・プログラム本体
MontyHall.php 更新履歴
バージョン 更新日 内容
1.3.0 2023/05/05 グラフにフッタを追加
1.2 2022/04/17 PHP8対応,リファラ・チェック改良
1.1 2018/06/05 JpGraph 4.2.1対応
1.0 2018/02/11

準備

グラフ描画には JpGraph を利用する。導入方法については、「PHPとJpGraphで人口ピラミッドを表示する」を参照のこと。"jpgraph_log.php" は、対数グラフを描くために必要なモジュールだ。

  33: //エラー表示用フォント・ファイル名;各自の環境に合わせて
  34: define('FONT', '../../../../common/font/ipagp.ttf');
  35: 
  36: //JpGraph(各自の環境に合わせて)
  37: require_once('jpgraph/jpgraph.php');
  38: require_once('jpgraph/jpgraph_log.php');
  39: require_once('jpgraph/jpgraph_line.php');
  40: 
  41: //ドア識別子
  42: define('DOOR_LABEL', 'ABC');
  43: 
  44: //試行回数
  45: $Counter = 10000;

3つのドアA・B・Cは定数 DOOR_LABEL に、試行回数は変数 $Counter にあらかじめ用意しておく。これらは変更可能である。
ドア識別子をA・B・C・D‥‥のように増やすことも可能で、増やしたとしてもドアを変更した方がアタリ確率がアップすることが分かる。

解説:モンティ・ホール問題

 133: /**
 134:  * モンティ・ホール問題(1回)
 135:  * @param   なし
 136:  * @return  array(介入なし,介入あり)
 137: */
 138: function MontyHall_once() {
 139:     $doors = array();
 140:     makeDoors($doors);
 141: 
 142:     //プレイヤーがドアを1つ選ぶ
 143:     $user = getRandomDoor($doors);
 144:     $doors[$user]['status'] = TRUE;
 145: 
 146:     //モンティがドアを1つ開ける
 147:     make_seed();
 148:     do {
 149:         $monty = getRandomDoor($doors);
 150:     } while ($doors[$monty]['hit'] == TRUE);
 151:     $doors[$monty]['status'] = TRUE;
 152: 
 153:     //プレイヤーはドアを変えない
 154:     if ($doors[$user]['hit'])   $res0 = TRUE;
 155:     else                        $res0 = FALSE;
 156: 
 157:     //プレイヤーはドアを変える
 158:     $user = getRandomDoor($doors);
 159:     if ($doors[$user]['hit'])   $res1 = TRUE;
 160:     else                        $res1 = FALSE;
 161: 
 162:     return array($res0, $res1);
 163: }

ドアを開ける作業は、ユーザー関数 MontyHall_once で行う。
冒頭に記したゲームのルールを、そのままプログラムに落とし込んである。

 166: /**
 167:  * モンティ・ホール問題
 168:  * @param   int   $n = 試行回数
 169:  * @return  array 確率配列
 170: */
 171: function MontyHall($n) {
 172:     $prob = array();
 173:     $cnt0 = 0;
 174:     $cnt1 = 0;
 175: 
 176:     for ($i = 0$i < $n$i++) {
 177:         list($res0, $res1) = MontyHall_once();
 178:         if ($res0)      $cnt0++;
 179:         if ($res1)      $cnt1++;
 180:         $prob[$i][0] = (double)$cnt0 / ($i + 1);
 181:         $prob[$i][1] = (double)$cnt1 / ($i + 1);
 182:     }
 183: 
 184:     return $prob;
 185: }

試行回数だけ繰り返し、そのときの確率を配列に格納していくのがユーザー関数 MontyHall である。

解説:グラフを描く

 187: /**
 188:  * グラフ描画
 189:  * @param   array  $items データ
 190:  * @param   int    $year_min 最小年
 191:  * @param   int    $year_max 最大年
 192:  * @return  object graphオブジェクト
 193: */
 194: function drawGraph($items, $n) {
 195:     $plotdata = array();
 196: 
 197:     //プロット配列を作成
 198:     for ($i = 0$i < $n$i++) {
 199:         $data_x[$i]  = $i;                      //横軸
 200:         $data_y0[$i] = $items[$i][0];
 201:         $data_y1[$i] = $items[$i][1];
 202:     }
 203: 
 204:     //グラフ描画処理開始
 205:     $graph = new Graph(600, 500, 'auto');
 206:     $graph->SetScale('loglin', 0.0, 1.0);       //X軸:対数,Y軸:小数
 207:     $graph->SetBackgroundGradient('#FFFFCC','#FFFFCC', GRAD_HOR, BGRAD_MARGIN);
 208: 
 209:     //タイトル、凡例などの設定
 210:     $graph->img->SetImgFormat('png');
 211:     $graph->img->SetMargin(60, 60, 20, 40);
 212:     $graph->title->SetFont(FF_PGOTHIC, FS_NORMAL, 16);
 213:     $graph->title->Set('モンティ・ホール問題');
 214:     $graph->xaxis->title->SetFont(FF_PGOTHIC);
 215:     $graph->xaxis->SetTitle('試行回数','center');
 216:     $graph->yaxis->title->SetFont(FF_PGOTHIC);
 217:     $graph->yaxis->title->Set('当たり確率');
 218:     $graph->yscale->ticks->Set(0.1);
 219:     $graph->yaxis->SetTitleMargin(40);
 220:     //フッタを設定する.
 221:     $graph->footer->right->SetFont(FF_PGOTHIC, FS_NORMAL, 10);
 222:     $graph->footer->right->Set('produced by JpGraph');
 223: 
 224:     //ドアを変更しない場合
 225:     $plotdata[0] = new LinePlot($data_y0);
 226:     $plotdata[0]->SetLegend('ドアを変更しない');    //凡例
 227:     $graph->Add($plotdata[0]);
 228: 
 229:     //ドアを変更する場合
 230:     $plotdata[1] = new LinePlot($data_y1);
 231:     $plotdata[1]->SetLegend('ドアを変更する');      //凡例
 232:     $graph->Add($plotdata[1]);
 233: 
 234:     //色指定は最後に
 235:     $plotdata[0]->SetColor('#0000FF');          //色
 236:     $plotdata[0]->SetWeight(2);             //太さ
 237:     $plotdata[1]->SetColor('#FF0000');          //色
 238:     $plotdata[1]->SetWeight(2);             //太さ
 239: 
 240:     //凡例
 241:     $graph->legend->SetFont(FF_PGOTHIC);                    //フォント
 242:     $graph->legend->SetColumns(1);                          //表示列数
 243:     $graph->legend->SetPos(0.15, 0.85, 'right', 'bottom');  //位置
 244: 
 245:     return $graph;
 246: }

目的のグラフは、「PHPで対数グラフ(ムーアの法則)を描く」とは逆で、X軸が対数で、Y軸が確率という形である。ユーザー関数 drawGraph で描画する。

参考サイト

(この項おわり)
header