Stable BaselinesとOpenAI Gymで強化学習の環境構築

強化学習のライブラリstable baselinesを使ってpythonで強化学習を行うためのメモ。

環境：Ubuntu 20.04

#目次

[1. 必要要件まとめ](#必要要件まとめ)
[2. インストール前の注意](#インストール前の注意)
[3. インストール手順](#インストール手順)
[4. 使い方](#使い方) 　
　　[4-1. OpenAI Gym](#openai-gym) 
　　[4-2. Stabe Baselines 基本編](#stable-baselines-基本編)
　　[4-3. Stabe Baselines 環境の並列化](#stable-baselines-環境の並列化)

<a id="a1"></a>
#必要要件まとめ
Python 3.7
pip 20.1.1
gym
TensorFlow 1.14.0
stable-baselines 2.10.1

強化学習を行うライブラリはたくさんあるが、ここではstable-baselinesを使用する。
理由は、KerasRLやChainerRLと比較して
・ 対応している強化学習アルゴリズムが多い
・ バグがない
・ 公式Documentが充実している
からだ。

#インストール前の注意
stable-baselinesは中でtensorflowを使用するが、tensorflow2には対応していないため、tensorflow１をインストールする必要がある。
最新のpip3==21.x.xではtensorflow1がインストールできないため、pip3==20.x.xを使用しなければならない。
そのためにPython3.8ではなく、Python3.7にしなければならないということだ。

ちなみにUbuntu20.04ではシステムのPythonがPython3.8になってしまっているのでpyenvやcondaなどを使用してPythonのバージョンを変更しなければならない。
またほかのプログラムでtensorflow2を使用する場合もpythonの仮想環境を作成してバージョンを分ける必要がある。

参考サイト
[Pyenvを使用してUbuntuにPythonをインストールする](https://qiita.com/micheleno13/items/39ad85cfe44ca32f53ee)
[venv: Python 仮想環境管理](https://qiita.com/fiftystorm36/items/b2fd47cf32c7694adc2e)

#インストール手順
Python3.7が入れられればインストールは簡単だ。

```
$ sudo apt install python3-pip
$ pip3 install gym tensorflow==1.14.0 stable-baselines
```

これでインストールは完了だ。
これらをインストールする際に、自動でnumpyなど必要なライブラリもインストールされる。

#使い方
##OpenAI Gym
```python
import gym

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

#環境を初期状態に戻す（返り値は状態）
obs = env.reset()

#シミュレーション開始
for id_step in range(max_step):
    #環境を1ステップ進める（引数は環境で用意されたサンプルアクション）
    obs, reward, done, info = env.step(env.action_space.sample())
    #描画
    env.render()
    #終了
    if done:
        break
```

OpenAI gymの詳しい使い方は[OpenAI Gym 入門](https://qiita.com/ishizakiiii/items/75bc2176a1e0b65bdd16)を参照。
[公式ドキュメント（英語）](https://github.com/openai/gym)

##Stable Baselines 基本編
stable-baselinesはopenAIが開発したライブラリであるため、gym形式の環境にしか強化学習を行えない。
以下はCartPole環境に対しDQNで学習を行った例だ。

```python
#!/usr/bin/env python3                                                                      
 import os
 import gym
 from stable_baselines.bench import Monitor
 from stable_baselines.deepq.policies import MlpPolicy
 from stable_baselines.common.vec_env import DummyVecEnv, SubprocVecEnv
 from stable_baselines.common.cmd_util import make_vec_env
 from stable_baselines.common.evaluation import evaluate_policy
 from stable_baselines import DQN
  
 def main():
     #ハイパーパラメータ設定
     time_steps = 25000
  
     #ログ保存先
     log_dir = './logs/'
     os.makedirs(log_dir, exist_ok=True)
     model_name = 'cartpoleDQN'
  
     #環境作成
     env = gym.make('CartPole-v0')
  
     #強化学習モデルの作成
     model = DQN(MlpPolicy, env, verbose=1, tensorboard_log=log_dir)
  
     #学習
     model.learn(total_timesteps=time_steps)
  
     #モデル評価
     eval_env = gym.make('CartPole-v0')
     mean_reward, std_reward = evaluate_policy(model, eval_env, n_eval_episodes=10)
     print('Mean reward: {} +/- {}'.format(mean_reward, std_reward))
  
     #モデルの保存
     model.save(model_name)
  
  
 if __name__ == '__main__':
     main()
```

実行すると警告が大量に出るがこれはstable-baselines内でtensorflow1の古いコードを用いていることが原因なので無視してよい。最終的に以下のようなコードが出れば成功だ。

```
--------------------------------------
| % time spent exploring  | 77       |
| episodes                | 100      |
| mean 100 episode reward | 23.6     |
| steps                   | 2332     |
--------------------------------------
--------------------------------------
| % time spent exploring  | 2        |
| episodes                | 200      |
| mean 100 episode reward | 90.2     |
| steps                   | 11349    |
--------------------------------------
Mean reward: 115.0 +/- 8.160882305241266
```

コードの細部について解説していく。

```python
model = DQN(MlpPolicy, env, verbose=1, tensorboard_log=log_dir)
```
第一引数のMlpPolicyはDQNで用いるニューラルネットワークの定義だ。MlpPolicyはstable-baselinesで定義されている最もシンプルなニューラルネットワークである。詳細は[こちら](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#mlp-policies)

第二引数のenvはこのモデルが対象とする環境だ。これによりMlpPolicyの入力層や出力層のノード数を決定する。

第三引数のverboseはコンソールに学習経過を出力するタイプだ。値は0, 1, 2の３つであり、大きいほど詳細に出力される。

第四引数のtensorboard_logは学習経過をtensorboardで観察するための出力ファイルだ。
学習後や学習中でもターミナルをもう一つ開き、

```
$ tensorboard --logdir=./logs/
```
とコマンドを実行し、表示されたURLをクリックするとtensorboardを開くことができる。

```
model.learn(total_timesteps=time_steps)
```
ここでtotal_timestepsは学習で実行される総ステップ数であり、総エピソード数ではない。つまり
total_timesteps = （1エピソードのステップ数）×（総エピソード数）
となる。


##Stable Baselines 環境の並列化
ここ数年で出てきたA2CやPPOなどの強化学習手法は環境を並列化して学習を行う。
ここでは環境を並列化する方法を解説する。
###1. make_vec_env
単一プロセスで複数の環境を実行するもの。最もよく使われるもの。
環境の並列数だけ設定すればよく、あとはいい感じに自動で設定してくれる。
###2. SubprocVecEnv
環境の数だけプロセスを用意し、1環境1プロセスで強化学習を行っていく。通常はプロセス間通信の遅延により、make_vec_envよりも時間がかかる。しかし、1ステップの処理が重い環境ではmake_vec_envよりも高速となる場合がある。
（ちなみに自分の場合は、カスタムgym環境を作って強化学習を行った際はSubprocVecEnvはmake_vec_envの10倍高速になった。）

```python:サンプルコード
#!/usr/bin/env python3
import os 
import gym
from stable_baselines.bench import Monitor
from stable_baselines.common.policies import MlpPolicy
from stable_baselines.common.vec_env import DummyVecEnv, SubprocVecEnv
from stable_baselines.common.cmd_util import make_vec_env
from stable_baselines.common.evaluation import evaluate_policy
from stable_baselines import PPO2

def main():
    #ハイパーパラメータ
    n_envs = 16
    time_steps = int(1e+5)
    multiproc = False

    #ログ保存先
    log_dir = './logs/'
    os.makedirs(log_dir, exist_ok=True)
    model_name = 'ppo2'
 
    #並列環境作成
    if multiproc == False:
        env = make_vec_env('CartPole-v0',
                n_envs=n_envs,
                monitor_dir=log_dir)
    else:
        env = SubprocVecEnv([lambda: Monitor(gym.make('CartPole-v0'),
                filename=log_dir+'{}.monitor.csv'.format(i), allow_early_resets=False) for i in range(n_envs)],
                start_method='spawn')

    #強化学習モデルの作成
    model = PPO2(MlpPolicy, env, verbose=1)

    #学習
    model.learn(total_timesteps=time_steps)

    #モデルの保存
    model.save(model_name)
```
SubprocVecEnvで各環境に対しログ保存ファイルを作ろうとしたがうまくいかず。。。
tensorboard用のログファイルは作成できるのですが各エピソードの記録を記したcsvファイルの作成がうまくいかず...
解決策をご存じの方がいたらコメントお願いします。
[](
gym型のカスタム環境を作る方法\
[番外編] カスタムgym環境の演算をC/C++で高速化\
)