Unity 2017 で Machine Learning Agents を使ってみる

## Machine Learning Agents とは
　Machine Learning Agents とは、Unity 2017 から利用可能（厳密には一部は Unity 5 でも動く様だが、試していないので分からない）になっている、Unity を通じて機械学習をさせるためのフレームワークです。リポジトリは、下記になります。

- [ml-agents](https://github.com/Unity-Technologies/ml-agents)

　内部的には [PPO](https://blog.openai.com/openai-baselines-ppo/) という学習アルゴリズムに対応しており、これを TensorFlow 経由で実行している、というイメージです。PPO 以外でも、自分でモデルを組み立てれば、それを使ったりもできる様ですが、試していないので詳しくは分かりません。

　Unity に関してはいつもドキュメントがクソみたいに分かりにくくてイライラする事が多いですが、今回も御多分に洩れず、**説明がめちゃくちゃ分かりにくい！**😞 というか、多分ちゃんと説明する気もないのでしょう。β版だし分かる人だけ分かって使えればいいという空気をひしひしと感じます。私はつい最近ディープラーニングについて学び始めた初学者なので、Python のエコシステムもあまり分かっていないし、かなりキツかった（今もキツい）です。しかし、私が理解した事を書き残しておく事でもし他の方の役に立てれば嬉しいという事で、ここに書き記しておきます。

　なお、何か間違っている事がありましたらコメント等でお教え頂けると大変嬉しいです。よろしくお願い致します。

## 基本構造
　今回一番困ったのがこれです。**何やら色々なコンポーネントがありすぎて、何が何だか全く分かりません**。それぞれがどう繋がっているのかいまいち分からず苦労したのですが、私なりに理解した事としては、下記の様な事です。

　すごく簡単に言うと…

- Unity 上で設定を施したシーンを、PC アプリ（.app）として書き出す ← これがそもそも分かりにくい😂
- Python からそのアプリを呼び出して起動し、学習させる ← Python が .app を起動するというのも分かりにくい😂
    - 内部的にはソケット経由で通信がされている
    - Python 側が内部で TensorFlow を利用している
- 学習の成果は自動的にファイルに書き出される
- Unity のコンポーネントの `Brain`（GameObject をどう動かすかの役割を担うもの）の動作方法には重要なものとして Internal, External がある
    - `Internal` は成果ファイルをベースに学習した成果で動かす。デプロイする際に使うイメージ
    - `External` は学習させながら動かす。デプロイに使うモデルを書き出すために使うイメージ
    - 他にもあるがここでは省略

　という感じかなと思います。そもそも構造からして結構分かりづらい（汗）細かいコンポーネントの説明は、下記です。

- Unity 側
    - `ML-AgentsWithPlugin.unitypackage` Unity 側で利用するものをパッケージにしたもの。学習をさせたり、学習データさせたデータから意思決定をさせてそれを GameObject の行動に反映させるまでのフローを繋げるためのコンポーネントなどが含まれている。[TensorFlowSharp](https://qiita.com/dora-gt/items/9bad1dd81ca07e1ca787) を同梱する事で、Python なしでもデプロイできる様にしている。実機でもプレイしたりしたいのであれば、こちらを利用する。
    - `ML-AgentsNoPlugin.unitypackage` 上記のパッケージの TensorFlowSharp を同梱していないバージョン。
- Python 側
    - `Jupyter` Python のコードをウェブサイト上で説明などを見ながら実行できるもの。今回の場合、コードと説明を見ながら動かしたいのでなければ、動作上必須ではない。これも結構ややこしくて Jupyter 経由で実行するのがデフォルトだと思い込んでいると Jupyter を起動してブラウザからコードを実行して…など、必要ない作業を繰り返す事になってしまうので注意。
    - `tensorboard` TensorFlow の学習状況などを視覚的に確認できる様にしてくれるツール。今回の場合、動作上必須ではないが、見えると結構楽しい。
    - `PPO.ipynb` Jupyter 経由で PPO を実行したい場合はこれを利用するが、必須ではない。
    - `Basics.ipynb` PPO 以外で自分でモデルを書いてみたい人はこれを参考にすればよい模様。
    - `ppo.py` **PPO で機械学習させるための本丸**。 `PPO.ipynb` を使えば Jupyter 経由でも実行できるが、これ自体を直接実行していれば、Jupyter を実行する必要はない。

## インストール
　実際に Machine Learning Agents をインストールするには、大きく下記の2つの作業を行う必要があります。

1. `ML-AgentsWithPlugin.unitypackage` もしくは `ML-AgentsNoPlugin.unitypackage` を Unity のプロジェクトに追加する
2. Python 側のライブラリをインストールする

　一見すると「はいはい」という感じだとは思いますが、これをリポジトリにある README.md のままインストールするとちょっと微妙な事になります。どう微妙になるかと言うと、下記の様な感じです。

1. `ML-AgentsWithPlugin.unitypackage` をインストールした場合、そのまま git 管理すると、100MB を超えるファイルがあるために push できなくなる
2. TensorFlow を既にインストールしている人は virtualenv を使っている可能性が高いと思うが、README のままインストールすると全体にインストールされてしまう

　2番に関しては Python に慣れている方には当たり前の作業かもしれませんが、私は普段 Python あまり使わないのでやってしまった後に気付きました…😞　後から削除するのはめちゃくちゃ大変だと思いますので、virtualenv してから実行するのがよいと思います（詳しい方、何か間違いなどありましたらご指摘頂けますと幸いです）。

　やり方については、これから個別に書きます。

### `ML-AgentsWithPlugin.unitypackage` のインストール
　これは ml-agents の [unity-environment の README](https://github.com/Unity-Technologies/ml-agents/tree/master/unity-environment) にも書いてある様に、以下からダウンロード可能です。

- [ML-Agents package with TensorflowSharp](https://s3.amazonaws.com/unity-agents/ML-AgentsWithPlugin.unitypackage)

　今回はモバイルにもデプロイする可能性を考慮して with TensorflowSharp のバージョンをインストールします。パッケージをダウンロードしたら、ダブルクリックで Unity に取り込む事ができると思います。ただ、ここで忘れてはいけないのが、**サイズの大きなファイルを git lfs 管理にする**という事です。これをしないと、下記の様に大きなファイルがあるから push できません！という旨の警告をされて push できなくなってしまいます。

```
$ git push
Counting objects: 252, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (252/252), done.
Writing objects: 100% (252/252), 203.04 MiB | 11.22 MiB/s, done.
Total 252 (delta 93), reused 0 (delta 0)
remote: Resolving deltas: 100% (93/93), completed with 2 local objects.
remote: warning: File Assets/ML-Agents/Plugins/Computer/libtensorflow.bundle is 85.33 MB; this is larger than GitHub Enterprise's recommended maximum file size of 50.00 MB
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote: error: File Assets/ML-Agents/Plugins/iOS/libtensorflow-core.a is 459.95 MB; this exceeds GitHub Enterprise's file size limit of 100.00 MB
To git@github.com:dora-gt/ML-Agents.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'git@github.com:dora-gt/ML-Agents.git'
```

　そうすると、[この記事](https://help.github.com/articles/removing-sensitive-data-from-a-repository/)にある様にコミットした過去の履歴を遡って該当のファイルを削除するといった面倒な事になりますので、予め追加しておきましょう。具体的には、下記の様にします。

```.sh
# 先ずは git lfs 自体のインストール（インストール済みの人は省略してよい）
$ brew install git-lfs

# 対象のリポジトリにいるという前提で
# この時点では、フックは何もない
$ ls -a .git/hooks/
.  ..

# 対象のリポジトリに git lfs 用のフックを仕掛ける
$ git lfs install
Updated git hooks.
Git LFS initialized.

# フックが色々仕掛けられている
$ ls -a .git/hooks/
.             ..            post-checkout post-commit   post-merge    pre-push

# サイズの大きいファイルを対象に追加する
$ git lfs track Assets/ML-Agents/Plugins/Computer/libtensorflow.bundle
$ git lfs track Assets/ML-Agents/Plugins/iOS/libtensorflow-core.a

#（コミット作業は省略）

# ls-files コマンドで LFS 管理下になっている事が分かる
$ git lfs ls-files
e26a8aa358 * Assets/ML-Agents/Plugins/Computer/libtensorflow.bundle
5f3550711c * Assets/ML-Agents/Plugins/iOS/libtensorflow-core.a
```

　なお、もう１つの解決方法として、`.gitignore` に大きめのファイルを追加する、という方法もあります。ml-agents のリポジトリ直下にある `.gitignore` にある下記の記述を追加しておけばよいと思いますが、これをやるとリポジトリをクローンしてきただけではビルドできない状態になるので注意が必要です。

```.gitignore:.gitignore
# Tensorflow Sharp Files
/unity-environment/Assets/ML-Agents/Plugins/Android*
/unity-environment/Assets/ML-Agents/Plugins/iOS*
/unity-environment/Assets/ML-Agents/Plugins/Computer*
/unity-environment/Assets/ML-Agents/Plugins/System*
```

### ml-agents のリポジトリをクローンする
　Python 関連のファイルが必要なので、ml-agents のリポジトリを適当な場所にクローンします。

```.sh
$ git clone git@github.com:Unity-Technologies/ml-agents.git
```

### Python 側のライブラリをインストール
　次に、クローンしたリポジトリ内で、次の様に実行し、virtualenv の中に諸々をインストールします。

```.sh
# クローンしたリポジトリ内に移動
$ cd ml-agents

# virtualenv を python ディレクトリの中に設定
# virtualenv はインストールされている前提なので、ない場合はインストールして下さい
# これは特に意味はなく、このディレクトリでなくても OK です
$ virtualenv --system-site-packages -p python3 python
Running virtualenv with interpreter /Users/dora-gt/anaconda/bin/python3
Using base prefix '/Users/dora-gt/anaconda'
New python executable in /Users/dora-gt/Documents/git/ml-agents/python/bin/python3
copying /Users/dora-gt/anaconda/bin/python3 => /Users/dora-gt/Documents/git/ml-agents/python/bin/python3
copying /Users/dora-gt/anaconda/bin/../lib/libpython3.6m.dylib => /Users/dora-gt/Documents/git/ml-agents/python/lib/libpython3.6m.dylib
Also creating executable in /Users/dora-gt/Documents/git/ml-agents/python/bin/python
Installing setuptools, pip, wheel...done.

# 設定した virtualenv を有効にする
# (python) と表示される事を確認
$ source ./python/bin/activate

# python ディレクトリ内に設定されているパッケージをインストール
$ cd python
$ pip install .
Processing /Users/dora-gt/Documents/git/ml-agents/python
Collecting tensorflow>=1.0 (from unityagents==0.1.1)
  Using cached tensorflow-1.3.0-cp36-cp36m-macosx_10_11_x86_64.whl
# (中略)
Installing collected packages: tensorflow, unityagents
  Running setup.py install for unityagents ... done
Successfully installed bleach-1.5.0 docopt-0.6.2 html5lib-0.9999999 jupyter-1.0.0 markdown-2.6.9 mock-2.0.0 pbr-3.1.1 protobuf-3.4.0 pytest-3.2.3 tensorflow-1.3.0 tensorflow-tensorboard-0.1.8 unityagents-0.1.1

# python ディレクトリ以下の lib/python3.6/site-packages/ 以下に色々インストールされている
$ ls -al lib/python3.6/site-packages
total 72
drwxr-xr-x  38 dora-gt  1522739515   1216 10  9 17:01 .
drwxr-xr-x  57 dora-gt  1522739515   1824 10  9 16:57 ..
drwxr-xr-x   9 dora-gt  1522739515    288 10  9 17:00 Markdown-2.6.9.dist-info
drwxr-xr-x   6 dora-gt  1522739515    192 10  9 17:01 __pycache__
drwxr-xr-x  41 dora-gt  1522739515   1312 10  9 17:01 _pytest
drwxr-xr-x   8 dora-gt  1522739515    256 10  9 17:00 bleach
drwxr-xr-x   9 dora-gt  1522739515    288 10  9 17:00 bleach-1.5.0.dist-info
drwxr-xr-x   9 dora-gt  1522739515    288 10  9 17:01 docopt-0.6.2.dist-info
-rw-r--r--   1 dora-gt  1522739515  19946 10  9 17:00 docopt.py
-rw-r--r--   1 dora-gt  1522739515    126 10  9 16:57 easy_install.py
drwxr-xr-x  29 dora-gt  1522739515    928 10  9 17:01 external
drwxr-xr-x   3 dora-gt  1522739515     96 10  9 17:00 google
drwxr-xr-x  17 dora-gt  1522739515    544 10  9 17:00 html5lib
drwxr-xr-x   9 dora-gt  1522739515    288 10  9 17:00 html5lib-0.9999999.dist-info
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:01 jupyter-1.0.0.dist-info
-rw-r--r--   1 dora-gt  1522739515    177 10  9 17:00 jupyter.py
drwxr-xr-x  16 dora-gt  1522739515    512 10  9 17:00 markdown
drwxr-xr-x   6 dora-gt  1522739515    192 10  9 17:01 mock
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:01 mock-2.0.0.dist-info
drwxr-xr-x  18 dora-gt  1522739515    576 10  9 17:01 pbr
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:01 pbr-3.1.1.dist-info
drwxr-xr-x  23 dora-gt  1522739515    736 10  9 16:57 pip
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 16:57 pip-9.0.1.dist-info
drwxr-xr-x   7 dora-gt  1522739515    224 10  9 16:57 pkg_resources
-rw-r--r--   1 dora-gt  1522739515    539 10  9 17:00 protobuf-3.4.0-py2.7-nspkg.pth
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:00 protobuf-3.4.0.dist-info
drwxr-xr-x  11 dora-gt  1522739515    352 10  9 17:01 pytest-3.2.3.dist-info
-rw-r--r--   1 dora-gt  1522739515   1804 10  9 17:00 pytest.py
drwxr-xr-x  38 dora-gt  1522739515   1216 10  9 16:57 setuptools
drwxr-xr-x  12 dora-gt  1522739515    384 10  9 16:57 setuptools-36.5.0.dist-info
drwxr-xr-x  15 dora-gt  1522739515    480 10  9 17:00 tensorboard
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:01 tensorflow
drwxr-xr-x  10 dora-gt  1522739515    320 10  9 17:01 tensorflow-1.3.0.dist-info
drwxr-xr-x  11 dora-gt  1522739515    352 10  9 17:00 tensorflow_tensorboard-0.1.8.dist-info
drwxr-xr-x   7 dora-gt  1522739515    224 10  9 17:01 unityagents
drwxr-xr-x   8 dora-gt  1522739515    256 10  9 17:01 unityagents-0.1.1-py3.6.egg-info
drwxr-xr-x  18 dora-gt  1522739515    576 10  9 16:57 wheel
drwxr-xr-x  11 dora-gt  1522739515    352 10  9 16:57 wheel-0.30.0.dist-info
```

　次に、Jupyter で今設定した virtualenv を参照できる様にします。そうしないと、インストールした TensorFlow などのパッケージが Jupyter から参照できないからです。[Installing the IPython kernel](http://ipython.readthedocs.io/en/stable/install/kernel_install.html#kernels-for-different-environments) を参考に、設定しました。

```.sh
# source して、(python) となっている状態で実行する
# --name は内部的に利用される名前、--display-name は実際に GUI 上で表示される名前を好きに設定する
$ python -m ipykernel install --user --name ml-python --display-name "ml-python"
Installed kernelspec ml-python in /Users/dora-gt/Library/Jupyter/kernels/ml-python

# この状態で Jupyter を起動すると、下図の様にメニューから ml-python が選択できる様になっている
$ jupyter notebook
```
　![image.png](https://qiita-image-store.s3.amazonaws.com/0/71179/13f4a40d-350b-9754-9d91-c7f75cf6bbc0.png)

　これで、Jupyter から諸々実行できる様になりました。まぁ、実際には `ppo.py` を実行すると思いますのであまり必要ないと言えばないのですが、Python 界では有名なパッケージぽいので、使える様にしておいて損はないでしょう。

## サンプルを使って学習させてみる
　ここではサンプルとして、板の上でボールを転がして落ちない様にする `3DBall` を実行してみましょう。

　![image.png](https://qiita-image-store.s3.amazonaws.com/0/71179/dc80e07f-634a-f657-13fb-afa748fbfcd0.png)

### Unity 側の操作
　先ほど Unity 側では `ML-AgentsWithPlugin.unitypackage` をインストールしたと思いますので、下地は整っています。その上でやらなければいけない事がいくつかあり、下記になります。

- `Assets/ML-Agents/Examples/3DBall/Scene` のシーンファイルを開く
- `Edit -> Project Settings -> Player` から、下記の設定を行う
    - `Resolution and Presentation -> Run in Background` にチェックを入れる
    - `Resolution and Presentation -> Display Resolution Dialog` を `Disabled` にする
- シーンヒエラルキー上の `Ball3DAcademy/Ball3DBrain` のインスペクタで `Brain` コンポーネントの `Type of Brain` が `External` になっている事を確認する
- `File -> Build Settings` を開く
- デバッグログが見たければ `Development Build` にチェックを入れる
- ビルドして、先ほどの python ディレクトリに `3DBall` という名前で保存する
    - PC, Mac & Linux Standalone の形式である必要があります

### Python 側の操作
　ここまでで Unity 側の準備はできました。次に Python 側です。

```.sh
# 先ほどの python のディレクトリで、virtualenv を有効にした状態で、以下のコマンドを実行する
$ python ppo.py 3DBall --train
```

　これで、先ほどビルドしたアプリが自動的に立ち上がり、学習が開始されたはずです。ここで注意なのですが、ここで与えている `3DBall` という名前は内部的に `env_name` という呼ばれ方をしていますが、これは**ビルドした時の `.app` の名前と一致していなければいけません**。どこにも書いていないので私はハマりました。ほーりーしっと。

　ただ、このままだと時間がかかるので、`--max-steps=<n>` というオプションを与える事で最大実行数を指定する事ができます。仮に100などにして実行すると、割とすぐ終わって `python/models/ppo/3DBall.bytes` というファイルが書き出されているはずです。これが学習データになり、`Brain` のモードを `Internal` にした時にデータとして指定してあげるファイルになります。ただし、これはファイルが書き出される事を確認したいがためにステップ数を少なくしただけで、学習が適切に行われているかは分かりませんので注意して下さい。

## サンプルを使って学習させたデータを実行してみる
　さて、ここまでで学習してデータを取り出す事ができたので実際にそれを使ってエディタ上で動かしてみます。

### Unity 側の操作
　学習させたモデルをデプロイするには、Unity 2017 以降で、TensorFlowSharp のプラグインがインストールされている必要があります。`ML-AgentsWithPlugin.unitypackage` のパッケージをインストールしていれば問題ありません。その上で、以下の設定を行います。

- `Edit -> Project Settings -> Player` から、以下の設定を行い、エディタを再起動する
    - `Other Settings` から `Scripting Runtime Version` を `Experimental (.NET 4.6 Equivalent)` にする
    - `Scripting Defined Symbols` のフィールドに、`ENABLE_TENSORFLOW ` を追加する
- 学習させた `3DBall.bytes` を `Assets/ML-Agents/Examples/3DBall/TFModels/` 以下にコピーする
    - 元からファイルがある場合は上書きでもよい
- シーンヒエラルキー上の `Ball3DAcademy/Ball3DBrain` のインスペクタで `Brain` コンポーネントの設定を以下の様に設定する
    -  `Type of Brain` を `Internal` にする
    - 出てきたインスペクタの項目の `Graph Model` の欄に先ほどの `3DBall.bytes` を指定する
    - `Graph Placeholder` のサイズを1にする
    - `epsilon` という `Name` の `Placeholder` を追加し、`Min:0`, `Max:0` に設定する

　![image.png](https://qiita-image-store.s3.amazonaws.com/0/71179/c41c8cf0-a0fb-6609-a05e-c26bb9323d82.png)


　この状態でプレイボタンを押せば、学習したデータで実行ができます。多少プルプルしますが（笑）最終的に、ボールがほぼ静止状態に収束すると思います。

## 自分でプロジェクトを作ってみる
　これ、ここが一番重要ですよね（笑）でもここはすみませんが私も勉強中なのでまた今度！😂　そもそも、このライブラリを使いこなすには PPO という手法に詳しくないとどうにもならない感があるので、先ずはそこから勉強しなければならないなと思います。

　という訳で、乞うご期待という事で今回はここまでにしたいと思います。

　何か間違いなどございましたら、コメント等でお教え頂ければと思います。よろしくお願い致します。