HandyRLの使い方

Qiita 初投稿です。研究で HandyRL を使うことになったので、使い方などを備忘録としてまとめようと思います。[HandyRL](https://github.com/DeNA/HandyRL) を使ってみたいけどやり方が分からない人の参考になればと思います。
![HandyRL](https://github.com/DeNA/HandyRL/blob/master/docs/img/logo.png?raw=true)
# 目次
[HandyRLとは](HandyRLとは)
[使い方](使い方)
[- 準備](準備)
[- 実行](実行)
[応用](応用)
[- 2台の計算機を使って並列学習をする](2台の計算機を使って並列学習をする)
[- 学習の勝率の可視化](学習の勝率の可視化)
[そのほか](そのほか)
[- パラメータの詳細](パラメータの詳細)
[- 参考文献](参考文献)
# HandyRLとは
HandyRL は DeNA が開発した最新の並列強化学習のフレームワークです。 この HandyRL は 「誰でも強化学習を手軽に扱える」「対戦ゲームにおいて強くて勝てるAIが作れる」「並列度を上げて学習がスケールする」ことの３つを目指して開発されたらしいです（[参考元](https://engineering.dena.com/blog/2021/12/distributed-reinforcement-learning-with-handyrl/)）。実際に kaggle で行われた Hungry Geese コンペティション（ヘビのゲーム）で優勝もしている強くて勝てるAIです。本当に手軽に動かすことができるので、強化学習に興味がある方はぜひ動かしてみてください。また実装するのが難しい並列強化学習を簡単に使うことができ、高速で学習することができるようになっています。
# 使い方
内容はほぼ GitHub の README 通りですが、ちょっと補足とか自分が詰まった点とかを書いときます。
## 書いてる人の動作環境
* MacBook Pro(2017)
* macOS Monterey バージョン:12.6.1（ Ubuntu16.04.7 でも動かせた）
* Visual Studio Code バージョン:1.73.1 
* anaconda バージョン:4.13.0

## 必要なもの
* **Python バージョン:3.7 以上**

## 準備
### インストール
HandyRLのリポジトリを自分の手元の環境に落とします。
#### GitHubからクローンする場合
以下を実行でGitHubからクローンを作成
~~~~:ターミナル
git clone https://github.com/DeNA/HandyRL.git
~~~~
#### GitHubから直接ダウンロードする場合
1\. [HandyRL](https://github.com/DeNA/HandyRL)のGitHubページにアクセス
2\. ページ内の`< > Code ▼` (緑色のボタン)をクリック
3\. `Download Zip`から自分の手元の環境にzipファイルをダウンロード
4\. お好きなところでzipファイルを解凍

### HandyRLで使うライブラリのインストール
HandyRLのディレクトリに移動
~~~:ターミナル
cd HandyRL
~~~
追加のライブラリ (numpy、pytorch など) をインストール
~~~:ターミナル
pip3 install -r requirements.txt
~~~
:::note warn
Dockerや仮想環境上で実行（pythonの環境構築が必要）
:::
kaggleの環境（Hungry Geeseなど）を使う場合には以下も追加でインストール
~~~:ターミナル
pip3 install -r handyrl/envs/kaggle/requirements.txt
~~~
## 実行
### パラメータを設定する
`config.yaml`でパラメータの設定をすることができます。デフォルトの環境は **Tic-Tac-Toe（三目並べ）** になっていて、他には **geister** や kaggleのコンペであった **Hungry Geese**, **Parallel Tic-Tac-Toe** を選択することができます。また自分で作成した環境を利用して学習させることも可能となっています。私自身研究用の環境を作成して追加したことがあります。興味がある方はぜひ別記事（後日公開予定）を読んでみてください。


~~~ruby:config.yaml
env_args:
    env: 'TicTacToe' #環境指定

train_args:
    ...
    batch_size: 64 #バッチサイズ
    ...
~~~
上記のコードの場合は環境を Tic-Tac-Toe に, バッチサイズを64に設定しています。
### 学習させる
パラメータ設定後、以下のコマンドで学習させることができます。学習済みのモデルは`models`内に保存されます。
~~~:ターミナル
python main.py --train
~~~
:::note warn
`config.yaml`のエポック数を決めるパラメータが初期状態だと`epochs：-1`になっていて学習が止まらないので注意
:::
### 学習させたモデルを評価する
以下のコマンドで任意のモデルを評価することができます。下の場合は評価のプロセス数を４つに指定し、100ゲーム行ったときの`models/1.pth`のモデルを評価してます。
~~~:ターミナル
python main.py --eval models/1.pth 100 4
~~~
# 応用
## ２台の計算機を使って並列学習をする
計算機の性能次第ですが、学習速度がめちゃめちゃ早くなります。
うまく使えば AWS とか GoogleColab とかでも動かせるかも？（やり方わからないので有識者いたらコメントください）
### パラメータの設定
`config.yaml`の以下の部分を設定します
~~~ruby:config.yaml
woker_args:
    server_address: '' # サーバーのIPアドレス
    num_parallel: 8 # ワーカーの数
~~~
:::note warn
おそらく外部からアクセスできるアドレスじゃないと接続できないので注意
:::
### 学習させる
以下を親サーバ側（server_addressに設定したIPアドレスの方）で実行します
~~~:ターミナル
python main.py --train-server
~~~
以下を子サーバ側で実行します
~~~:ターミナル
python main.py --worker
~~~
:::note info
いろいろな実行方法
python main.py *** （***の部分は短縮可能）
* 通常の学習：`--train` or `-t`
* 学習サーバを起動：`--train-server` or `-ts`
* ワーカーを起動：`--worker` or `-w`
* モデルの評価：`--eval` or `-e`
:::
### 学習させたモデルを評価する
通常時と同様
~~~:ターミナル
python main.py --eval models/1.pth 100 4
~~~
## 学習の勝率の可視化
`scripts/win_rate_plot.py`を使うことで可視化ができます。
学習時の出力を任意の`.txt`に出力する必要があります。
ちなみに公式はあまり推奨していなさそう？なので自己責任でお願いします。(可視化のコードに注意書きがありました）
### ライブラリを追加インストール
~~~:ターミナル
pip3 install -r scripts/requirements.txt
~~~
### 可視化する際の学習方法（ファイル出力）
今回は`tee`コマンドを使って txt ファイルに学習時のログを出力します。
~~~:ターミナル
python main.py --train | tee train-log-000.txt
~~~
:::note info
上記コードの000の部分は任意の値でOK（txtファイル自体の名称も任意で大丈夫だったはず・・・）
:::
### 学習の可視化
~~~:ターミナル
python3 scripts/win_rate_plot.py train-log-000.txt all
~~~
`train-log-000.txt`の部分で可視化したいログファイルを指定し、`all`でグラフのタイトルを指定できます。

:::note warn
ログのパスに注意（上記のコードだとmain.pyと同じ階層にtxtファイルがある）
:::
### 可視化できずにエラーが出た場合
#### エラー内容
~~~:ターミナル
ValueError: x and y must have same first dimension, but have shapes (86,) and (87,)
~~~
#### 原因
ログファイルの終わりの部分が前後している可能性があるので修正します。
~~~:train-log-000.txt（修正前）
epoch 98
win rate = 0.826 (73.5 / 89)
generation stats = 0.000 +- 0.928
loss = p:-0.038 v:0.104 ent:1.491 total:-0.037
updated model(295)
20300 20400 opened worker 0
closed worker 0
opened worker 3
~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~
disconnected
disconnected

epoch 99
win rate = 0.753 (67.0 / 89)
generation stats = 0.000 +- 0.939
loss = p:-0.041 v:0.114 ent:1.505 total:-0.031
updated model(298)
disconnected
finished server
~~~
上記の通り `epoch 98` と `epoch 99`の間に`opend woker ~`などが入っています（**上は`epochs:100`で実行した場合です**）。通常の場合は、`epoch 99`の後に表示されるのが変な位置で表示されているためエラーが出ているっぽいです。
#### 対処法
力技になりますがログファイルをちょっと書き換えます。
~~~:train-log-000.txt（修正後）
epoch 98
win rate = 0.826 (73.5 / 89)
generation stats = 0.000 +- 0.928
loss = p:-0.038 v:0.104 ent:1.491 total:-0.037
updated model(295)
20300 20400
epoch 99
win rate = 0.753 (67.0 / 89)
generation stats = 0.000 +- 0.939
loss = p:-0.041 v:0.114 ent:1.505 total:-0.031
updated model(298)
disconnected
finished server
~~~
`open woker ~`などをバッサリ削除して`epoch 98`と`epoch 99`を繋げました。これで可視化できるはず・・・。
ちなみに`open woker ~`部分は可視化に影響しないので消して大丈夫です。
# そのほか
## パラメータの詳細
ここでは`config.yaml`内のパラメータについて記載します。
間違っていたらご指摘いただけると幸いです。
### 環境のパラメータ設定（env_args）
* **`env`（string型）**
    * 学習させる環境名
    * TicTacToe, Geister, ParallelTicTacToe, HungryGeese
    * 自身で作成した環境を使用する場合は`handyrl/envs/環境名.py`に自作環境を置く必要がある
        * `environment.py`の`ENV`にも追加する必要があるかも？
### 学習のパラメータ設定（train_args）
ここのパラメータは学習時に使われます。（`python main.py --train` or `python main.py --train-server`）
* **`turn_based_training`（bool型）**
    * 環境がターン制のゲーム（複数のプレイヤーが交互に行うゲーム）かどうかのフラグ
    * `True`：ターン制のゲーム（Tic-Tac-Toe, Geister）
    * `False`：ターン制じゃないゲーム（HungryGeese）
* **`observation`（bool型）**
    * 学習時に対戦相手の機能を使用するかどうか
* **`gamma`（double型）**
    * 0.0 <= `gamma` <= 1.0 の範囲で設定
    * 割引率
* **`forward_steps`（int型）**
    * 学習に何ステップ先までの情報を用いるかの設定
* **`compress_steps`（int型）**
    * 効率的にデータを処理するためにエピソードのデータを圧縮する手順？
    * 【**注意点**】システムで使われるパラメータらしいので初期設定でもOK
* **`entropy_regularization` （double型）**
    * 0.0 <= `entropy_regularization`の範囲で設定
    * エントロピー正則化係数
* **`entropy_regularization_decay` （double型）**
    * 0.0 <= `entropy_regularization_decay` <= 1.0 の範囲で設定
    * ステップ進行中のエントロピー正則化の減衰率
    * 【**注意点**】HandyRLの場合は、ターンが進むにつれて減衰するらしい
    * 【**注意点**】値が大きいほど効果が小さく、値が小さいほど効果が大きくなる
* **`update_episodes`（int型）**
    * モデルを更新して保存するエピソードの間隔の設定
    * 設定したタイミングでワーカーのモデルが更新される
* **`batch_size`（int型）**
    * バッチサイズ
* **`minimum_episodes`（int型）**
    * エピソードのデータを保存するための最小のバッファサイズ
    * `minimum_episodes`以上のエピソードのデータが貯まったら学習が開始される
* **`maximum_episodes`（int型）**
    * `minimum_episodes` <= `maximum_episodes`の範囲で設定
    * エピソードのデータを保存する最大のバッファサイズ
    * `maximum_episodes`以上にデータが貯まると古いものから無くなる
* **`epochs`（int型）**
    * トレーニングを停止するエポックの設定
    * 【**注意**】 0未満に設定した場合は無限にトレーニングする
* **`num_batchers`（int型）**
    * マルチプロセスでバッチデータを作成するバッチャーの数
* **`eval_rate`（double型）**
    * 評価に使うワーカーの割合
    * 残りはデータ生成用のワーカー
* **`worker`**
    * **`num_parallel`（int型）**
        * ワーカーの数
* **`lambda`（double型）**
    * 0.0 <= `lambda` <= 1.0 の範囲で設定
    * モンテカルロ法と1ステップTD法の両方で用いられるラムダの値
    * 【**注意**】詳細は[TD（λ）wiki](https://en.wikipedia.org/wiki/Temporal_difference_learning#TD-Lambda)を参照
    * 【**注意**】HandyRLではTD, V-Trace, UPGOのラムダを使用して値を計算する
* **`policy_target`（enum型）**
    * 方策の計算に使うアルゴリズム
    * `MC`：モンテカルロ
    * `TD`：TD(λ)
    * `VTRACE`： [IMPALA の論文の V-Trace](https://arxiv.org/abs/1802.01561)
    * `UPGO`：[AlphaStar の論文の UPGO](https://www.nature.com/articles/s41586-019-1724-z)
* **`value_target`（enum型）**
    * 価値の計算に使うアルゴリズム
    * `MC`：モンテカルロ
    * `TD`：TD(λ)
    * `VTRACE`： [IMPALA の論文の V-Trace](https://arxiv.org/abs/1802.01561)
    * `UPGO`：[AlphaStar の論文の UPGO](https://www.nature.com/articles/s41586-019-1724-z)
* **`seed`（int型）**
    * learnerとworkerのseedの設定
    * 【**注意**】現状は再現性が保証されてない
* **`restart_epoch`(int型)**
    * 学習を再開するエポック数
    * `100`と設定すると`models/100.pth`から学習が再開されて、次のモデル`models/101.pth`から保存されるようになる
### ワーカーのパラメータ設定（worker_args）
ここのパラメータは並列学習するときにのみ使われます。(`python main.py --worker`)
* **`server_address`(string型)**
    * ワーカーから接続されるトレーニングサーバーのアドレス
    * 【**注意**】クラウドサービス（GCP,AWSなど）でモデルを学習する場合、仮想マシンの内部/外部のIPをここで設定できるらしい
* **`num_parallel`（int型）**
    * ワーカーの数

## 参考文献
https://github.com/DeNA/HandyRL

https://engineering.dena.com/blog/2021/12/distributed-reinforcement-learning-with-handyrl/

https://github.com/DeNA/HandyRL/blob/master/docs/parameters.md

https://en.wikipedia.org/wiki/Temporal_difference_learning#TD-Lambda

https://arxiv.org/abs/1802.01561

https://www.nature.com/articles/s41586-019-1724-z