Hydraを使う

# Hydraを使う

Hydraといってもパスワード解除の方ではなく
Metaがリリースしてるコマンドのパラメータ設定なんかを便利にしてくれる方のやつです。

argparseでコマンドラインツールに引数を処理する場合が多いと思いますが、引数が多くなると管理するのがなかなかつらいってことがよく起きるわけです。
前にこのコマンド実行したときって何を引数に渡したっけ？なんてこともありますよね

* 階層的なコンフィギュレーション
* コマンドラインからコンフィグを上書きできる
* コマンドラインで補完してくれる
* アプリケーションをローカルで実行したり、リモートで実行できる
* 1つのコマンドで複数のジョブを異なる引数で実行できる。

インストールは以下を実行

```sh
pip  install hydra-core --upgrade
```

これで準備はOKなんで、まずはチュートリアルを見てみましょうか

## シンプルコマンドラインアプリケーション

コマンドラインで引数を渡してその内容をyamlに変換してプリントするという内容のアプリケーションです。

```python: my_app.py
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None)
def my_app(cfg: DictConfig) -> None:
    print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
    my_app()
```

これを実行すると、引数に渡した値がYamlに変換されてプリントされます。

```shell
$ python my_app.py +db.driver=mysql +db.user=omry +db.password=secret
db:
  driver: mysql
  user: omry
  password: secret
```





Hydraは実行するとoutputsというフォルダを自動で作成してコマンドを実行したときのログを出力してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7739/969573cc-2ab3-db79-b3e2-840be9022acd.png)



## 設定ファイルを指定する
今度はyamlで設定ファイルを用意しておき
デコレータで設定ファイルを指定します。

Yamlファイルを作成して以下の内容で保存します。
保存先はPythonスクリプトと同じフォルダ内に。

```yaml: config.yaml
db: 
  driver: mysql
  user: omry
  password: secret
```

Pythonスクリプトを用意します。
今度はデコレータにconfig_pathとconfig_nameを指定します。

```python: my_app.py
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None, config_path=".", config_name="config")
def my_app(cfg):
    print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
    my_app()
```
これでconfig.yamlが自動的に読み込まれるようになります。

```shell: 実行結果
$D:\hydra\config_file> python my_app.py
db:
  driver: mysql
  user: omry
  password: secret
```

今度はさらにコマンドラインに引数を追加して設定値を上書きしてみましょう

```: 実行結果
$D:\hydra\config_file> python my_app.py db.driver=postsql
db:
  driver: postsql
  user: omry
  password: secret
```

dirver値がmysqlからpostsqlちゃんと値が変わっていますね。

設定ファイルに無い項目を引数に渡すとどうなるかというとパースエラーになります。

```
D:\hydra\config_file> python my_app.py db.secret
Error parsing override 'db.secret'
missing EQUAL at '<EOF>'
See https://hydra.cc/docs/1.2/advanced/override_grammar/basic for details

Set the environment variable HYDRA_FULL_ERROR=1 for a complete stack trace.
```
設定ファイルに無い項目を渡したい場合は引数にプレフィクスとして+を付けます

```shell:実行結果
$d:\hydra\config_file> python my_app.py +db.secret=aaa
db:
  driver: mysql
  user: omry
  password: secret
  secret: aaa
```
項目secretが追加されました。

今度は設定項目がある値に対して+を付けて引数を渡すとこれもエラーとなります。

```shell
$d:\hydra\config_file> python my_app.py +db.user=hoge
Could not append to config. An item is already at 'db.user'.
Either remove + prefix: 'db.user=hoge'
Or add a second + to add or override 'db.user': '++db.user=hoge'
```

意図しない上書きを防ぐという意味でいい設計だと思います。

設定ファイルに無ければ追加、有れば上書きしたいという場合もあると思います。
そういう時はプレフィクスとして++を付けて引数を渡します。

```shell:実行結果
$d:\hydra\config_file> python my_app.py ++db.user=hoge
db:
  driver: mysql
  user: hoge
  password: secret
```


## configオブジェクトを扱う

Hydraは実行時に自動的にconfigオブジェクトを作成します。configオブジェクトはomegaconfのDictConfigクラスのインスタンスです。

```yaml: config.yaml
node:                         # Config is hierarchical
  loompa: 10                  # Simple value
  zippity: ${node.loompa}     # Value interpolation
  do: "oompa ${node.loompa}"  # String interpolation
  waldo: ???                  # Missing value, must be populated prior to access
```

```python: main.py
from omegaconf import DictConfig, OmegaConf
import hydra

@hydra.main(version_base=None, config_path=".", config_name="config")
def my_app(cfg: DictConfig):
    assert cfg.node.loompa == 10          # attribute style access
    assert cfg["node"]["loompa"] == 10    # dictionary style access

    assert cfg.node.zippity == 10         # Value interpolation
    assert isinstance(cfg.node.zippity, int)  # Value interpolation type
    assert cfg.node.do == "oompa 10"      # string interpolation

    cfg.node.waldo                        # raises an exception

if __name__ == "__main__":
    my_app()
```

``` : 実行結果
$ python my_app.py 
Traceback (most recent call last):
  File "my_app.py", line 32, in my_app
    cfg.node.waldo
omegaconf.errors.MissingMandatoryValue: Missing mandatory value: node.waldo
    full_key: node.waldo
    object_type=dict
```