OpenAI GYMでPathPlanning用のオリジナル環境構築

# OpenAI Gymと強化学習
OpenAI Gymは, [OpenAI](https://openai.com/)の提供する強化学習の開発・評価用プラットフォームです。
強化学習は、与えられた環境(Environment)の中で、エージェント(Agent)が試行錯誤しながら価値を最大化する行動を学習するアルゴリズムです。
OpenAI GymではAtariなどさまざまな環境(Environment)が公開されていますが、自律移動ロボット用の環境はあまり公開されておらず、自作する必要があります。
ここでは、OpenAI GymによるPathplanning用の環境の作り方についてまとめます。
[git-hub](https://github.com/Sadaku1993/gym-pathplan)に公開していますので、ぜひ参考にしてみて下さい。

## OpenAI Gymのインストール
OpenAI GymのインストールはOpenAIの[インストールガイド](https://github.com/openai/gym)に従います。

```
$ git clone https://github.com/openai/gym.git
$ cd gym
$ pip install -e .
```

## 環境(Environment)づくりの基本

以下の方々が自前の環境セットアップについてわかり易い記事を書いて下さっています。
ここでは後で見直し易いように簡単にまとめ直しました。
[OpenAI Gym で自前の環境をつくる](https://qiita.com/ohtaman/items/edcb3b0a2ff9d48a7def)
[OpenAI Gymでオリジナルの環境を作る](https://omedstu.jimdo.com/2018/05/30/openai-gym%E3%81%A7%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%8A%E3%83%AB%E3%81%AE%E7%92%B0%E5%A2%83%E3%82%92%E4%BD%9C%E3%82%8B/)

### gym.Envを継承したクラスを実装する
gym.Envのクラスは以下のメソッドとプロパティを実装する必要があります。

|メソッド|解説|
|:--|:--|
|_setp(self, action)|actionを実行し、結果を返す|
|_reset(self)|状態を初期化し、初期の観測値を返す|
|_render(self, mode='human', close=False)|環境を可視化する|
|_close(self)|環境を閉じて後処理をする|
|_seed(self, seed=none)|ランダムシードを固定する|

|プロパティ|解説|
|:--|:--|
|action_space|行動(Action)の張る空間|
|observation_space|観測値(Observation)の張る空間|
|reward_range|報酬の最小値を最大値のリスト|

レポジトリ及びファイルの構造は次のようにします。

```
gym-foo/
  README.md
  setup.py
  gym_foo/
    __init__.py
    envs/
      __init__.py
      foo_env.py
```

### gym-foo/gym_foo/__init__.py
子ディレクトリ内の__init__.py
gym.envs.registration.registerを使ってgymに登録します。

```python:gym-foo/gym_foo/__init__.py
from gym.envs.registration import register

register(
  id='foo-v0'
  entry_point='gym_foo.envs.FooEnv',
)
```

### gym-foo/gym_foo/envs/__init__.py
孫ディレクトリ内の__init__.py

```python:gym-foo/gym_foo/envs/__init__.py
from gym_foo.envs.foo_env import FooEnv
```

### gym-foo/gym_foo/envs/foo_env.py
環境(Environment)をClassとして定義します。

```python:gym-foo/gym_foo/envs/foo_env.py
import gym
from gym import error, spaces, utils
from gym.utils import seeding

class FooEnv(gym.Env):
  metadata = {'render.modes': ['human']}

  def __init__(self):
    ...
  def step(self, action): # actionを実行し、結果を返す
    ...
  def reset(self): # 状態を初期化し、初期の観測値を返す
    ...
  def render(self, mode='human', close=False): # 環境を可視化する
    ...
  def close(self): # 環境を閉じて後処理をする
    ...
  def seed(self, seed=None): # ランダムシードを固定する
    ...
```

### 環境の使い方

```python:gym-foo/sample.py
import gym
import gym_foo
env = gym.make('foo-v0')
```

## PlathPlanning用の環境(Environment)構築
オリジナルの環境のセットアップ方法がわかったので、PathPlanning用の環境構築をおこないます。
今回の目標はオリジナルの環境を構築することなので、障害物も何もないシンプルな環境を作ります。
![ezgif.com-video-to-gif-4.gif](https://qiita-image-store.s3.amazonaws.com/0/239786/e7a29bc6-7522-5af8-ad27-40cca85fffbd.gif)
緑の丸がスタート地点、赤の丸がゴール地点、青の丸がロボットです。

レポジトリ及びファイルの構造は次のようにします

```
gym-pathplan/
  README.md
  setup.py
  simple/
    check.py
    simple.py
  gym_pathplan/
    __init__.py
    envs/
      __init__.py
      simple.py
      function/
        raycast.py
```

```python:gym-pathplan/gym_pathplan/__init__.py
from gym.envs.registration import register

register(
  id='gym_pathplan'
  entry_point='gym_pathplan.envs.Simple',
)
```

```python:gym-pathplan/gym_pathplan/envs/__init__.py
from gym_pathplan.envs.sample import Simple
```

### オリジナル環境クラスの設定
それでは、オリジナル環境設定をどんどん書いていきます。

今回の以下の情報を観測できる環境を作成します。

1. Robot State (x[m], y[m], yaw[rad], velocity[m], omega[rad/x])
2. LiDAR (x[m], y[m], angle[rad], distance[m], angleid)
3. Goal (x[m], y[m])
4. Map (np.array([grid_size][grid_size]))

大まかな構成は以下の通りです。

```python:gym-pathplan/gym_pathplan/envs/simple.py
#coding:utf-8

import numpy as np
import math

import gym
from gym import error, spaces, utils
from gym.utils import seeding

import sys
import os

from function.raycast import * #raycast用の関数をimport

class Sample(gym.Env):
  metadata = {'render.modes' : ['human', 'rgb_array']}

  def __init__(self):
    ...
  def reset(self): # 状態を初期化し、初期の観測値を返す
    ...
  def step(self, action): # actionを実行し、結果を返す
    ...
  def observe(self): # 観測結果を算出
    ...
  def resest_map(self): # grid_mapを初期化
    ...
  def reward(self): # 報酬値を返す
    ...
  def is_done(self): # 終端状態に達しているかを判定
    ...
  def is_goal(self): # ゴールに到達したかを判定
    ...
  def is_movable(self): # grid_map内に存在するかを判定
    ...
  def is_collision(self): # 衝突判定
    ...
  def render(self, mode='human', close=False) # 環境を可視化
    ...
```

### action_spaceとobservation_spaceを確認
actionやobservationには最大値と最小値が定められています。それぞれ確認してみましょう。
check.pyを実行してみてください。

```python:gym-pathplan/simple/check.py
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import gym
import gym_pathplan
import numpy as np

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

## action space
print("action space", env.action_space) # action_spaceのデータ型を表示
print("action space low",  env.action_space.low) # action_spaceの最小値を表示
print("action space high", env.action_space.high) # action_spaceの最大値を表示
print(" ")

## observation space
print("observation space", env.observation_space) # observation_spaceのデータ型を表示
print("state low", env.observation_space.spaces['state'].low) # stateの最小値を表示
print("state high", env.observation_space.spaces['state'].high) # stateの最大値を表示

...
```

action_spaceは速度と角速度がBox型で格納された配列
observation_spaceはロボットの状態、ゴール位置、Map情報、LiDAR情報がDict型で格納されています。


### ランダムウォーク
作成した環境でのランダムウォークを行います。

```python:gym-pathplan/simple/simple.py
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

import gym
import gym_pathplan
import numpy as np

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

observation = env.reset()

for _ in range(10000):
    env.render()
    action = env.action_space.sample() # random action
    observation, reward, done, _ = env.step(env.action_space.sample())

    if done:
        env.reset()
```

### LiDARの可視化
RayCastingの結果を可視化するには、self.vis_lidarをTrueに変更して下さい。

![ezgif.com-video-to-gif-2.gif](https://qiita-image-store.s3.amazonaws.com/0/239786/42e38163-aadd-a43c-b0b4-6adbedcebbdc.gif)


## まとめ
おもっていたよりも簡単に自作の環境をつくることができました。
この程度の環境であれば、半日で十分オリジナル環境を自作することができると思います。
厄介なのはレンダリングの部分だと思います。[CartPole](https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py)や[MountainCar](https://github.com/openai/gym/blob/master/gym/envs/classic_control/mountain_car.py)の環境設定を参考にするとオリジナルの環境をセットアップする際に非常に役に立つと思います。

時間に余裕ができ次第、オリジナル環境でマルチエージェント強化学習、逆強化学習、ValueIterationなどを使ったコードをアップロードしていこうと思います。

# Reference
研究室の後輩が深層強化学習を用いたMotion Planningに取り組んでいます。
上記以外の環境の作り方について知りたい方は以下のリンクを参考にしてみてください。
[Navigation_Simulator_for_Mobile_Robot](https://github.com/Jumpei-Arima/Navigation_Simulator_for_Mobile_Robot)