Up

PyGameによる強化学習シミュレーションの動的表示

 

 

R. S. Sutton & A. G. Barto (2018). Reinforcement learnig, 2nd ed. The MIT Press. pp. 25-32に説明されている強化学習のシミュレーションをPyGameを用いて動的に表示してみた。

先ず、アルゴリズムの確認のために、Sutton & Burto (2018)Figure 2.2の上のグラフを再現するスクリプトをリスト1に示すようにように作成した。なお、本ウェブサイトのスクリプトファイルは、まとめて圧縮ファイルscriptfiles.zipとしている。リスト1のスクリプトを実行すると図1のグラフを得た。図1のグラフは、SuttonらのFigure 2.2の上のものと同じと見做されるので、リスト1のスクリプトはSuttonらの説明のアルゴリズムを表しているものと考えた。

図1

 

リスト1のスクリプトは、Sutton Barto (2018)A simple bandit algorithm (p.32)に基づいており、変数名も出来るだけ合わせた。

強化を表すincremental formulaは、リスト1では次のコードで表されている。

 

            Qt[a] += (R - Qt[a]) / Nt[a]       #  平均rewardの更新

 

 

リスト1では、シミュレーションの結果の最後のまとめがrewardの平均値のグラフとして表示されるだけで、個々の機械(machine)の学習の途中経過は分からない。PythonのライブラリPyGameを利用して、途中経過をアニメーションのように表示するスクリプトをリスト2のように作成してみた。リスト2のスクリプトファイルは圧縮ファイルscriptfiles.zipにまとめた。

アルゴリズムは、基本的にはリスト1のスクリプトもリスト2のスクリプトも同じである。図1では個々のmachineの振る舞いが分からないので、リスト2では、個々のmachinerewardの軌跡も描画している。

まず、PyGameによる描画画面は、次のコードで設定している。

 

screen  = pg.display.set_mode((1200, 800))

 

用意した画面の大きさに合わせて、横軸と縦軸の単位を次の関数で調整している。

 

def x_scale(x):

    """

         [0, 1]区間を画面の横サイズに合わせて変換

    """

    return 200 + 800 * x

 

def y_scale(y):

    """

         [0, 1]区間を画面の縦サイズに合わせて変換

    """

    return 700 - 600 * y

 

シミュレーションはステップ単位で進むので、シミュレーションの開始の初期は図2のようなグラフになる。

図2

 

εの各値に対してn_mcs = 1000台のmachineが用意されているが、グラフの折れ線が重なり過ぎないように各εに対して10台のグラフが描かれている。

ステップが進むと図3のようになる。

図3

 

ステップは、n_simu = 1000ステップまで進む。

全ステップが終了すると、各εごとの1000台のmachine平均値のグラフが表示される(図4)。

図4

 

PyGameによるグラフ(図4の左側後ろのグラフ)ではepsilon=0.1のグラフが青色で分かり難い。Matplotlibによる描画(図4の右側前のグラフ)も用意した。Matplotlibによる描画では、各εに対応するmachineをランダムに10台選び、グラフの描画はalpha=0.1として線の透明度を調整している。

プログラムの終了は、matplotlibによる描画画面を閉じてから、PyGameによる描画画面を閉じる。

 

 

 

リスト1 シミュレーションの静的表示

 

"""

       R. S. Sutton and A. G. Barto (2018)

       Reinforcement Learnig, 2nd ed., 25-32.

"""

import numpy as np

import matplotlib.pyplot as plt

 

rng = np.random.default_rng(123)    #  シミュレーションの再現性のために値を指定して初期化

 

k = 10

RsDic = {}

Rsave = {}

n_simu = 1000

ep_lst = [0.0, 0.01, 0.1]

 

n_mcs = 1000

for ep in ep_lst:

    RsDic[ep] = []

    for i_mcs in range(n_mcs):

        if i_mcs % 100 == 0:

            print(f'{ep}/ep  {i_mcs}/{n_mcs}')

        Qt = np.zeros(k)      #  Mean reward actually received

        Nt = np.zeros(k)

        q_star = rng.normal(0, 1 , k)

        Rt = []

 

        for i in range(n_simu):

            if rng.uniform(0.0, 1.0) < ep:

                a = rng.choice(np.arange(k))   # Exploration

            else:

                a = np.argmax(Qt)              # Greedy

 

            Nt[a] += 1

            R = rng.normal(q_star[a], 1, 1)[0]

            Qt[a] += (R - Qt[a]) / Nt[a]       #  平均rewardの更新

            Rt.append(R)

        RsDic[ep].append(Rt)

