2018年4月5日木曜日

OpenAI Gym で強化学習に入門する

概要

OpenAI Gym は強化学習をゲームなどを使って学ぶことができる学習ツールです
今回は入門ということで「CartPole」問題に挑戦してみました
実際にコーディングしつつ変数などの値をデバッグすることで理解を深めていきたいと思います

環境

  • macOS 10.13.2
  • Python 3.6.4
  • pip 9.0.3
  • OpenAI Gym 0.10.4

インストール

  • sudo pip3 install gym

とりあえず CartPole を動作させる

CartPole 問題はカート上に置かれた棒を左右に動かしていかに長く棒を倒さないかを求めるゲームです
GUI を使って動作させることができるのでとりあえず動かせる状況を作ります

  • vim test-gym.py
import gym

env = gym.make('CartPole-v0')

for i_episode in range(20):
    observation = env.reset()
    for t in range(100):
        env.render()
        print(observation)
        action = env.action_space.sample()
        print(env.action_space)
        print(env.observation_space)
        print(env.observation_space.high)
        print(env.observation_space.low)
        observation, reward, done, info = env.step(action)
        print(reward)
        if done:
            print("Episode finished after {} timesteps".format(t+1))
            break

これでとりあえず実行してみると CartPole の GUI が表示されてある程度すると終了すると思います
やっていることとしては 20 エピソードテストしてそのテスト内で 100 ステップカートを動かして 100 ステップ完走すれば成功で 100 ステップ完走するまえに if done に行ってしまうと失敗です

実行してみるとわかりますがだいたい 20 - 30 ステップくらいで全エピソードが done してしまっているのがわかると思います

詳細説明

ところどころ print 文をあえて入れています
import 文のところは特に問題ないと思います

env = gym.make('CartPole-v0') ですがこれで CartPole 問題の環境を呼び出しています
OpenAI Gym ではこれ以外にも様々な環境が用意されており、それぞれで強化学習を学ぶためのデータやアクションがすでに実装されています
OpenAI Gym ではそれらのデータやアクションを使って自分なりのロジックを少し付け加えることで強化学習を簡単に学べる仕組みになっています

for i_episode in range(20): エピソードを 20 回実行します
要するにテストする回数です
20 でなくても問題ないですがとりあえず 20 にします

observation = env.reset() これで CartPole 問題をスタートします
返り値として observation が返ってきます
これが所謂「状態」を表す変数となっており CartPole 問題の場合は 4 つの変数が入った配列になっています
状態を表す変数はそれぞれ

  • カート位置 -2.4~2.4
  • カート速度 -3.0~3.0
  • 棒の角度 -41.8~41.8
  • 棒の角速度 -2.0~2.0

となっており reset 直後はスタート直後の棒とカートの状態を取得することができます
print 文をしているので実際に値を確認してみると良いと思います

for t in range(100): はエピソード内で 100 ステップ実行しろという命令になります
冒頭説明しましたがこの 100 ステップの間棒が倒れなければクリアになります

env.render() これは observation に応じて GUI をレンダリングしているだけです

action = env.action_space.sample() で次に取るアクションを決めます
まず env.action_space を見てみると Discrete クラスのオブジェクトだということがわかります
値は 2 となっており要するにカートが取ることができるアクションの種類を管理しています
今回であれば 1 -> 右にカートを動かす, 0 -> 左にカートを動かすの 2 種類を選択することができます
そしてそれに対して .sample() することで 1 or 0 のどちらかをランダムで取得するということをしています
ようするに env.action_space.sample() はランダムでカートを動かすという命令になります

次の行動が決まったらそれを実行するだけです
observation, reward, done, info = env.step(action) で action を引数にすることで実行できます
実行後は 4 つの値が返ってきます
それぞれ

  • observation・・・行動後の状態
  • reward・・・行動によって得られた報酬
  • done・・・エピソードが終了したかどうか (true なら終了、ゲーム終わり)
  • info・・・詳細情報

になります
reward を print していますが中身を見てみると毎回 1.0 が得られていることがわかると思います
action 後もし棒が倒れていないのであれば 1.0 sec の報酬が得られるようです

if done: はエピソードの終了処理になります
内容は何ステップ実行できたかを print しているだけです

説明は以上になります
要するにサンプルはカートを左右ランダムに動かしているだけになります
当たり前ですが何も考えずランダムにカートを動かしているだけではすぐに棒は倒れてしまいます
では何をしなければ行けないかというと observation (状態) に応じて次に取るべき action (行動) を決めなければいけません

observation に応じて action を決定するロジックを追加する

棒がもっと長く立っていられるように変更してみます

  • vim test_gym2.py
import gym
import numpy

env = gym.make('CartPole-v0')
loop = 100

for i_episode in range(20):
    observation = env.reset()
    for t in range(loop):
        env.render()
        sum = numpy.sum(observation)
        action = 0
        if sum > 0:
            action = 1
        observation, reward, done, info = env.step(action)
        if done:
            print("Episode finished after {} timesteps with done".format(t + 1))
            break
        elif t == loop - 1:
            print("Episode finished after {} timesteps with count".format(t + 1))

ポイントは

sum = numpy.sum(observation)
action = 0
if sum > 0:
    action = 1

の部分です
observation の 4 つの値の合計を取りその値が 0 以上であった場合にカートを右に動かします
そうでない場合は左にカードを動かします
要するに棒が左に倒れそうなときはカートを右に動かし、右に倒れそうなときはカートを左に動かすということをしています

また、このロジックを追加することで棒がかなり倒れづらくなるので終了条件にステップが 100 回に達した場合も終了するようにしました
これで実行してみましょう

  • python3 test_gym2.py
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 99 timesteps with done
Episode finished after 100 timesteps with count
Episode finished after 77 timesteps with done
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 78 timesteps with done
Episode finished after 76 timesteps with done
Episode finished after 97 timesteps with done
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count
Episode finished after 100 timesteps with count

するとほぼすべてのエピソードで 100 ステップ達成していることがわかると思います

今回は単純に observation の合計を見ただけです
更に倒れないようなロジックを組むには合計ではなく observation それぞれの値を確認して action を決定する必要があります
所謂 Q-learning を実装する必要があります
Q-Table などを作成し行動に応じて報酬が最大となるような表を作成するなどの実装が必要になります

最後に

OpenAI Gym で手を動かしながら強化学習に入門してみました
強化学習のポイントとなる要素「状態」「行動」「報酬」の仕組みが少しは理解できたと思います

強化学習は教師あり学習や教師なし学習に比べて応用的な分野になるので理解することも多いです
使いこなせるようになればかなり便利だとは思いますが思いついて、それを実装できるレベルになるまでには結構な学習コストが必要だと思います
OpenAI Gym に関してはサンプルコードも豊富なのでとりあえず他に人の学習方法を真似て実装してみるのは良いかもしれません

それぞれの環境における observation や action の種類に関しては Github の Wiki に記載されているのでそちらを確認すると良いと思います

参考サイト

0 件のコメント:

コメントを投稿