Pythonプログラミングイントロダクション(16章) ジャンケンをモンテカルロ・シミュレーションしてみる

2020–01–04

16章ではモンテカルロ・シミュレーションについての説明になります。 モンテカルロ・シミュレーションって大そうな名前がついていますが、この章までにやってきたランダムネスを利用したシミュレーションのことをモンテカルロ・シミュレーションといいます。 この章でもサイコロゲームを題材にランダムネスを利用したモンテカルロシミュレーションの実装がいくつか例示されています。

ランダムネスで円周率を求めるプログラミングなんかは痺れました。

ジャンケンのシミュレーション

ここではジャンケンのモンテカルロ・シミュレーションをPythonで実装して理解を深めていきたいと思います。

import pylab
import random


def janken():
    JANKEN_PATTERN = ['G', 'C', 'P']
    while True:
        a_t = random.choice(JANKEN_PATTERN)
        b_t = random.choice(JANKEN_PATTERN)
        if a_t != b_t:
            break
    return janken_judge(a_t, b_t)


def janken_kai():
    JANKEN_WIN_PATTERN = ['G', 'G', 'C', 'P']
    JANKEN_LOSE_PATTERN = ['G', 'C', 'C', 'P']
    while True:
        a_t = random.choice(JANKEN_WIN_PATTERN)
        b_t = random.choice(JANKEN_LOSE_PATTERN)
        if a_t != b_t:
            break
    return janken_judge(a_t, b_t)


def janken_judge(a_t, b_t):
    """ a_tとb_tは違う値の想定。
    aが勝っていたらTrue, 負けていたらFalseを返す """

    win_pattern = [('G', 'C'), ('C', 'P'), ('P', 'G')]
    return (a_t, b_t) in win_pattern


def show_plot(trials, target):
    a_rate = []
    a_win = 0
    for k in range(1, trials + 1):
        if target():
            a_win += 1

        a_rate.append(a_win / k * 100)
    pylab.plot(a_rate)
    pylab.ylim(0, 100)
    pylab.xlim(1, trials)
    pylab.xlabel('times')
    pylab.ylabel('rate')
    pylab.title('Junken')
    pylab.show()


def get_stdDev(trials):
    a_rate = []
    for i in range(0, 100):
        a_win = 0
        for k in range(0, trials):
            if janken():
                a_win += 1
        a_rate.append(a_win / k * 100)
    return stdDev(a_rate)


def variance(X):
    """分散"""
    mean = sum(X)/len(X)
    tot = 0.0
    for x in X:
        tot += (x - mean)**2
    return tot/len(X)


def stdDev(X):
    """標準偏差(分散の平方根)"""
    return variance(X)**0.5


show_plot(1000, janken)
show_plot(1000, janken_kai)
print('10 trials stdDev: ', get_stdDev(10))
print('100 trials stdDev: ', get_stdDev(100))
print('1000 trials stdDev: ', get_stdDev(1000))	  

janken関数では、a,bそれぞれのグー(G)、チョキ(C)、パー(P)の中からランダムに選択した上で勝ち負けを判定しています。 あいこの場合はもう一度ランダムに選択しなおします。

完全にランダムにグー、チョキ、パーを選ぶ場合は試行回数が増えるについれて勝率は5割に近づいていきます。 15章で学んだ大数の法則の通りですね。

人間がジャンケンをする場合は完全にランダムとはいかず、グー、チョキ、パーのどれかに選択が偏ることがあるでしょう。 ランダムではありますが、aさんがグーを出しがち、bさんがチョキを出しがちな場合はaさんの勝率が上がっていきます。そんなjanken_kaiを作ってみました。 この場合はaさんの勝率が5割より上回っていますね。

続いて標準偏差を出してみました。 試行回数を増やすにつれ、標準偏差が減っていきます。

: 10 trials stdDev:  17.91612832955234
: 100 trials stdDev:  5.337158102067871
: 1000 trials stdDev:  1.6880153857382443	  

まとめ

これまで学んだ統計の知識を織り交ぜつつ、ジャンケンを題材にモンテカルロ・シミュレーションをやってみました。 ここ何章かは思ったより統計学の内容が多いことに若干戸惑いもありましたが、内容も楽しいですしトレンド的にも良い題材なのかと思います。 Pythonを学ぶついでに統計学も学べるお得な一冊だと思います。