ep_c = {0.0:'g', 0.01:'r', 0.1:'b'}

for ep in ep_lst:

    Rsave[ep] = np.mean(RsDic[ep], axis=0)

 

for ep in ep_lst:

    plt.plot(np.arange(n_simu), Rsave[ep], c=ep_c[ep], lw=2,

             label=fr'$\epsilon$={ep}')

plt.xlabel('Steps')

plt.ylabel('Average reward')

plt.legend()

plt.show()

 

 

 

リスト2 シミュレーションの動的表示

 

"""

       R. S. Sutton and A. G. Barto (2018)

       Reinforcement Learnig, 2nd ed., 25-32.

"""

import pygame as pg

import numpy as np

import matplotlib.pyplot as plt

import sys

 

rng = np.random.default_rng(123)   #  値を指定して乱数の初期化

 

k = 10             # k-armed testbed

n_steps = 1000     # ステップ数

n_mcns = 2000      # 条件当たりのmachineの数

 

pg.init()

screen  = pg.display.set_mode((1200, 800))

pg.display.set_caption('My Simple Simulation')

screen.fill((255,255,255))

pg.display.flip()

 

def x_scale(x):

    """

         [0, 1]区間を画面の横サイズに合わせて変換

    """

    return 200 + 800 * x

 

def y_scale(y):

    """

         [0, 1]区間を画面の縦サイズに合わせて変換

    """

    return 700 - 600 * y

 

"""

        座標軸の描画

"""

pg.draw.line(screen, (0,0,0), (x_scale(0), y_scale(0)),

                 (x_scale(1), y_scale(0)), 2)

 

pg.draw.line(screen, (0,0,0), (x_scale(0), y_scale(0)),

                 (x_scale(0), y_scale(1)), 2)

 

 

pg.draw.line(screen, (0,0,0), (x_scale(0), y_scale(0)),

             (x_scale(0), y_scale(-0.03)), 2)

pg.draw.line(screen, (0,0,0), (x_scale(1), y_scale(0)),

             (x_scale(1), y_scale(-0.03)), 2)

pg.draw.line(screen, (0,0,0), (x_scale(0), y_scale(0)),

             (x_scale(-0.02), y_scale(0)), 2)

 

my_font = pg.font.SysFont(None, 40)

img_str = my_font.render('1', True, (0,0,0), None)

rect_str = img_str.get_rect()

