ChainerRLをつかって、Minecraftで深層強化学習 ハンズオン

# Note
- 現在はこちらの方法の方がより簡単に環境構築できます [ChainerRL + Minecraftで深層強化学習 ハンズオン [MineRL version]](https://qiita.com/keisuke-umezawa/items/ff9dbdaef3b2ccf75c35)

# Minecraft
みなさん、ご存知Minecraft。私は知らなかったですが、マイクラって略されるんですね。公式のPR動画としては以下のようなものもあります。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/gITUej4Tmb">https://t.co/gITUej4Tmb</a></p>&mdash; Keisuke Umezawa (Chainer Evangelist) (@kmechann) <a href="https://twitter.com/kmechann/status/1072849400458764288?ref_src=twsrc%5Etfw">2018年12月12日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


また、[Minecraft Japan Wiki](https://www26.atwiki.jp/minecraft/)では下記の通り説明されています。

> Minecraftは、2009年5月10日にNotch氏(本名:Markus Persson)が開発を始めた、サンドボックス型のものづくりゲームです。 
レトロゲーを想起させるドットテイストのブロックが溢れる世界で、プレイヤーは建物やその他のものを自由に創造することが出来ます。 
未開の土地を探索したり、洞窟を探検したり、モンスターと戦ったり、植物を育てたり、新しいブロックを手に入れ配置することで、様々なものを作ることができます。 
創造（想像）力次第で小さな家から、ドット絵、地下基地、巨大な城まで何でも作ることができます。
>
> マルチプレイでは、協力して巨大な建築物を作ったり、Player VS Player(PvP)で他プレイヤーと戦うことも出来ます。

この説明にあるようにいくつか、現在の強化学習に適したキーワードがあります。

* 建物やその他のものを自由に作ることできる
* 色々なゲームができる
* マルチプレイできる

このあたりの特性がMinecraftにあるからこそ、Minecraftで強化学習をしようというモチベーションが生まれたのだと思います。

# Marlo Project
このハンズオンでは、Minecraftをシミュレーター環境として深層強化学習を行います。現在、Minecraftを活用した[MARLO](https://www.crowdai.org/challenges/marlo-2018)という深層強化学習のコンテストが行われています。今回はこのコンテストで活用されいる[marLo](https://github.com/crowdAI/marLo)という[OpenAI's Gym](https://github.com/openai/gym)互換のMinecraft環境を使います。OpenAI's Gym互換のため、ChainerRLなどの強化学習フレームワークを簡単に使用することができます(完全ではないが・・・例えば動画の保存に使うwrapperが使えなかったりする)。

marLoでは下記のような環境が用意されており、例えば溶岩の上の一本道を歩くAIを深層強化学習で作ることが出来ます。今回は`MarLo-FindTheGoal-v0`で説明を行い、その後皆さんに新しい課題にチャレンジしていただきます。

|||||
|---|---|---|---|
|``MarLo-MazeRunner-v0`` ![](https://media.giphy.com/media/u45fNQxG59wfnRpzwJ/giphy.gif)|``MarLo-CliffWalking-v0`` ![](https://media.giphy.com/media/ef4lPGNqaLlKr45rWB/giphy.gif)|``MarLo-CatchTheMob-v0`` ![](https://media.giphy.com/media/9A1gHZrWcaS4AYzcIU/giphy.gif)|``MarLo-FindTheGoal-v0`` ![](https://media.giphy.com/media/1gWkQbDsHOfo4kZXZv/giphy.gif)|
|``MarLo-Attic-v0`` ![](https://media.giphy.com/media/47C7AYB3FA6kgrMiQ3/giphy.gif)|``MarLo-DefaultFlatWorld-v0`` ![](https://media.giphy.com/media/L0s9QXuR6vIJh6A0dq/giphy.gif)|``MarLo-DefaultWorld-v0`` ![](https://media.giphy.com/media/4Nx7gYiM9NDrMrMao7/giphy.gif)|``MarLo-Eating-v0`` ![](https://media.giphy.com/media/pObNMjjfcGI5tVhmX6/giphy.gif)|
|``MarLo-Obstacles-v0`` ![](https://media.giphy.com/media/5sYmFFkq7aEMKTbKP4/giphy.gif)|``MarLo-TrickyArena-v0`` ![](https://media.giphy.com/media/1g1bxw2nD3G9fz2WVV/giphy.gif)|``MarLo-Vertical-v0`` ![](https://media.giphy.com/media/ZcaMeSnzLrMY1NWM7f/giphy.gif)|||

# ハンズオンの内容
※ このハンズオンは、https://github.com/keisuke-umezawa/marlo-handson を元に作成しています。

## Requirement

2018/12/14時点では以下が必要です。

- Python 3.5+ environment with
    - Chainer v5.0.0
    - CuPy v5.0.0
    - ChainerRL v0.4.0
    - marlo v0.0.1.dev23

## Azureで環境設定

※ 以下の手順を踏むためには、Azureのsubscriptionが必要です。
※ 他の環境での構築方法の[記事](https://qiita.com/namahoge/items/ae690bdb9cee42ad9394)もあるのでご参考ください。

### 1. Azure CLIのインストール

環境に合わせて下記から選択ください。

1. Windowsの場合
  * [Install Azure CLI on Windows](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?view=azure-cli-latest)

2. Homebrew(macOS)の場合
    
    ```
    $ brew update && brew install azure-cli
    ```

3. pythonを使用する場合

    ```
    $ pip install azure-cli
    ```

### 2. Azureにログイン

```
$ az login
```

### 3. subscriptionの選択

以下のコマンドで、あなたがお持ちのsubscriptionをリストアップできます。

```
$ az account list --all
```

あなたのアカウントに選択したいsubscriptionを設定しましょう。もちろん、[A SUBSCRIPTION ID]はあなたのsubscription IDに置き換えてください。

```
$ az account set --subscription [A SUBSCRIPTION ID]
```

### 4. GPU VMの起動

次に、以下のコマンドでデータサイエンスVMを作成します。`--generate-ssh-keys`は、VMに接続するための鍵を自動で作成し、`~/.ssh/`以下に秘密鍵`id_rsa`、公開鍵`id_rsa.pub`として保存します。

```
$ AZ_USER=[好きなuser名 e.g. kumezawa]
$ AZ_LOCATION=[resource-groupを作ったlocation e.g. eastus]
$ AZ_RESOURCE_GROUP=[resource-groupを作ったlocation e.g. marmo]
$ az vm create \
--location ${AZ_LOCATION} \
--resource-group ${AZ_RESOURCE_GROUP} \
--name ${AZ_USER}-vm \
--admin-username ${AZ_USER} \
--public-ip-address-dns-name ${AZ_USER} \
--image microsoft-ads:linux-data-science-vm-ubuntu:linuxdsvmubuntu:latest \
--size Standard_NC6 \
--generate-ssh-keys
```

うまくいくと、以下のようなメッセージがでてくるはずです。

```
{
  "fqdns": "[YOUR USERNAME].eastus.cloudapp.azure.com",
  "id": "/subscriptions/[YOUR SUBSCRIPTION ID]/resourceGroups/marLo-handson/providers/Microsoft.Compute/virtualMachines/vm",
  "location": "eastus",
  "macAddress": "AA-BB-CC-DD-EE-FF",
  "powerState": "VM running",
  "privateIpAddress": "10.0.0.4",
  "publicIpAddress": "123.456.78.910",
  "resourceGroup": "marLo-handson",
  "zones": ""
}
```

`publicIpAddress`を次の手順で使うので覚えておいてください。

#### Note
`~/.ssh/`以下に秘密鍵`id_rsa`、公開鍵`id_rsa.pub`がある場合、上記コマンドがエラーを起こします。
その場合は自分で鍵を作成し、それを指定して作成することができます。

```
$ az vm create \
--location ${AZ_LOCATION} \
--resource-group ${AZ_RESOURCE_GROUP} \
--name ${AZ_USER}-vm \
--admin-username ${AZ_USERER} \
--public-ip-address-dns-name ${AZ_USER} \
--image microsoft-ads:linux-data-science-vm-ubuntu:linuxdsvmubuntu:latest \
--size Standard_NC6 \
--ssh-key-value [公開鍵のpath e.g. ~/.ssh/id_rsa.pub]
```

#### Note
もし、GPUではなく、CPUインスタンスで起動したい場合は、例えば`--size Standard_D2s_v3`としてください。他の使えるVMのサイズを調べたい場合は、こんな感じで調べられます。

```
az vm list-sizes --location eastus --output table
```

### 5. アクセスに必要なportのopen

```
$ az vm open-port --resource-group ${AZ_RESOURCE_GROUP} --name ${AZ_USER}-VM --port 8000 --priority 1010 \
&& az vm open-port --resource-group ${AZ_RESOURCE_GROUP} --name ${AZ_USER}-VM --port 8001 --priority 1020 \
&& az vm open-port --resource-group ${AZ_RESOURCE_GROUP} --name ${AZ_USER}-VM --port 6080 --priority 1030
```

### 6. VMにssh接続

```
$ AZ_IP=[あなたのvmのIPアドレス e.g. "40.121.36.99"]
$ ssh ${AZ_USER}@${AZ_IP} -i ~/.ssh/id_rsa
```

### 7. MarLoのためのConda環境の作成

VM環境で以下のコマンドを実行してください。

```
$ curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - \
&& distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
&& curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list \
&& sudo apt-get update \
&& sudo apt-get install -y libopenal-dev
```

```
$ conda config --set always_yes yes \
&& conda create python=3.6 --name marlo \
&& conda config --add channels conda-forge \
&& conda activate marlo \
&& conda install -c crowdai malmo matplotlib ipython numpy scipy opencv \
&& pip install git+https://github.com/crowdAI/marLo.git
```

現在インストールされているcudaのバージョンに応じて、適切なcupyとchainerをインストールしましょう。例えば、cuda9.0の場合は、`cupy-cuda90`です。

```
$ nvcc --version
```

```
$ pip install chainer==5.1.0 cupy-cuda90==5.1.0 chainerrl==0.5.0
```


### 8. DockerによるMinecraft clientの起動

```
$ sudo docker pull ikeyasu/marlo:latest
$ VNC_PW=[適当なpassword]
$ sudo docker run -it --net host --rm --name robo6082 -d -p 6080:6080 -p 10000:10000 -e VNC_PW=${VNC_PW} ikeyasu/marlo:latest
```

`http://あなたのvmのIP:6080` にアクセスし、自分で設定したパスワード${VNC_PW}を入力してみてください。

VNC接続によりリモート環境に接続し、その中でMinecraftが起動することを確認できるはずです！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/111482/b9a4da31-dc56-d4e9-87f0-619722ba5b5c.png)

## ハンズオンをはじめよう

### 0. Conda環境がactivateされているか確認

```
$ conda info -e
# conda environments:
#
base                     /anaconda
marlo                 *  /anaconda/envs/marlo
py35                     /anaconda/envs/py35
py36                     /anaconda/envs/py36
```

もし、先ほど作った`marlo`がactivateされていない場合は、下のコマンドを実行してください。

```
$ conda activate marlo
```

### 1. Hands-on レポジトリのClone

```
$ git clone https://github.com/keisuke-umezawa/marlo-handson.git
$ cd marlo-handson
```

### 2. malroテストスクリプトの実行

```
$ python test_malmo.py
```

暫く待つと以下のような画面が表示されるはずです。

<img width="700" alt="スクリーンショット 2018-12-13 21.20.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/111482/811839a5-55ad-9acd-04a8-8eb06cef2fc7.png">


これは以下のpythonスクリプトを実行します。

[test_malmo.py](https://github.com/keisuke-umezawa/marlo-handson/blob/master/test_malmo.py)

```python
import marlo


def make_env(env_seed=0):
    join_tokens = marlo.make(
        "MarLo-FindTheGoal-v0",
        params=dict(
            allowContinuousMovement=["move", "turn"],
            videoResolution=[336, 336],
            kill_clients_after_num_rounds=500
        ))
    env = marlo.init(join_tokens[0])

    obs = env.reset()
    action = env.action_space.sample()
    obs, r, done, info = env.step(action)
    env.seed(int(env_seed))
    return env


env = make_env()
obs = env.reset()


for i in range(10):
    action = env.action_space.sample()
    obs, r, done, info = env.step(action)
    print(r, done, info)
```

* `"MarLo-FindTheGoal-v0"`を変更すると環境を変更できます
* `obs`はnumpy形式の画像データです。
* `r`は前回の行動に対する報酬です
* `done`はゲームが終了したか真偽値です。
* `info`はその他情報が入っています。


#### 課題1： 上記などを確認したり、変更したりして、遊んでみてください。

### 3. ChainerRLによるDQNの訓練スクリプトの実行

まずは以下のコマンドを実行してみてください。そうすると、ChainerRLでDQNという強化学習のモデルの訓練を０から開始できます。

```
$ python train_DQN.py
```

適当なタイミングでCTRL+Cを押して、スクリプトを止めてみましょう。そうすると以下のようなディレクトリができているはずです。xxxx_exceptに今まで学習したモデルが保存されています。

```
$ ls results/
3765_except  scores.txt
```

#### Note
cpuのみで動作させたい場合は、以下のオプションを追加して動かしてみてください。

```
$ python train_DQN.py --gpu -1
```

### 3. 保存したモデルの動作確認

以下のコマンドで、学習したモデルのロードと、その動作確認ができます。

```
$ python train_DQN.py --load results/3765_except --demo
```

どうでしょうか。たいして学習させていないのでちゃんと動かないはずです。

### 4. 保存したモデルから訓練の開始

以下のコマンドを使用すれば、以前保存したモデルから学習を再開できます。

```
$ python train_DQN.py --load results/3765_except
```

また、こちらである程度訓練したモデルを用意したので、そこから学習を開始することもできます。ただし、単に``python train_DQN.py``を実行して作成されたモデルですので、コードに変更を加えたり、環境を変更したりした場合動かないことがあります。

```
$ wget https://github.com/keisuke-umezawa/marlo-handson/releases/download/v0.2/157850_except.tar.gz
$ tar -xvzf 157850_except.tar.gz
$ python train_DQN.py --load 157850_except
```

#### 課題2: train_DQN.pyのコードを読もう

このスクリプト自体は以下の通りです。

[train_DQN.py](https://github.com/keisuke-umezawa/marlo-handson/blob/master/train_DQN.py)


```python

# ...省略...

def main():
    parser = argparse.ArgumentParser()
    
    # ...省略...

    # Set a random seed used in ChainerRL.
    misc.set_random_seed(args.seed, gpus=(args.gpu,))

    if not os.path.exists(args.out_dir):
        os.makedirs(args.out_dir)

    experiments.set_log_base_dir(args.out_dir)
    print('Output files are saved in {}'.format(args.out_dir))

    env = make_env(env_seed=args.seed)

    n_actions = env.action_space.n

    q_func = links.Sequence(
        links.NatureDQNHead(n_input_channels=3),
        L.Linear(512, n_actions),
        DiscreteActionValue
    )

    # Use the same hyper parameters as the Nature paper's
    opt = optimizers.RMSpropGraves(
        lr=args.lr, alpha=0.95, momentum=0.0, eps=1e-2)

    opt.setup(q_func)

    rbuf = replay_buffer.ReplayBuffer(10 ** 6)

    explorer = explorers.LinearDecayEpsilonGreedy(
        1.0, args.final_epsilon,
        args.final_exploration_frames,
        lambda: np.random.randint(n_actions))

    def phi(x):
        # Feature extractor
        x = x.transpose(2, 0, 1)
        return np.asarray(x, dtype=np.float32) / 255

    agent = agents.DQN(
        q_func,
        opt,
        rbuf,
        gpu=args.gpu,
        gamma=0.99,
        explorer=explorer,
        replay_start_size=args.replay_start_size,
        target_update_interval=args.target_update_interval,
        update_interval=args.update_interval,
        batch_accumulator='sum',
        phi=phi
    )

    if args.load:
        agent.load(args.load)

    if args.demo:
        eval_stats = experiments.eval_performance(
            env=env,
            agent=agent,
            n_runs=args.eval_n_runs)
        print('n_runs: {} mean: {} median: {} stdev {}'.format(
            args.eval_n_runs, eval_stats['mean'], eval_stats['median'],
            eval_stats['stdev']))
    else:
        experiments.train_agent_with_evaluation(
            agent=agent,
            env=env,
            steps=args.steps,
            eval_n_runs=args.eval_n_runs,
            eval_interval=args.eval_interval,
            outdir=args.out_dir,
            save_best_so_far_agent=False,
            max_episode_len=args.max_episode_len,
            eval_env=env,
        )


if __name__ == '__main__':
    main()
```

* 参考リンク
  * [NatureDQNHead](https://github.com/chainer/chainerrl/blob/v0.5.0/chainerrl/links/dqn_head.py#L13-L36)
  * [DiscreteActionValue](https://github.com/chainer/chainerrl/blob/v0.5.0/chainerrl/action_value.py#L53-L102)
  * [ReplayBuffer](https://github.com/chainer/chainerrl/blob/v0.5.0/chainerrl/replay_buffer.py#L131-L163)
  * [LinearDecayEpsilonGreedy](https://github.com/chainer/chainerrl/blob/v0.5.0/chainerrl/explorers/epsilon_greedy.py#L51-L90)
  * [DQN](https://github.com/chainer/chainerrl/blob/v0.5.0/chainerrl/agents/dqn.py#L84)

#### 課題3： 性能をあげよう

強化学習の性能を改善するには以下のような手段があります。

* モデルの変更
* ReplayBufferの変更
* そのたパラメータの変更

また、[Rainbow: Combining Improvements in Deep Reinforcement Learning](https://arxiv.org/pdf/1710.02298.pdf)という、強化学習に関する手法を色々試して、その性能向上を評価した論文もあります。これによると以下によって性能向上を試すことができる可能性があります。

* PrioritizedReplayBufferの使用
* DDQNの使用
* etc