kumilog.net

データ分析やプログラミングの話などを書いています。

OpenAI Gymで強化学習

OpenAI Gymは、非営利団体であるOpenAIが提供している強化学習用のツールキットです。以下のようなブロック崩しの他いくつかの環境(ゲーム)が用意されています。OpenAI Gymをつかって強化学習に触れてみたいと思います。

f:id:xkumiyu:20171120211817g:plain

強化学習

f:id:xkumiyu:20171123202807p:plain 出典: AlphaGo Zero: Learning from scratch | DeepMind

強化学習で最も有名な事例は、Google DeepMindのAlphaGoではないでしょうか。2017年10月に第4世代であるAlphaGo Zeroが発表され注目を浴びました。

強化学習とは

強化学習は、以下の図のようにエージェントが行動を行い、環境から得られる報酬を最大となるように学習を行うアルゴリズムです。

ブロック崩しで例えると、エージェントは、右/左にバーを動かすといった行動を行い、ブロックを崩すと報酬が貰えます。たくさんブロックを崩せるように(報酬が最大になるように)状況に応じて、最適な行動を選択できるように学習させるのが、強化学習です。

f:id:xkumiyu:20171120221137p:plain

1サイクルは、

  1. エージェントは観測した現在の状態から行動を実行
  2. 環境が状態の更新を行い、エージェントに報酬を付与
  3. エージェントが状態と報酬を受け取り、それを元に学習
  4. 1.に戻る

のような流れになります。

OpenAI Gymは環境の役割を担い、エージェントからの行動を受け取り、状態の更新と報酬の付与を行います。

Q学習

エージェントは、現在の状態に応じて行動を選択しますが、行動の選択を最適化するのが強化学習です。

強化学習の学習方法の1つにQ学習があり、以下のように{ Q(s, a) }を更新します。

$$ Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha (R(s_t, a_t) + \gamma max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t)) $$

行動評価関数

{ Q(s, a) }は、状態{ s }のとき行動{ a }を実行したときの行動評価関数です。そして、最も行動評価値の高い行動を選択します。

例えば、以下のような行動評価関数の場合、状態Aのときは、左という行動を選択しますし、状態Bのときは右を選択します。

状態 { s } 行動 { a } 行動評価値 { Q(s, a) }
A 0
A 1
B 0.6
B 0.2

TD誤差

{ \alpha }は、学習率です。0.01とか小さい値を設定します。ステップ毎に更新される値は以下の値と学習率の積になります。

$$ R(s_t, a_t) + \gamma max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) - Q(s_t, a_t) $$

{ R(s_t, a_t) }は、状態{ s_t }で行動{ a_t }を実施したときの報酬で、 {  max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) }は次に最適な行動を実施したときのQ値です。

{ R(s_t, a_t) + \gamma max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) }は、普通の機械学習の教師データに近いもので、次のQ値の期待値を意味します。実際のQ値{ Q(s_t, a_t) }はこの値に近づくように学習を繰り返します。

この2つの値の差をTD誤差ともいいます。

Epsilon-Greedy法

Q学習では、最大のQ値の行動を選択するので、選ばれない行動が発生し、局所最適解に陥り、うまく学習が行われません。

そこで、ときどきランダムに行動を選択します。epsilonの確率でランダムに行動を選択し、それ以外で最大のQ値となる行動を選択するのが、Epsilon-Greedy法です。

活用と探索ともいいます。活用が知っている情報(Q値)を活用することで、探索が未知の情報を得るためにランダムに行動するを表します。

強化学習で出てくる用語まとめ

用語 説明
環境(Enviroment) エージェントからの行動を受け取り、状態の更新と報酬の付与を行う。
エージェント(Agent) 環境に対して行動を実行する。強化学習ではエージェントの行動の最適化を行う。対戦ゲームで例えるとプレイヤー。
行動(Action) エージェントの行動。CartPoleでは、右を押す/左を押すといった行動が可能。
(観測された)状態(observation=state) 特定の場面。CartPoleでは、台車の位置やポールの角度などが観測可能。
報酬 行動によって得られる報酬。CartPoleではポールが立っているとstep毎に報酬がもらえる。
step 行動と状態の観測、報酬の受取の1サイクル。
episode 1ゲームのこと。CartPoleでは、ポールが倒れるまでが1episode。

OpenAI Gym

それでは、OpenAI Gymをつかって強化学習を行ってみます。

インストール

インストールは簡単です。PyPIよりインストールすることができます。

$ pip install gym

pip install gymは最小限のインストールとなり、一部の環境を使うことができません。すべてを使うには、次のようにインストールします。

$ brew install cmake boost boost-python sdl2 swig wget
$ pip install 'gym[all]'

上記はMacOSの例です。他の環境では、必要に応じてパッケージをインストールしてください。公式のReadmeにはUbuntu14.04の例が記載されています。

とりあえず動かしてみる

強化学習のHello world的な存在であるCartPole(倒立振子)をやってみます。CartPoleは、ポールが動く台車の上に立っており、ポールが倒れないように台車を右/左に動かし、より長くポールを立っている状態を保つのが目的です。

import gym

# 環境の作成
env = gym.make('CartPole-v0')

# 環境の初期化
env.reset()

# 1000step回す
for _ in range(1000):
    env.render()  # 描画
    action = env.action_space.sample()  # ランダムに行動を選択
    env.step(action)  # 行動を実行