screen.blit(img_str, (x_scale(0)-rect_str.width//2, y_scale(0)+rect_str.height))

 

 

img_str = my_font.render(fr'{n_steps}', True, (0,0,0), None)

rect_str = img_str.get_rect()

screen.blit(img_str, (x_scale(1)-rect_str.width//2,

                      y_scale(0)+rect_str.height))

 

img_str = my_font.render('Steps', True, (0,0,0), None)

rect_str = img_str.get_rect()

screen.blit(img_str, (x_scale(0.5)-rect_str.width//2,

                      y_scale(0)+rect_str.height*2))

 

for iy in np.arange(-4, 6.1, 1):

    pg.draw.line(screen, (0,0,0), (x_scale(0), y_scale((iy+4.0)/10.0)),

                 (x_scale(-0.02), y_scale((iy+4.0)/10.0)), 2)

    img_str = my_font.render(fr'{iy:.1f}', True, (0,0,0), None)

    rect_str = img_str.get_rect()

    screen.blit(img_str, (x_scale(-0.03)-rect_str.width,

                          y_scale((iy+4.0)/10.0)-rect_str.height/2))

img_str = my_font.render('Indivisual and average rewards', True, (0,0,0), None)

img_str = pg.transform.rotate(img_str, 90)

rect_str = img_str.get_rect()

screen.blit(img_str, (x_scale(-0.1)-rect_str.width,

                      y_scale(0.5)-rect_str.height/2))

 

pg.display.flip()                        

 

"""

       Epsilonの値と表示色のリスト

"""

ep_lst = [0.0, 0.01, 0.1]

ep_clr = {0.0:(0,255,0), 0.01:(255,0,0), 0.1:(20,0,255)}

 

for i, ep in enumerate(ep_lst):

    img_str = my_font.render(f'epsilon={ep}', True, ep_clr[ep], None)

    rect_str = img_str.get_rect()

    screen.blit(img_str, (x_scale(i/3), y_scale(1.1)))

 

 

Rt = {}

for ep in ep_lst:

    Rt[ep] = {}

    for i_mcn in range(n_mcns):

        Rt[ep][i_mcn] = []

Nt = {}

for ep in ep_lst:

    Nt[ep] = {}

    for i_mcn in range(n_mcns):

        Nt[ep][i_mcn] = {}

        for a in range(k):

            Nt[ep][i_mcn][a] = 0

Qt = {}             #  Mean reward actually received

for ep in ep_lst:

    Qt[ep] = {}

    for i_mcn in range(n_mcns):

        Qt[ep][i_mcn] = np.zeros(k)

q_star = {}

for ep in ep_lst:

    q_star[ep] = {}

    for i_mcn in range(n_mcns):

        q_star[ep][i_mcn] = rng.normal(0, 1, k)

 

for i_step in range(n_steps):

    if i_step % 100 == 0:

        print(f'i_step = {i_step}/{n_steps}')

    for ep in ep_lst:

        for i_mcn in range(n_mcns):

            if rng.uniform(0.0, 1.0) < ep:

                a = rng.choice(np.arange(k))

            else:

                a = np.argmax(Qt[ep][i_mcn])

            Nt[ep][i_mcn][a] += 1

            R = rng.normal(q_star[ep][i_mcn][a], 1, 1)[0]

            Qt[ep][i_mcn][a] += (R - Qt[ep][i_mcn][a]) / Nt[ep][i_mcn][a]

            Rt[ep][i_mcn].append(R)

            if i_step > 0:

                if i_mcn <10:   #  先頭の10台のみReward曲線を描画

                    pg.draw.line(screen, ep_clr[ep],

                                 (x_scale((i_step)/n_steps),

                                  y_scale((Rt[ep][i_mcn][i_step-1]+4.0)/10.0)),

                                 (x_scale((i_step+1)/n_steps),

                                  y_scale((Rt[ep][i_mcn][i_step]+4.0)/10.0)), 1)

    pg.display.flip()

   

    for e in pg.event.get():   #  eventの処理で表示が止まるのを避ける

        if e.type == pg.QUIT:  #  Close Windowボタン

            pg.quit()

            sys.exit()

        if e.type == pg.KEYDOWN:

            if e.key == pg.K_ESCAPE:   #   Escape キー

                pg.quit()

                sys.exit()

"""

      平均reward曲線の表示

"""

for ep in ep_lst:

    Rtemp = []

    for i_mcn in range(n_mcns):

        Rtemp.append(Rt[ep][i_mcn])

    Rmean = np.mean(Rtemp, axis=0)

    for i_step in range(1, n_steps):

        pg.draw.line(screen, ep_clr[ep], 

                     (x_scale((i_step)/n_steps),

                      y_scale((Rmean[i_step-1]+1.0)/4)),

                     (x_scale((i_step+1)/n_steps),

                      y_scale((Rmean[i_step]+1.0)/4)), 3)

 

pg.display.flip()

 

"""

       Matplotlibによる描画

"""

plt.figure(figsize=(8, 6))

 

for ep in ep_lst:

    """

          epsilon条件において10台をランダムに選んで表示

    """

    for i_mcn in rng.choice(range(n_mcns), 10, replace=False, ): 

        plt.plot(Rt[ep][i_mcn], c=np.array(ep_clr[ep])/255.0, alpha=0.1)

"""

    平均reward曲線の描画

"""

for ep in ep_lst:

    Rtemp = []

    for i_mcn in range(n_mcns):

        Rtemp.append(Rt[ep][i_mcn])

    Rmean = np.mean(Rtemp, axis=0)

    plt.plot(range(len(Rmean)), Rmean, c=np.array(ep_clr[ep])/255.0, lw=3,

             label=fr'$\epsilon$={ep}')

plt.xlabel('Steps', fontsize=18)

plt.ylabel('Individual and average rewards', fontsize=18)

plt.legend(fontsize=16)

plt.show()

 

ck_0 = True

while ck_0:

    for e in pg.event.get():

        if e.type == pg.QUIT:

            ck_0 = False

        if e.type == pg.KEYDOWN:

            if e.key == pg.K_ESCAPE:   #   Escape キー

                ck_0 = False

 

pg.quit()   

 

 

 

Home