FlappyBird で強化学習の練習 その0: 環境編

# この記事は何

せっかく [Pythonで学ぶ強化学習](https://amzn.to/2NloIsR) をざっと読んだので、手を動かしてみる大作戦です。
FlappyBird という数年前に話題になったゲームがあり、それを強化学習を用いて学習していきたいと思います。
のんびり動かしてみつつ、色々やったことを記録していこうと考えています :muscle: 

- [FlappyBird で強化学習の練習 その0: 環境編](https://qiita.com/cfiken/items/af3d4489e6c6aab9e8bf) ← これ
- [FlappyBird で強化学習の練習 その1: DQN](https://qiita.com/cfiken/items/52f18cfc7f584ee7b5f5)
- [FlappyBird で強化学習の練習 その2: Double DQN](https://qiita.com/cfiken/items/58c890d683f9cd1b6baf)
- [FlappyBird で強化学習の練習 その3: DQN + Dueling network](https://qiita.com/cfiken/items/564da44480d37f6b766b)

この記事は、環境構築です。JupyterLab で動画を見るのに少し手間取ったため、残しておきます。
自分のローカルマシンである mac と、リモート環境である ubuntu で動作確認しています。

# 目次

- FlappyBird を Python (gym) で動かす
- FlappyBird を JupyterLab 上で動かす

# FlappyBird を Python で動かす

コマンドでインストール一発打つだけかと思ってたんですが、そんなことはなかった。
↓を使います。

- [gym](https://github.com/openai/gym)
  - ゲームを強化学習で学習できるような環境を提供する、 OpenAI によるプラットフォーム
- [ple (pygame learning environment)](https://github.com/ntasfi/PyGame-Learning-Environment)
  - pygame (Python でゲーム作るライブラリ)のゲームで強化学習環境を用意してくれるやつ
- [gym-ple](https://github.com/lusob/gym-ple)
  - ple を gym のインターフェースで動かせるやつ
  - 使わなくても良いけど gym ライクにやりたかったので

## 環境構築

※ 私の環境ではこれで動きましたが、人によっては追加でライブラリなど必要かと思います。

ubuntu

```sh
$ sudo apt-get install ffmpeg libav-tools
```

mac

```sh
$ brew install ffmpeg
```

その後

```sh
$ mkdir flappybird; cd flappybird
$ touch Pipfile
$ pipenv install --python 3.6.5
$ vi Pipfile
```
出来た `Pipfile` を開いて、必要そうなライブラリを記述。なぜか `pipenv install [libname]` で ple が入らなかったが、 `Pipfile` に書くと入った(後々使う TensorFlow や matplotlib も入れてます)。

```ini:Pipfile
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]

[packages]
tensorflow = "*"
gym = "*"
jupyterlab = "*"
ipython = "*"
ple = {editable = true, git = "https://github.com/ntasfi/PyGame-Learning-Environment.git"}
pygame = "*"
gym-ple = "*"
matplotlib = "*"

[requires]
python_version = "3.6"
```
ライブラリをインストールする。

```sh
$ pipenv install
```

## 動作確認のサンプルコード

こんな感じにとりあえず gym を通じて動くサンプル (gym-ple からとってきて少し修正) を動かしてみます。
https://github.com/cfiken/flappybird-try/blob/master/00_environment.py

実行すると `render = True` だと(ローカルだと)画面が出て動きます。 `render = False` だと `/tmp` 以下に json のログと mp4 が保存されます。

```py
import logging
import os, sys

import gym
from gym.wrappers import Monitor
import gym_ple

# random agent!
class RandomAgent(object):
    def __init__(self, action_space):
        self.action_space = action_space

    def act(self, observation, reward, done):
        return self.action_space.sample()

render = True  # 画面表示したい場合は True, そうでない場合は False にしてください

if __name__ == '__main__':
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    env = gym.make('FlappyBird-v0' if len(sys.argv)<2 else sys.argv[1])

    if not render:
        outdir = '/tmp/random-agent-results'
        env = Monitor(env, directory=outdir, force=True)

    # This declaration must go *after* the monitor call, since the
    # monitor's seeding creates a new action_space instance with the
    # appropriate pseudorandom number generator.
    env.seed(0)
    agent = RandomAgent(env.action_space)

    episode_count = 100
    reward = 0
    done = False

    for i in range(episode_count):
        ob = env.reset()

        while True:
            action = agent.act(ob, reward, done)
            ob, reward, done, _ = env.step(action)
            if render:
                env.render()
            if done:
                break

    # Dump result info to disk
    env.close()

    logger.info("Successfully ran RandomAgent")
```

```sh
$ pipenv run python 00_environment.py
# render = False の場合は実行が終わるまで待ちます
```

`render = True` なら、ディスプレイにゲーム画面が表示されます。 `render = False` なら、 `/tmp/random-agent-results` 以下にログと mp4 の動画が保存されています。

# JupyterLab で動かす

[こちらのサイト](https://cnc-selfbuild.blogspot.com/2018/04/jupyter-notebookjupyterlab-matplotlib.html) を参考にしました。

Jupyter Notebook だとチュートリアルがあったりして楽に動くっぽいですが、JupyterLab だと下記の理由で大変みたいです。

- JS をうまく使えない?
- nbagg がエラーになる

最終的に HTML5 を使ってアニメーション再生することで動くようにしました。リアルタイムではなく、一度実行したものの最終エピソードを後で再生するようにしています。長いので github へのリンクで。
https://github.com/cfiken/flappybird-try/blob/master/00_environment.ipynb

スクリプト版との差分としては、

- `%matplotlib inline` にする
- `from IPython.display import HTML`
- `env.render()` で取得できる frame をリストに保存しておく
- `animate` であとから保存したリストをHTML5アニメーションにする

です。アニメーションを表示する部分は次のようにしています。

```py
def animate(frames):
    anims = []
    fig = plt.figure()
    plt.axis('off')
    for f in frames:
        im = plt.imshow(f)
        anims.append([im])
    plt.close()
    ani = animation.ArtistAnimation(fig, anims, interval=50)
    return HTML(ani.to_html5_video())
```

一度 `plt.close()` を呼んでいるのは、途中段階の画像が表示されてしまうためです(もっと良いやり方があるかも)。

以上です。
何か間違いやもっと良い方法あればコメントなどいただけると助かります :bow: