からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

1.4.2:エージェントの実装【ゼロつく4のノート】

はじめに

 『ゼロから作るDeep Learning 4 ――強化学習編』の独学時のまとめノートです。初学者の補助となるようにゼロつくシリーズの4巻の内容に解説を加えていきます。本と一緒に読んでください。

 この記事は、1.4.2節の内容です。ε-greedy法により行動するエージェントを実装します。

【前節の内容】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この記事の内容】

1.4.2 エージェントの実装

 エージェント(プレーヤー)の機能を持つAgentクラスの処理を確認します。

 利用するライブラリを読み込みます。

# 利用するライブラリ
import numpy as np


・処理の確認

 Agentクラスの内部で行われる処理を確認します。Banditクラスについては「1.4.1:スロットマシンの実装【ゼロつく4のノート】 - からっぽのしょこ」を参照してください。

・更新メソッド

 まずは、1つのマシンのみをプレイすることを考えます。

 マシン番号を指定して繰り返しプレイし、価値の推定値(標本平均)を求めます。

# マシンの数を指定
arms = 10

# インスタンスを作成
bandit = Bandit(arms)

# マシン番号を指定
arm = 0

# arm番目のマシンの確率を確認
print('p =', bandit.rates[arm])

# 試行回数を指定
N = 10

# 値を初期化
Q = 0

# N回試行
for n in range(1, N+1):
    # arm番目のマシンをプレイ
    reward = bandit.play(arm)
    print('n =', n, ' reward =', reward)
    
    # n回目時点の推定価値(標本平均)を更新:式(1.5)
    Q += (reward - Q) / n
    print('Q =', np.round(Q, 3))
p = 0.6409563990499948
n = 1  reward = 1
Q = 1.0
n = 2  reward = 0
Q = 0.5
n = 3  reward = 0
Q = 0.333
n = 4  reward = 1
Q = 0.5
n = 5  reward = 1
Q = 0.6
n = 6  reward = 1
Q = 0.667
n = 7  reward = 0
Q = 0.571
n = 8  reward = 1
Q = 0.625
n = 9  reward = 1
Q = 0.667
n = 10  reward = 1
Q = 0.7

 スロットマシンの数を指定して、Banditクラスのインスタンスを作成します。
 プレイするマシン番号を指定して、Banditクラスのプレイメソッドplay()でランダムに報酬を得ます。
 得られた報酬を使って、価値の推定値(標本平均)を更新します。

 試行回数が増えるほど、標本平均Qが真の期待値rateに近付くのが分かります。この例では、報酬が0, 1なので真の期待値と確率が一致します。

 続いて、複数のマシンをランダムにプレイすることを考えます。

# マシンの数を指定
action_size = 10

# インスタンスを作成
bandit = Bandit(action_size)

# 各マシンの確率を確認
print('p =', np.round(bandit.rates, 2))

# 試行回数を指定
N = 10

# 値を初期化
Qs = np.zeros(N)
ns = np.zeros(N)

# N回試行
for n in range(1, N+1):
    # プレイするマシン番号をランダムに決定
    action = np.random.randint(0, action_size)
    
    # action番目のマシンをプレイ
    reward = bandit.play(action)
    
    # action番目のプレイ回数をカウントアップ
    ns[action] += 1
    print('n =', ns, ' action =', action, ' reward =', reward)
    
    # action番目の推定価値(標本平均)を計算:式(1.5)
    Qs[action] += (reward - Qs[action]) / ns[action]
    print('Q =', np.round(Qs, 3))
