はじめに
『ゼロから作るDeep Learning 4 ――強化学習編』の独学時のまとめノートです。初学者の補助となるようにゼロつくシリーズの4巻の内容に解説を加えていきます。本と一緒に読んでください。
この記事は、1.4.2節の内容です。ε-greedy法により行動するエージェントを実装します。
【前節の内容】
【他の記事一覧】
【この記事の内容】
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
を更新します。
ns
のaction
番目の要素が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
クラスの実装は、次のページを参照してください。
実装したクラスを試してみます。
マシンの数とランダムに行動する確率を指定して、エージェントクラスのインスタンスを作成します。
# マシンの数を指定 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
になります。)
以上で、エージェントの機能を持つクラスを実装できました。次節では、実装したクラスを使ってバンディット問題を解きます。
参考文献
- 斎藤康毅『ゼロから作るDeep Learning 4 ――強化学習編』オライリー・ジャパン,2022年.
- サポートページ:GitHub - oreilly-japan/deep-learning-from-scratch-4
おわりに
いつものことだけど想定レベルをどうするかに悩みます。現状は1巻を1周したレベルくらいの雰囲気で書いてるのですが、3章を読んでる途中の感じだと内容に関連はなさそうです。クラスがどんなものか軽く知っていて、NumPyを触ったことがあれば大丈夫そう?
【次節の内容】