OpenAI Gym入門 / Q学習
1. Q学習
「Q学習」は、経験(状態、行動、報酬、次の状態のセット)によって「行動価値関数」を更新することで、エージェントがより最適な行動が採れるように訓練する強化学習アルゴリズムです。「行動価値関数」は、ある状態である行動を採る「価値」を計算する関数で、「Q学習」の「行動価値関数」は「Q関数」とも呼ばれます。
今回は、この「Q学習」を使って古典的な強化学習環境である「MountainCar」を攻略します。
2. MountainCar
「MountainCar」は、車を前後に移動させ勢いを付けることにより、山の頂上まで登らせる「環境」です。
「MountainCar」の入力と出力は次の通りです。
・入力
・車の位置(-1.2〜0.6)
・車の速度(−0.07〜−0.07)
・出力
・行動(0:左移動, 1:右移動)
前回のコードを使って、環境の状態空間と行動空間を調べると次のようになります。
環境ID: MountainCar-v0
状態空間: Box(2,)
最小値: [-1.2 -0.07]
最大値: [0.6 0.07]
行動空間: Discrete(3)
最小値: 0
最大値: 2
車は速度が0、位置は-0.6〜-0.4のランダム位置から開始します。報酬はステップ毎に-1が与えられます。
右の山の上(位置0.5)に到達するとゴールで、エピソード完了になります。200ステップに達した時もタイムオーバーでエピソード完了となります。
3. Q学習エージェントの実装
Q学習によって学習するエージェント「QAgent」を実装します。
◎パッケージのインポート
はじめにパッケージをインポートします。
#!/usr/bin/env/ python
import gym
import numpy as np
◎定数の定義
次に定数の定義を行います。「NUM_EPISODES」「MAX_STEPS」は、訓練のための定数、「EPSILON_MIN」「ALPHA」「GAMMA」「NUM_DISCRETE_BINS」は学習アルゴリズムのための定数になります。
# 定数(訓練)
NUM_EPISODES = 50000 # 学習するエピソード数
MAX_STEPS = 200 # 1エピソードの最大ステップ数
# 定数(学習アルゴリズム)
EPSILON_MIN = 0.005 # εの最小値
EPSILON_DECAY = 500 * EPSILON_MIN/(NUM_EPISODES*MAX_STEPS) # ε値の減衰量
ALPHA = 0.05 # 学習係数(1回の学習の更新の大きさ)
GAMMA = 0.98 # 時間割引率(0〜1)
NUM_BINS = 30 # 離散化時の分割数
◎QAgentの定義
Q学習エージェントのクラス「QAgent」を定義します。
# Q学習エージェント
class QAgent(object):
# エージェントの初期化
def __init__(self, env):
〜省略〜
# 状態を連続値から離散値に変換
def discretize(self, state):
〜省略〜
# 状態に応じて行動を選択
def get_action(self, state):
〜省略〜
# 収集した経験に応じてQ関数を更新
def learn(self, state, action, reward, next_state):
〜省略〜
◎__init__(env)の実装
__init__(env)では、エージェントの初期化を行います。
状態空間と行動空間と離散化の値を取得し、インスタンス変数で保持します。次にQ関数として51x51x3のNumpy配列を生成します。最後にεの初期値の準備します。
# エージェントの初期化
def __init__(self, env):
# 状態空間と行動空間の値の準備
self.obs_shape = env.observation_space.shape # 状態のシェイプ
self.obs_high = env.observation_space.high # 状態の最大値
self.obs_low = env.observation_space.low # 状態の最小値
self.action_shape = env.action_space.n # 行動の数
# 離散化の値の準備
self.bin_width = (self.obs_high - self.obs_low)/NUM_BINS # 離散化時の1ビンの幅
# Q関数の生成(31 x 31 x 3)
self.Q = np.zeros((NUM_BINS+1, NUM_BINS+1, self.action_shape))
# εの初期値の準備
self.epsilon = 1.0
◎discretize(state)の実装
discretize(state)は、状態を連続値から離散値に変換するメソッドです。
状態空間の連続値を30分割した、0〜30の離散値に変換します。
たとえば、車の速度(−0.07〜−0.07)の場合、bin_widthは「0.007-(-0.007)/30=0.00046」で、
(-0.07+bin_width*0)以上(-0.07+bin_width*1)以下の場合は0、
(-0.07+bin_width*1)以上(-0.07+bin_width*2)以下の場合は1
のように変換します。
# 状態を連続値から離散値に変換
def discretize(self, state):
return tuple(((state-self.obs_low)/self.bin_width).astype(int))
◎get_action(state)の実装
get_action(state)は、在の状態に応じて行動を選択するメソッドです。
最もよく使われている行動選択のポリシーは「ε-greedy」です。
これはエージェントの推定値に従って、1-εの確率で最良の行動、εの確率でランダム行動を選択します。
# 状態に応じて行動を選択
def get_action(self, obs):
# 状態を連続値から離散値に変換
state = self.discretize(obs)
# εの減衰
if self.epsilon > EPSILON_MIN:
self.epsilon -= EPSILON_DECAY
# 最良な行動の選択
if np.random.random() > self.epsilon:
return np.argmax(self.Q[state])
# ランダム行動の選択
else:
return np.random.choice([a for a in range(self.action_shape)])
◎learn(state, action, reward, next_state)の実装
learn(state, action, reward, next_state)は、収集した経験(状態、行動、報酬、次の状態のセット)に応じてQ関数を更新するメソッドです。
これにより、エージェントは時間の経過とともに、最適な行動を採れるようになります。
Q関数の更新には、以下の更新式を使っています。
# 収集した経験に応じてQ関数を更新
def learn(self, state, action, reward, next_state):
# 状態を連続値から離散値に変換
state = self.discretize(state)
next_state = self.discretize(next_state)
# Q関数の更新
td_target = reward + GAMMA * np.max(self.Q[next_state])
td_error = td_target - self.Q[state][action]
self.Q[state][action] += ALPHA * td_error
4. Q学習エージェントの学習
Q学習エージェントの学習を行うtrain()を作ります。
環境の行動空間からランダム行動を選ぶ代わりに、agent.get_action(state)を使ってエージェントから行動を取得します。エージェントの行動を環境に渡し、経験を受け取った後に、agent.learn()を呼んでQ関数を更新します。
# 学習
def train(agent, env):
# ベスト報酬
best_reward = -float('inf')
# 学習ループ
for episode in range(NUM_EPISODES):
# 環境のリセット
state = env.reset()
done = False
total_reward = 0.0
# 1エピソードのループ
for step in range(MAX_STEPS):
# 行動の取得
action = agent.get_action(state)
# 1ステップの実行
next_state, reward, done, info = env.step(action)
# 学習
agent.learn(state, action, reward, next_state)
state = next_state
total_reward += reward
# エピソード完了
if done:
break
# ベスト報酬の更新
if total_reward > best_reward:
best_reward = total_reward
print("episode:{} reward:{} best_reward:{} eps:{}".
format(episode, total_reward, best_reward, agent.epsilon))
# ポリシーを返す
return np.argmax(agent.Q, axis=2)
5. Q学習エージェントのテスト
Q学習エージェントのテストを行うtrain()を作ります。
訓練と似てますが、Q関数の更新は行なわず、戻り値も報酬和になります。
# テスト
def test(agent, env, policy):
# 環境のリセット
obs = env.reset()
done = False
total_reward = 0.0
# 1エピソードのループ
for step in range(MAX_STEPS):
# 環境の描画
env.render()
# 行動の取得
action = policy[agent.discretize(obs)]
# 1ステップの実行
next_obs, reward, done, info = env.step(action)
obs = next_obs
total_reward += reward
# エピソード完了
if done:
break
# 報酬和を返す
return total_reward
6. 学習とテストの実行
最後に学習とテストのコードを実行します。
エージェントが学習しはじめたばかりの時は、報酬は常に-200になります。これは、200ステップ内に山の頂上に到達していないことを意味します。
さらに、εがゆっくり減衰するのを観察することができます。
学習し続けると、エージェントは徐々に改善され、より少ないステップで山の頂上に到達するようになります。
# メインの実行
if __name__ == "__main__":
# 環境の生成
env = gym.make('MountainCar-v0')
# エージェントの生成
agent = QAgent(env)
# 学習
learned_policy = train(agent, env)
# テスト
for _ in range(10):
print('reward: ', test(agent, env, learned_policy))
# 環境のクローズ
env.close()
ログは次のように出力されます。
episode:0 reward:-200.0 best_reward:-200.0 eps:0.999949999999993
episode:1 reward:-200.0 best_reward:-200.0 eps:0.999899999999986
episode:2 reward:-200.0 best_reward:-200.0 eps:0.999849999999979
episode:3 reward:-200.0 best_reward:-200.0 eps:0.999799999999972
episode:4 reward:-200.0 best_reward:-200.0 eps:0.999749999999965
:
episode:49997 reward:-167.0 best_reward:-93.0 eps:0.0049999999486979654
episode:49998 reward:-120.0 best_reward:-93.0 eps:0.0049999999486979654
episode:49999 reward:-159.0 best_reward:-93.0 eps:0.0049999999486979654
reward: -110.0
reward: -108.0
reward: -126.0
reward: -108.0
reward: -107.0
reward: -164.0
reward: -108.0
reward: -108.0
reward: -108.0
reward: -111.0