Hydraの簡単な使い方
Hydraは、階層的にパラメータの指定ができて(argparseと比較して可読性が高い)、ログなども自動で出力可能な便利なツールです。何気なく使用していましたが、公式のtutorialを確認すると便利な機能がありそうだったので、tutorialに沿って試してみます。インストールはgithubを参照。
config.yamlの読み込み方
hydraを使うには、config.yamlに必要なパラメータを記述して、スクリプトとして実行する関数に@hydra.main(config_name=’config’)のデコレータを記述する必要があります。
└─my_app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
""""""""""""""" config.yamlの中身 """"""""""""""" db: driver: mysql user: omry password: secret """"""""""""""" my_app.pyの中身 """"""""""""""" from omegaconf import DictConfig, OmegaConf import hydra @hydra.main(config_name="config") def my_app(cfg: DictConfig) -> None: print(OmegaConf.to_yaml(cfg)) if __name__ == "__main__": my_app() |
デコレータの引数ではyaml拡張子は不要でmy_appを実行すると、同じ階層にあるconfig.yamlが読み込まれて値がcfgに格納されます。cfgにはpythonの辞書型と同じようにcfg.db.user, cfg[‘db’][‘user’]でアクセスできます。
文字列など色々なパラメータの設定
config.yamlは数字、文字列、文字列補完が使えますが、ログでは文字列が補完された状態では出力されないようです。
1 2 3 4 5 6 7 8 |
""""""""""""""" 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 acces |
???を設定したパラメータには、アクセスする時に値が上書きされていない場合はエラーが出ます。
別々のconfig.yamlを読み込む方法
パラメータを別yamlファイルで管理して、引数で特定のyamlファイルを読み込めます。
└─my_app.py
confディレクトリを作成して、この直下にconfig.yamlを置き、実行時に選択が必要なパラメータを管理するディレクトリーを作成して上のようにyamlファイル置きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
""""""""""""""" config.yamlの中身 """"""""""""""" defaults: - model: model_1 """"""""""""""" model_1.yamlの中身 """"""""""""""" # @package _group_ name : GBDT parameter_1 : 1 parameter_2 : 2 parameter_3 : 3 """"""""""""""" model_2.yamlの中身 """"""""""""""" # @package _group_ name : NN parameter_1 : 4 parameter_2 : 5 parameter_3 : 6 """"""""""""""" my_app.pyの中身 """"""""""""""" from omegaconf import DictConfig, OmegaConf import hydra @hydra.main(config_name="config", config_path="conf") def my_app(cfg: DictConfig): print(OmegaConf.to_yaml(cfg)) # raises an exception if __name__ == "__main__": my_app() |
各ファイルの中身を表示しています。読み込まれるmodel_1.yamlとmodel_2.yamlの先頭には 「# @package _group_ 」の記述が必要です。また、デコレータの引数でconfig_name の他に、config_path(confディレクトリー)を指定する必要があります。
config.yamlにはdafaultで使用するyamlファイルをリスト形式で指定しておくことで、引数を指定しない時の実行条件が設定できます。(デフォルトで指定がない場合も python hoge.py +model=model_1とすれば指定可能)
[実行例]
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(base) root@edd37023ffb5:/project/practice# python my_app.py model=model_1 model: name: GBDT parameter_1: 1 parameter_2: 2 parameter_3: 3 (base) root@edd37023ffb5:/project/practice# python my_app.py model=model_2 model: name: NN parameter_1: 4 parameter_2: 5 parameter_3: 6 |
上記のように引数で任意のyamlファイルを指定して実行すると、config.yamlのdefaultsの値を上書きできて、指定したファイルの読み込みが可能になります。
└─ my_app.py
cofing.yamlのデフォルトには複数のフォルダを指定できるので、例えば上記のようなフォルダ構成にしてデフォルトから引数で変更することで別々のパラメータでの実験が可能になります。
あるパラメータが他のパラメータに依存する場合
└─ my_app.py
学習データA, BとモデルA,Bがある場合、各組み合わせで実験条件を変えたい場合はconfig.yamlのdefaultsにvariable interpolationの記述方法で指定できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
""""""""""""""""" config.yamlの中身 """"""""""""""""" defaults : - model : model_A - dataset : dataset_A - dataset_model : ${defaults.0.model}_${defaults.1.dataset} optional : True """"""""""""""""" デフォルトコマンドと結果 """"""""""""""""" (base) root@edd37023ffb5:/project/practice# python my_app.py model: name: read_model_A dataset: dataset: read_dataset_A dataset_model: name: read_model_A_dataset_A """"""""""""""""" 上書きする場合のコマンドと結果 """"""""""""""""" (base) root@edd37023ffb5:/project/practice# python my_app.py model=model_B model: name: read_model_B dataset: dataset: read_dataset_A dataset_model: name: read_model_B_dataset_A |
引数として与えるモデルを変えると、自動で読み込まれるファイルが変更していることがわかります。また、モデルCなど組み合わせのconfigファイルがない引数を与えてしまうと、通常ならエラーがでてしまいますが、optional:Trueを与えることで、組み合わせの読み込みは無視して実行してくれます。
Multi Run機能(sweep)
例えば複数の条件で実験を回したい時は、multi runが便利です。-m または-multirunとスペース無しのカンマで条件を与えることで、直列に実験が行われます。下記はmodel_1とmodel_2を実験するコマンドです。
[実行例]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
(base) root@edd37023ffb5:/project/practice# python my_app.py -m model=model_1,model_2 [2021-05-25 09:24:38,775][HYDRA] Launching 2 jobs locally [2021-05-25 09:24:38,775][HYDRA] #0 : model=model_1 model: name: GBDT parameter_1: 1 parameter_2: 2 parameter_3: 3 process: hoge_exp1_param_1: 1 hoge_exp1_param_2: 2 hoge_exp1_param_3: 3 hoge_exp1_param_4: 4 [2021-05-25 09:24:38,985][HYDRA] #1 : model=model_2 model: name: NN parameter_1: 4 parameter_2: 5 parameter_3: 6 process: hoge_exp1_param_1: 1 hoge_exp1_param_2: 2 hoge_exp1_param_3: 3 hoge_exp1_param_4: 4 |
数字なども指定できて、range関数で与えることもできるので、seedなど探索範囲が広い場合に便利です。下記は引数としてseed=”range(1,5)”を与えています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(base) root@edd37023ffb5:/project/practice# python my_app.py -m seed="range(1,5)" [2021-05-25 09:27:46,650][HYDRA] Launching 4 jobs locally [2021-05-25 09:27:46,650][HYDRA] #0 : seed=1 seed: 1 [2021-05-25 09:27:46,833][HYDRA] #1 : seed=2 seed: 2 [2021-05-25 09:27:47,007][HYDRA] #2 : seed=3 seed: 3 [2021-05-25 09:27:47,181][HYDRA] #3 : seed=4 seed: 4 |
その他にもランダムで引数を指定する方法など色々とあるようですが、あまり活用方法が思い浮かばなかったので割愛します。
outputディレクトリの指定
hydraを使用した場合、実行毎に自動でoutputディレクトリーが作成されます。
└─ my_app.log
config.yamlに実行時に使用したパラメータの情報がコピーされるので、こちらを読み込めば結果を再現できるので便利です。また、実行関数(my_app)の名前でログファイルが作成されており、logger.info(“hoge”)などのようにログのレベルを指定することで出力フォーマットなどの設定をしなくても、ログを取ることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
""""""""""""""" config.yamlの中身 """"""""""""""" hydra: job_logging : formatters: simple: format: '%(message)s' """"""""""""""" my_app.py の中身 """"""""""""""" import logging from omegaconf import DictConfig import hydra # A logger for this file log = logging.getLogger(__name__) @hydra.main(config_name="config") def my_app(_cfg: DictConfig) -> None: log.info("Info level message") if __name__ == "__main__": my_app() """"""""""""""" output & log出力結果 """"""""""""""" Info level message |
config.yamlなどの保存が不要な場合は実行時のconfigファイルにhydra.output_subdirにnullを指定すると保存されなくなります。
デフォルトではworking directoryがoutput/日付日時/になってしまうので、データの読み込みなどを行う時のpathの指定を実行関数からの相対pathで指定したい場合など、注意が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import os from omegaconf import DictConfig import hydra @hydra.main() def my_app(_cfg: DictConfig) -> None: print(f"Current working directory : {os.getcwd()}") print(f"Orig working directory : {hydra.utils.get_original_cwd()}") print(f"to_absolute_path('foo') : {hydra.utils.to_absolute_path('foo')}") print(f"to_absolute_path('/foo') : {hydra.utils.to_absolute_path('/foo')}") if __name__ == "__main__": my_app() """"""""""""""""""""" python /project/practice/my_app.py を実行した時のpath """"""""""""""""""""" Current working directory : /project/practice/outputs/2021-05-25/12-05-47 Original working directory : /project/practice to_absolute_path('foo') : /project/practice/foo to_absolute_path('/foo') : /foo |
一般的に?実行関数からの相対pathで読み込みを行うと思うので、その場合は、hydra.utils.get_original_cwd() + “../input/~”とすると良いかと思います。
タブ補完
同じ階層に複数のパラメータがある場合などに便利です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
""""""""""""""""""""" 下記でインストール可能 """"""""""""""""""""" root@edd37023ffb5:/project/practice# eval "$(python my_app.py -sc install=bash)" """"""""""""""""""""" config.yamlの中身 """"""""""""""""""""" parameter : parameter_1 : 1 parameter_2 : 2 parameter_3 : 3 parameter_4 : 4 """"""""""""""""""""" tab補完 """"""""""""""""""""" root@edd37023ffb5:/project/practice# python my_app.py parameter.parameter_ parameter.parameter_1= parameter.parameter_2= parameter.parameter_3= parameter.parameter_4= |
インストールしておいて損はなさそうですね。
まとめ
機械学習で活用できそうな機能を中心にまとめましたが、他にも色々と機能がありそうです。また、開発が結構進んでそうなので、新しい機能が追加されたらこちらの記事に追記していきます。