argparseからhydraへの移植

Pythonのargparseの設定をhydraに変換する際に，調べたことを備忘録的にまとめたものです．
最後にhydraにしかない便利機能を書いています．


## はじめに
機械学習の実験スクリプトをpythonで書く際，ハイパーパラメータなどの設定方法として，[argparse](https://docs.python.org/3/library/argparse.html)を使っていました．
しかし，実験が複雑になるにつれて，argparseの設定を記述したファイルだけで，かなりの行数になってしまったり，設定を構造化したいなどの欲が出てきました．．．
そこで，[ymymさんの「ハイパラ管理のすすめ -ハイパーパラメータをHydra+MLflowで管理しよう-」](https://ymym3412.hatenablog.com/entry/2020/02/09/034644)を読み，「これだ！」となったので，使ってみることにしました．
そして，argparseで書いていた実験の設定をhydraに移行する際にいろいろ調べたので，それをまとめてみました．


## hydraとは？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224401/761ca00b-7a70-c858-9ee5-1060004b96c4.png)

"A framework for elegantly configuring complex applications"
"複雑なアプリケーションの設定をエレガントに管理するためのフレームワーク"らしいです．
facebook researchを中心に開発が行われています． 

[GitHub](https://github.com/facebookresearch/hydra) / [Official Document](https://hydra.cc/docs/intro)


## hydraの使い方
詳しい設定については，上のymymさんの記事と，[公式 Tutorial](https://hydra.cc/docs/intro)を参照いただければと思います．
1番の特徴は，yaml fileに設定を構造的に記述して，その内容を上書きしたければ，実行時にコマンドラインで上書きをするという感じです．

## 各種書き方
### オプションの定義と読み込み
- hydraはyamlファイルに設定を記述して，それを実行する関数にデコレータとして付与する形になります．

argparse

```python:main.py
def main():
    parser = parser.ArgumentParser(...)
    parser.add_argument('--hoge', type=int, default=1)
    cfg = parser.parse_args()
    print(cfg.hoge) # 1
```

hydra

```yaml:config.yaml
hoge: 1
```

```python:main.py
@hydra.main(config_path='config.yaml')
def main(cfg):
    print(cfg.hoge) # 1
```

### defaultを上書き
- `=`で指定する
- keyとvalueの間にスペースは開けない

argparse

```shell:shell
$ python main.py --hoge 2
```

hydra

```shell:shell
$ python main.py hoge=1
```

### nargs
- hydraからparseされてくる設定は，[omegaconf](https://github.com/omry/omegaconf)で全て記述されているため，単なる`list`ではないことに注意が必要です．

argparse

```python:main.py
parser.add_argument('--hoge', type=int, nargs=3, default=[1, 2, 3])
```
```shell:shell
$ python main.py --hoge 4 5 6
```

hydra

```yaml:config.yaml
hoge:
  - 1
  - 2
  - 3
```
```shell:shell
$ python main.py hoge=[1,2,3]
```


### required=True
- valueに`???`を指定する
- 指定しないで実行すると，OmegaConfの`omegaconf.errors.MissingMandatoryValue`というエラーが出る．
- しかし，これは実行時に評価されるわけではなく，keyにアクセスしたタイミングで評価されてエラーが出ます．そのため，指定しないで実行しても，`???`で指定してkeyにアクセスする行までのコードは実行されてしまいます．（hydra側の設定で実行時に評価させる方法などがあったら教えていただきたいです）

argparse

```python:main.py
parser.add_argument('--hoge', type=int, required=True)
```

hydra

```yaml:config.yaml
hoge: ???
```


### choices
- 特定のkeyに対して，許可する値のリストを指定する，という機能は今のところなさそうです． (将来的に機能追加されるかどうかも怪しいと思います．)
- 近い機能は，configを構造化することによって，達成できます．
    - 詳しくは[ここら辺](https://hydra.cc/docs/tutorial/config_groups)を参照するのが良いと思います．

argparse

```python:main.py
parser.add_argument('--hoge', type=int, default=1, choices=[1, 2])
print(cfg.hoge)
```
```shell:shell
$ python main.py --hoge 2
# 2
```

hydra

```shell:tree
├── config
|   ├── config.yaml
│   └── choice
│       ├── a.yaml
│       └── b.yaml
└── main.py
```
```yaml:config.yaml
choice: a
```
```yaml:choice/a.yaml
hoge: 1
```
```yaml:choice/b.yaml
hoge: 2
```
```python:main.py
print(cfg.hoge)
```
```shell:shell
$ python main.py choice=b
# 2
```


### --help

argparse

```shell:shell
$ python main.py --help
```

hydra

```shell:shell
$ python main.py --cfg job
```


## hydraにしかない便利機能
ほとんどtutorialの紹介になってしまうので，section名に対象のtutorialへのリンクを埋め込みます．
詳細はそちらをご参照ください．

### [Multi-run](https://hydra.cc/docs/tutorial/multi-run)
上のchoicesのところで設定したconfigで，複数の設定での同時実行をしてみます．

```shell:shell
$ python main.py hoge=a,b
```


### [タブ補完](https://hydra.cc/docs/tutorial/tab_completion)
スクリプト実行前に，一度以下のコマンドを実行すると，コマンドラインからオプションを指定する際に，タブ補完を利用できます．

```shell:shell
$ eval "$(python main.py -sc install=bash)"
```


### [ログディレクトリの作成](https://hydra.cc/docs/tutorial/working_directory)
hydraは何も設定をしなくても，自動で日付と時間をもとに構造化されたログのディレクトリを作成し，そこを実行時のワーキングディレクトリとして，実行時に生成されたファイルや保存されたファイル等を保存します．

```python:main.py
import os

@hydra.main()
def main(_cfg):
    print("Working directory : {}".format(os.getcwd()))
```
```shell:shell
$ python main.py
Working directory : /home/omry/dev/hydra/outputs/2019-09-25/15-16-17

$ python main.py
Working directory : /home/omry/dev/hydra/outputs/2019-09-25/15-16-19
```

しかし，勝手にワーキングディレクトリ指定するなや！っていう人もいると思うので（自分），そういう人は，[自らhydraにディレクトリを指定](https://hydra.cc/docs/configure_hydra/workdir)するようにしましょう．


## まとめ
僕自身使い始めたばかりなので，記事中で間違っている箇所や，もっと良い方法を知っている方などいれば，ご教授いただけますと幸いです．
hydraを使うと，コードがスッキリするので，是非使ってみてください！（特に大規模なコードほど）

最後に，詳しい使い方を知りたい方は，[公式document](https://hydra.cc/docs/intro)と[ymymさんブログ](https://ymym3412.hatenablog.com/entry/2020/02/09/034644)をご参照ください．