はじめに
『ゼロから作るDeep Learning 4 ――強化学習編』の独学時のまとめノートです。初学者の補助となるようにゼロつくシリーズの4巻の内容に解説を加えていきます。本と一緒に読んでください。
この記事は、6.5節の内容です。サンプリング版のQ学習を実装します。
【前節の内容】
【他の記事一覧】
【この記事の内容】
6.5 分布モデルとサンプルモデル
ここまでは、分布モデルで実装しました。この節では、サンプルモデルで実装することを考えます。
6.5.2 サンプルモデル版のQ学習
6.4節で実装したQ学習をサンプルモデルで実装します。
利用するライブラリを読み込みます。
# ライブラリを読み込み import numpy as np from collections import defaultdict
また、3×4マスのグリッドワールドのクラスGridWorld
を読み込みます。
# 実装済みのクラスと関数を読み込み import sys sys.path.append('../deep-learning-from-scratch-4-master') from common.gridworld import GridWorld from common.utils import argmax, greedy_probs
実装済みクラスの読み込みについては1巻の3.6.1項、GridWorld
クラスについては4.2.1項、argmax
関数とgreedy_probs
関数については5.4.3項を参照してください。
処理の確認
サンプルモデル版のQlearningAgent
クラスのget_action
メソッドの内部で行う処理を確認します。他のメソッドについては6.4.3項を参照してください。
例として、ランダムな値の行動価値関数を作成しておきます。
# (仮の)現在の状態を設定 state = (1, 2) # 行動の種類数を指定 action_size = 4 # (仮の)行動価値関数を作成 Q = {(state, action): np.random.rand() for action in range(action_size)} print(list(Q.keys())) print(np.round(list(Q.values()), 3))
[((1, 2), 0), ((1, 2), 1), ((1, 2), 2), ((1, 2), 3)]
[0.363 0.102 0.08 0.904]
現在の状態state
における上下左右の4つの行動に対する値をディクショナリに格納します。
行動価値が最大の行動を抽出します。
# 現在の状態の全ての行動価値を取得 qs = [Q[state, action] for action in range(action_size)] print(np.round(qs, 3)) # 行動価値が最大の行動を取得 max_action = np.argmax(qs) print(max_action)
[0.363 0.102 0.08 0.904]
3
リスト内包表記を使って、行動価値関数Q
から状態state
における全ての行動価値を取り出してqs
とします。
qs
のインデックスが行動番号に対応するので、np.argmax()
で最大値のインデックスを取得します。
これがgreedy法における処理です。
続いて、ε-greedy法により行動を決定します。
# ランダムに行動する確率を指定 epsilon = 0.1 #epsilon = 0.9 # ε-greedy法により行動を決定:式(6.11) if np.random.rand() < epsilon: # ランダムに行動を出力 action = np.random.choice(action_size) else: # 現在の状態の全ての行動価値を取得 qs = [Q[state, a] for a in range(action_size)] # 行動価値が最大の行動を取得 action = np.argmax(qs) print(action)
3
0から1の一様乱数をnp.random.rand()
を生成して、ランダムに行動する確率epsilon
と比較します。epsilon
より小さければ($\epsilon$未満の確率で)、np.random.choice()
でランダムな行動を出力します。epsilon
以上であれば($1-\epsilon$の確率で)、行動価値が最大となる行動を出力します。
ただし、np.argmax()
は、値が同じだと最小のインデックスを返すのでした。
# (仮の)行動価値のリストを作成 qs = [0, 0, -1, 0] # 行動価値が最大の行動を抽出 print(np.argmax(qs))
0
そこで、argmax()
を使います。
# 繰り返し行動価値が最大の行動を抽出 for _ in range(10): print(argmax(qs))
0
3
3
1
3
3
3
0
3
1
値が同じインデックスからランダムに返します。
(本では紹介されていませんが、utils.py
から読み込むgreedy_probs()
の内部では、np.argmax()
ではなくargmax()
が使われています。QLearningAgent
クラスでも、argmax()
を使わないと上にばかりに進もうとするエージェントになります。)
以上が、サンプリング版のQ学習による方策制御を行うエージェントの処理です。
実装
処理の確認ができたので、Q学習におけるサンプルモデル版のエージェントをクラスとして実装します。
# サンプリング版Q学習によるエージェントの実装 class QLearningAgent: # 初期化メソッドの定義 def __init__(self): # パラメータを指定 self.gamma = 0.9 # 収益の計算用の割引率 self.alpha = 0.8 # 状態価値の計算用の学習率 self.epsilon = 0.1 # ランダムに行動する確率 self.action_size = 4 # 行動の種類数 # オブジェクトを初期化 self.Q = defaultdict(lambda: 0) # 行動価値関数 # 行動メソッドの定義 def get_action(self, state): # ε-greedy法により行動を決定:式(6.11) if np.random.rand() < self.epsilon: # ランダムに行動を出力 return np.random.choice(self.action_size) else: # 現在の状態の行動価値を取得 qs = [self.Q[state, a] for a in range(self.action_size)] # 行動価値が最大の行動を出力 #return np.argmax(qs) return argmax(qs) # 更新メソッドの定義 def update(self, state, action, reward, next_state, done): # ゴールの場合 if done: # 次の状態・行動の行動価値の最大値を0に設定 next_q_max = 0 # ゴール以外の場合 else: # 次の状態・行動の行動価値を取得 next_qs = [self.Q[next_state, a] for a in range(self.action_size)] # 次の状態・行動の行動価値の最大値を取得 next_q_max = max(next_qs) # 現在の行動価値関数を更新:式(6.14) target = reward + self.gamma * next_q_max self.Q[state, action] += (target - self.Q[state, action]) * self.alpha
実装したクラスを試してみましょう。
環境(グリッドワールド)とエージェントのインスタンスを作成して、1エピソードの処理を行います。
# 環境・エージェントのインスタンスを作成 env = GridWorld() agent = QLearningAgent() # 行動の表示用のリストを作成 arrows = ['↑', '↓', '←', '→'] # 最初の状態を設定 state = env.start_state # 時刻(試行回数)を初期化 t = 0 # 1エピソードのシミュレーション while True: # 時刻をカウント t += 1 # ε-greedy法により行動を決定 action = agent.get_action(state) # サンプルデータを取得 next_state, reward, done = env.step(action) # 現在の状態・行動の行動価値関数を更新:式(6.14) agent.update(state, action, reward, next_state, done) # サンプルデータを表示 print( 't=' + str(t) + ', S_t=' + str(state) + ', A_t=' + arrows[action] + ', S_t+1=' + str(next_state) + ', R_t=' + str(reward) ) # ゴールに着いたらエピソードを終了 if done: break # 状態を更新 state = next_state
t=1, S_t=(2, 0), A_t=←, S_t+1=(2, 0), R_t=0
t=2, S_t=(2, 0), A_t=←, S_t+1=(2, 0), R_t=0
t=3, S_t=(2, 0), A_t=→, S_t+1=(2, 1), R_t=0
t=4, S_t=(2, 1), A_t=↓, S_t+1=(2, 1), R_t=0
t=5, S_t=(2, 1), A_t=←, S_t+1=(2, 0), R_t=0
t=6, S_t=(2, 0), A_t=↑, S_t+1=(1, 0), R_t=0
t=7, S_t=(1, 0), A_t=↑, S_t+1=(0, 0), R_t=0
t=8, S_t=(0, 0), A_t=↓, S_t+1=(1, 0), R_t=0
t=9, S_t=(1, 0), A_t=→, S_t+1=(1, 0), R_t=0
t=10, S_t=(1, 0), A_t=←, S_t+1=(1, 0), R_t=0
t=11, S_t=(1, 0), A_t=→, S_t+1=(1, 0), R_t=0
t=12, S_t=(1, 0), A_t=↑, S_t+1=(0, 0), R_t=0
t=13, S_t=(0, 0), A_t=↑, S_t+1=(0, 0), R_t=0
t=14, S_t=(0, 0), A_t=←, S_t+1=(0, 0), R_t=0
t=15, S_t=(0, 0), A_t=↓, S_t+1=(1, 0), R_t=0
t=16, S_t=(1, 0), A_t=←, S_t+1=(1, 0), R_t=0
t=17, S_t=(1, 0), A_t=↓, S_t+1=(2, 0), R_t=0
t=18, S_t=(2, 0), A_t=→, S_t+1=(2, 1), R_t=0
t=19, S_t=(2, 1), A_t=↓, S_t+1=(2, 1), R_t=0
t=20, S_t=(2, 1), A_t=←, S_t+1=(2, 0), R_t=0
t=21, S_t=(2, 0), A_t=↑, S_t+1=(1, 0), R_t=0
t=22, S_t=(1, 0), A_t=↓, S_t+1=(2, 0), R_t=0
t=23, S_t=(2, 0), A_t=↓, S_t+1=(2, 0), R_t=0
t=24, S_t=(2, 0), A_t=↓, S_t+1=(2, 0), R_t=0
t=25, S_t=(2, 0), A_t=→, S_t+1=(2, 1), R_t=0
t=26, S_t=(2, 1), A_t=→, S_t+1=(2, 2), R_t=0
t=27, S_t=(2, 2), A_t=→, S_t+1=(2, 3), R_t=0
t=28, S_t=(2, 3), A_t=→, S_t+1=(2, 3), R_t=0
t=29, S_t=(2, 3), A_t=↓, S_t+1=(2, 3), R_t=0
t=30, S_t=(2, 3), A_t=→, S_t+1=(2, 3), R_t=0
t=31, S_t=(2, 3), A_t=↑, S_t+1=(1, 3), R_t=-1.0
t=32, S_t=(1, 3), A_t=←, S_t+1=(1, 2), R_t=0
t=33, S_t=(1, 2), A_t=↑, S_t+1=(0, 2), R_t=0
t=34, S_t=(0, 2), A_t=→, S_t+1=(0, 3), R_t=1.0
agent
のget_action()
で挙動方策に従い行動して、env
のstep()
で状態を遷移し報酬を出力します。
得られたサンプルデータ(現在の状態・行動・報酬・次の状態)を使って、agent
のupdate()
で現在の状態と行動の行動価値関数を計算します。
ゴールマスに着くとdone
がTrue
に設定されるので、break
でループ処理を終了します。
行動価値関数をヒートマップで確認します。
# 行動価値関数のヒートマップと方策ラベルを作図
env.render_q(q=agent.Q)
render_q()
内部のnp.argmax()
の仕様で、行動価値が等しいとインデックスが最小の行動がラベルで表示されます。
状態を指定して、greedy化した方策とε-greedy化した方策を作成します。
# 状態を指定 state = (0, 2) # ランダムに行動する確率を指定 epsilon = 0.1 # 行動価値関数を確認 print([agent.Q[state, action] for action in range(agent.action_size)]) # greedy法による方策を作成 pi_state = greedy_probs(agent.Q, state, 0.0) print(pi_state) # ε-greedy法による方策を作成 b_state = greedy_probs(agent.Q, state, epsilon) print(b_state)
[0, 0, 0, 0.8]
{0: 0.0, 1: 0.0, 2: 0.0, 3: 1.0}
{0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
全ての状態でgreedy化した方策を格納したディクショナリを作成します。
# 状態ごとにgreedy化 pi = {state: greedy_probs(agent.Q, state, 0.0) for state in env.states()} # 状態ごとに表示 for state in env.states(): print('------ state : ' + str(state) + ' ------') print('action value : ' + str([np.round(agent.Q[state, action], 5) for action in range(agent.action_size)])) print('max action : ' + str(np.argmax([agent.Q[state, action] for action in range(agent.action_size)]))) print('probs : ' + str(pi[state]))
------ state : (0, 0) ------
action value : [0.0, 0.0, 0.0, 0]
max action : 0
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
------ state : (0, 1) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 0.0, 1: 0.0, 2: 0.0, 3: 1.0}
------ state : (0, 2) ------
action value : [0, 0, 0, 0.8]
max action : 3
probs : {0: 0.0, 1: 0.0, 2: 0.0, 3: 1.0}
------ state : (0, 3) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
------ state : (1, 0) ------
action value : [0.0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
------ state : (1, 1) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 1.0, 1: 0.0, 2: 0.0, 3: 0.0}
------ state : (1, 2) ------
action value : [0.0, 0, 0, 0]
max action : 0
probs : {0: 0.0, 1: 0.0, 2: 1.0, 3: 0.0}
------ state : (1, 3) ------
action value : [0, 0, 0.0, 0]
max action : 0
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
------ state : (2, 0) ------
action value : [0.0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
------ state : (2, 1) ------
action value : [0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.0, 1: 0.0, 2: 0.0, 3: 1.0}
------ state : (2, 2) ------
action value : [0, 0, 0, 0.0]
max action : 0
probs : {0: 1.0, 1: 0.0, 2: 0.0, 3: 0.0}
------ state : (2, 3) ------
action value : [-0.8, 0.0, 0, 0.0]
max action : 1
probs : {0: 0.0, 1: 1.0, 2: 0.0, 3: 0.0}
行動価値が等しい場合は、greedy_probs()
内部のargmax()
によって、$1 - \epsilon$の確率で取る行動がランダムに決まります(probsの値(確率)が1.0
の行動とmax actionが異なる場合があります)。
続いて、全ての状態でε-greedy化した方策を格納したディクショナリを作成します。
# ランダムに行動する確率を指定 epsilon = 0.1 # 状態ごとにε-greedy化 b = {state: greedy_probs(agent.Q, state, epsilon) for state in env.states()} # 状態ごとに表示 for state in env.states(): print('------ state : ' + str(state) + ' ------') print('action value : ' + str([np.round(agent.Q[state, action], 5) for action in range(agent.action_size)])) print('max action : ' + str(np.argmax([agent.Q[state, action] for action in range(agent.action_size)]))) print('probs : ' + str(b[state]))
------ state : (0, 0) ------
action value : [0.0, 0.0, 0.0, 0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (0, 1) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 0.925, 1: 0.025, 2: 0.025, 3: 0.025}
------ state : (0, 2) ------
action value : [0, 0, 0, 0.8]
max action : 3
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (0, 3) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 0.025, 1: 0.925, 2: 0.025, 3: 0.025}
------ state : (1, 0) ------
action value : [0.0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (1, 1) ------
action value : [0, 0, 0, 0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (1, 2) ------
action value : [0.0, 0, 0, 0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.925, 3: 0.025}
------ state : (1, 3) ------
action value : [0, 0, 0.0, 0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (2, 0) ------
action value : [0.0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.025, 1: 0.025, 2: 0.025, 3: 0.925}
------ state : (2, 1) ------
action value : [0, 0.0, 0.0, 0.0]
max action : 0
probs : {0: 0.925, 1: 0.025, 2: 0.025, 3: 0.025}
------ state : (2, 2) ------
action value : [0, 0, 0, 0.0]
max action : 0
probs : {0: 0.925, 1: 0.025, 2: 0.025, 3: 0.025}
------ state : (2, 3) ------
action value : [-0.8, 0.0, 0, 0.0]
max action : 1
probs : {0: 0.025, 1: 0.925, 2: 0.025, 3: 0.025}
以上で、サンプリング版のQ学習のエージェントを実装できました。分布モデル版と内部の処理は異なりますが、クラスとして利用する処理は同じなので、6.4節のコードで方策制御を行えます。
この章では、TD法を実装しました。次章では、ニューラルネットワークによるQ学習を実装します。
参考文献
- 斎藤康毅『ゼロから作るDeep Learning 4 ――強化学習編』オライリー・ジャパン,2022年.
- サポートページ:GitHub - oreilly-japan/deep-learning-from-scratch-4
おわりに
数式は出てこないし目新しい処理も出てこないので、手元のノートには書きつつ記事にするつもりはなかったのですが、argmax()
に関して自分用のメモとしても書き残しておいた方がいいかなと思って一応記事としてアップしておくことにしました。
これで6章完了です!お疲れ様でした。いよいよニューラルネットに進みます。
【次節の内容】