OpenAI Gymの倒立振子をKerasで解いてみる.


概要

  • シンプルな方法でOpenAI Gymの倒立振子(CartPole-v0)を解いてみた.

CartPole-v0のルール

  • 台車に立てられた棒を台車に左右から力を加えることでバランスを取る問題.

  • "CartPole-v0"と"CartPole-v1"の違いは最大ターン数と成功条件の閾値のみ
  • 各クリア条件と成功条件の閾値がここにまとまっている.
    • すごい重要な情報な気がしますが,皆さん最初どうやって見つけているんでしょう...
register(
    id='CartPole-v0',
    entry_point='gym.envs.classic_control:CartPoleEnv',
    max_episode_steps=200,
    reward_threshold=195.0,
)

register(
    id='CartPole-v1',
    entry_point='gym.envs.classic_control:CartPoleEnv',
    max_episode_steps=500,
    reward_threshold=475.0,
)

行動空間

  • 右か左のどちらかへ棒に力を加えられる.

状態空間

OpenAI Gymのコードによると,状態空間は下記の連続する4次元の値で構成されています.

  • 各次元の定義
    • 0: 位置($x$: position of the cart on the track)
    • 1: 速度($\dot{x}$: cart velocity)
    • 2: 角度($\theta$: angle of the pole with the vertical)
    • 3: 角速度($\dot{\theta}$: rate of change of the angle)

報酬入手条件

  • 倒立振子の角度が垂直から15度以内
  • 振り子の位置が中心から台車2.4台分以内

ゲーム失敗条件

  • 報酬入手条件を満たさない状態になる

ゲーム成功条件

  • 200ターンでゲームは終了(これが明示されてないので,とっかかりづらい気がする...)
  • 連続100回のゲームで平均スコアで195以上獲得

報酬設計

  • なるだけ失敗条件を満たさないようにすれば良い.
  • 最大ターン数$T$は200なので,最大収益は200.
  • 0〜1となるようTで割った値を収益に設定
G = \frac{1}{T}\sum^T_{t=0} R(x_t)

ここで$R(x_t)$は状態$x_t$時に得られた報酬.

解法

行動価値関数$Q(x_t, a_t)$を下記で更新.

Q(x_t, a_t) \leftarrow (1 - \alpha)Q(x_t, a_t) + \alpha G 
import numpy as np
import gym
from gym import wrappers
import keras
from keras.models import Sequential
from keras.layers.core import Dense, Activation
from keras.utils import np_utils
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

def build_model(input_dim, output_dim):
    model = Sequential()
    model.add(Dense(200, kernel_initializer="he_normal", activation="relu", input_dim=input_dim))
    model.add(Dense(200, kernel_initializer="he_normal", activation="relu"))
    model.add(Dense(output_dim, kernel_initializer="he_normal", activation="sigmoid"))
    return model

def run():
    max_score = 200.0
    num_episodes = 3000
    env = gym.make("CartPole-v0")
    env = wrappers.Monitor(env, directory="/tmp/cartpole-v0", force=True)
    logger.info("Action Space: %s" % str(env.action_space))
    logger.info("Observation Space: %s" % str(env.observation_space))
    model = build_model(input_dim=env.observation_space.shape[0], output_dim=env.action_space.n)
    model.compile(loss="mse", optimizer="adam", metrics=["mae", "mse"])
    Gs = []
    for episode in range(num_episodes):
        x = env.reset()
        X, Q, A, R = [], [], [], []
        done = False
        while not done:
            q = model.predict(np.asarray([x]))[0]
            a = np.argmax(q)
            X.append(x)
            A.append(a)
            Q.append(q)
            x, r, done, info = env.step(a)
            R.append(r)
            # print episode, len(X), a, q, x, r, done, info
        alpha = 0.1
        T = len(X)
        G = np.sum(R)
        for t in range(T):
            a = A[t]
            Q[t][a] = (1-alpha) * Q[t][a] + alpha * (G / max_score)
        model.fit(np.asarray(X), np.asarray(Q), verbose=0, batch_size=T)
        logger.debug("Episode: %d, Reward: %.2f" % (episode, G)) 
        Gs.append(G)
    logger.info("Average Reward: %.3f" % np.mean(Gs))
    env.close()

if __name__ == "__main__":
    run()

References

[1]. OpenAI Gym, Brockman et al., 2016
[2]. Neuronlike Adaptive Elements That Can Solve Difficult Learning Control Problem, Barto et al., 1983
[3]. Simple reinforcement learning methods to learn CartPole, Frans, 2016
[4]. Actor-Criticの疑問のまとめ, yasaki6023, 2017
[5]. 倒立振子で学ぶ DQN (Deep Q Network), ashitani, 2016
[6]. 倒立振子でDQNにおけるモデルの複雑さと学習内容の関係をちらっと確かめてみた系の話, enakai00, 2016
[7]. 各クリア条件と成功条件の閾値, Brockman et al.,2016