makeメソッドで環境を作ることができます。引数は環境のIDで、CartPoleのIDを指定しています。環境のIDは envs.registry.all()で確認することができます。

from gym import envs

print(envs.registry.all())
# [EnvSpec(DoubleDunk-v0), EnvSpec(InvertedDoublePendulum-v0), EnvSpec(BeamRider-v0), ...

ランダムに行動を実行しているので、全然立っていませんが、とりあえず動いています。

f:id:xkumiyu:20171120214241g:plain

以降、環境はgym.make('CartPole-v0')で作成されたものを用います。

環境から得られる情報

エージェントの学習について説明する前に、OpenAI Gymの環境から得られる情報についてまとめておきたいと思います。

Observations

まず、環境の状態は、環境の初期化や行動の実行の返り値として得ることができます。

# 環境の初期化
observation = env.reset()

# 行動の実行
observation, reward, done, info = env.step(action)

print(observation)
# array([-0.00712775, -0.04548404,  0.04320993,  0.04356523])

この結果は、CartPoleの例です。CartPoleでは、

  • 台車の位置座標(-2.4 ~ 2.4)
  • 台車の速度(-inf ~ inf)
  • ポールの角度(-41.8° ~ 41.8°)
  • ポールの角速度(-inf ~ inf)

の4つが環境の状態として連続値で観測できます。 もちろん、環境によって得られる値は異なります。

stepメソッドの返り値として、環境の状態以外に、報酬(reward)、終了フラグ(done)、デバッグ情報(info)があります。

報酬は、float型で、行動によって得られる報酬です。CartPoleではポールが立っていると1.0となります。

print(reward)
# 1.0

終了フラグは、boolean型で、ゲームが終了するとTrueになります。CartPoleでは、ポールが一定以上傾いたり、台車の位置が中心から離れ過ぎたりすると終了となります。

print(done)
# False

infoはデバッグに役立つ情報だと公式ドキュメントにありましたが、空の辞書以外、見たことないです。。あまり使わないと思います。

print(info)
# {}

Spaces

CartPoleでは、環境の状態は4つの配列で得られ、行動は右か左の2つでしたが、OpenAI Gymでは、取りうる行動と得ることができる環境の状態を、Spaceオブジェクトで知ることができます。

print(env.action_space)
# Discrete(2)

print(env.observation_space)
# Box(4,)

Discreteは離散空間で、非負の数値となります。この場合は、0か1です。

Boxはn次元の連続値の配列です。この場合は、4次元配列です。highメソッドやlowメソッドで上限/下限を知ることができます。

print(env.observation_space.high)
# [  4.80000000e+00   3.40282347e+38   4.18879020e-01   3.40282347e+38]

print(env.observation_space.low)
# [ -4.80000000e+00  -3.40282347e+38  -4.18879020e-01  -3.40282347e+38]

QAgent

先程は、ランダムに行動を選択したので、すぐに倒れてしまいました。より長く直立状態を保つために、強化学習を用いて状況に応じて最適な行動を選択できるようにします。例えば、右に倒れそうであれば、左に押すといったように。

コードにすると以下のようなイメージです。

agent = QAgent(q)
observation = env.reset()
for _ in range(1000):
    env.render()

    # 1. エージェントが行動を選択する
    action = agent.act(observation)

    # 2. 行動が実行され、状態の更新と報酬の付与を行う
    next_observation, reward, done, _ = env.step(action)

    # 3. エージェントが状態と報酬を受け取り、学習する
    agent.train(observation, next_observation, reward, done)

    if done:
        break
    observation = next_observation

少し省略していますが、エージェントの実装は以下のようになります。また、Q関数の実装はこちらを参考にしました。

class QAgent(object):
    def __init__(self, q):
        self.q = q # Q関数の実装は省略

    def act(self, obs):
        if np.random.random() < epsilon:
            # epsilon の確率でランダムに行動を選択(Epsilon-Greedy法)
            action = np.random.choice(self.q.n_actions)
        else:
            # Q値が最大となる行動を選択
            action = np.argmax(self.q.values(obs))

        return action

    def train(self, obs, next_obs, reward, done):
        action = self.act(obs)
        state = self.q.observation_to_state(obs)

        future = 0.0
        if not done:
            future = np.max(self.q.values(next_obs))

        # TD誤差
        loss = reward + gamma * future - self.q.table[state][action]
        # Q値の更新
        self.q.table[state][action] += lr * loss

        return loss

完全なコードは以下を参照ください。

github.com

いろいろ学習させてみる

CartPole-v0

これまで扱ったきたCartPole-v0です。

f:id:xkumiyu:20171202223905g:plain
学習開始時

10stepくらいで終了して繰り返し最初から表示されているため、挙動不審になっています。。

f:id:xkumiyu:20171202223913g:plain
学習終了時

Pendulum-v0

Pendulum-v0は回転する振り子で、より垂直に近い状態にほど高い報酬が貰えます。

f:id:xkumiyu:20171202224037g:plain
学習開始時

f:id:xkumiyu:20171202224046g:plain
学習終了時

MountainCar-v0

MountainCar-v0は、台車を丘に登らせるのが目的です。右/左に押すといった行動の他、何もしないといった行動も選択可能です。

f:id:xkumiyu:20171202224056g:plain
学習開始時

f:id:xkumiyu:20171202224058g:plain
学習終了時