p = [0.72 0.94 0.71 0.01 0.28 0.16 0.03 0.95 0.75 0.06]
n = [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]  action = 0  reward = 1
Q = [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
n = [1. 0. 0. 0. 0. 0. 0. 0. 1. 0.]  action = 8  reward = 1
Q = [1. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
n = [1. 0. 1. 0. 0. 0. 0. 0. 1. 0.]  action = 2  reward = 0
Q = [1. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
n = [1. 0. 1. 0. 0. 0. 0. 0. 2. 0.]  action = 8  reward = 1
Q = [1. 0. 0. 0. 0. 0. 0. 0. 1. 0.]
n = [1. 0. 2. 0. 0. 0. 0. 0. 2. 0.]  action = 2  reward = 1
Q = [1.  0.  0.5 0.  0.  0.  0.  0.  1.  0. ]
n = [1. 1. 2. 0. 0. 0. 0. 0. 2. 0.]  action = 1  reward = 1
Q = [1.  1.  0.5 0.  0.  0.  0.  0.  1.  0. ]
n = [1. 1. 2. 0. 0. 0. 0. 0. 3. 0.]  action = 8  reward = 0
Q = [1.    1.    0.5   0.    0.    0.    0.    0.    0.667 0.   ]
n = [1. 1. 2. 0. 0. 0. 0. 0. 4. 0.]  action = 8  reward = 1
Q = [1.   1.   0.5  0.   0.   0.   0.   0.   0.75 0.  ]
n = [1. 1. 2. 0. 0. 0. 1. 0. 4. 0.]  action = 6  reward = 0
Q = [1.   1.   0.5  0.   0.   0.   0.   0.   0.75 0.  ]
n = [1. 1. 2. 0. 0. 1. 1. 0. 4. 0.]  action = 5  reward = 0
Q = [1.   1.   0.5  0.   0.   0.   0.   0.   0.75 0.  ]

 マシンごとに、n回目時点のプレイ回数nsと価値の推定値(標本平均)Qsを更新します。

 nsaction番目の要素が1増えているのが分かります。
 試行回数が少ないので、標本平均が真の期待値に近付いているかは分かりません。次節で確認します。

・行動メソッド

 先ほどは、プレイするマシンをランダムに決めました。実装では、ε-greedy法によりマシンを選びます。

# ランダムにマシンを選ぶ確率を指定
epsilon = 0.5

# 一様乱数を生成
r = np.random.rand()
print(r)

# プレイするマシンを決定
if r < epsilon:
    # ランダムにマシン番号を出力
    action = np.random.randint(0, len(Qs))
    print('random :', action)
else:
    # 推定価値が最大のマシンを出力
    action = np.argmax(Qs)
    print('argmax :', action)
0.539124092304009
argmax : 0

 1.4.1項のときと同様に、0から1の一様乱数を生成してランダムに行動する確率epsilonと比較します。epsilonの方が大きければnp.random.randint()でランダムにマシンを選び、epsilon未満であればnp.argmax()で価値の推定値が最大のマシン番号(値が最大のQsのインデックス)を選びます。

 np.argmax()の処理を確認します。

# 各マシンの推定価値を確認
print(np.round(Qs, 3))

# 値が最大のインデックスを抽出
print(np.argmax(Qs))
[1.   1.   0.5  0.   0.   0.   0.   0.   0.75 0.  ]
0

 最大値が重複すると、インデックスが小さい方を出力します。

 以上が、Agentクラスで行う処理です。

・実装

 Agentクラスの実装は、次のページを参照してください。

github.com


 実装したクラスを試してみます。

 マシンの数とランダムに行動する確率を指定して、エージェントクラスのインスタンスを作成します。

# マシンの数を指定
action_size = 5

# ランダムにマシンを選ぶ確率を指定
epsilon = 0.5

# インスタンスを作成
agent = Agent(epsilon, action_size)
print(agent.Qs)
print(agent.ns)
[0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0.]

 各マシンの価値の推定値Qsとプレイ回数nsの初期値は0です。

 プレイするマシンをε-greedy法により決定して、価値の推定値を更新します。

# 試行回数を指定
N = 5

# N回試行
for n in range(N):
    # ε-greedy法によりマシンを決定
    action = agent.get_action()

    # 確認用の報酬を設定
    reward = 1

    #  action番目のマシンの推定価値(標本平均)を更新
    agent.update(action, reward)
    print('n =', agent.ns, ' action =', action)
    print('Q =', agent.Qs)
n = [1. 0. 0. 0. 0.]  action = 0
Q = [1. 0. 0. 0. 0.]
n = [1. 0. 0. 0. 1.]  action = 4
Q = [1. 0. 0. 0. 1.]
n = [1. 0. 1. 0. 1.]  action = 2
Q = [1. 0. 1. 0. 1.]
n = [2. 0. 1. 0. 1.]  action = 0
Q = [1. 0. 1. 0. 1.]
n = [3. 0. 1. 0. 1.]  action = 0
Q = [1. 0. 1. 0. 1.]

 ε-greedy法により行動できました。(この設定では、マシンを実行すると必ず報酬1が得られるので、価値の推定値(標本平均)は常に1になります。)

 以上で、エージェントの機能を持つクラスを実装できました。次節では、実装したクラスを使ってバンディット問題を解きます。

参考文献


おわりに

 いつものことだけど想定レベルをどうするかに悩みます。現状は1巻を1周したレベルくらいの雰囲気で書いてるのですが、3章を読んでる途中の感じだと内容に関連はなさそうです。クラスがどんなものか軽く知っていて、NumPyを触ったことがあれば大丈夫そう?

【次節の内容】

www.anarchive-beta.com