🐉

argparseの代わりにhydra+mlflowを使ってみる (2)

2020/12/29に公開

数理最適化ソルバを使うときに,いくつかパラメータを振りたいときがあります.こういうときにパラメータをargparseで書いていると

  • 可読性が低いので死んでしまう
  • そもそも実行に使ったパラメータがどれかわからなくなった
  • 出力がどこにいったかよく分からない

といった様々な面倒なことがあります.そこでhydra+mlflowを使えるかどうかを考えてみます.

準備

  • 前回と同様にhydraでパラメータを渡しつつ実験を行います
  • OR-Toolsの例から取ってきたプログラムは微調整し,各ユーザの経路と積載量のリストを返すように修正しています
  • loggerで出力するようにしました
  • mlflowのsessionを開始して,そこに実験を記録しました

mlflow関係のメモ

実装部分

細かい使い方はよく分からないので,とりあえずはほとんど基本通りの (というかtutorialとかexapmleからパチってきた) スクリプトです.

experiment_with_mlflow.py
import os
import os.path
import logging
import hydra
import mlflow
from solver import main
from omegaconf import OmegaConf
from mlflow import log_metric, log_param, log_artifacts

log = logging.getLogger(__name__)

@hydra.main(config_name="conf/default.yaml")
def run_experiment(cfg):
    with mlflow.start_run():
        # パラメータの保存
        log_param("penalty", cfg.solver.penalty)
        log_param("num_vehicle", cfg.solver.num_vehicle)

        # ソルバで解を求める
        res, ds, td, tl = main(cfg.solver.penalty, cfg.solver.num_vehicle)

        # metricの保存
        log_metric("total distance", td)
        log_metric("total load", tl)
        log_metric("number of dropped nodes", len(ds))

        # Hydraのlogging
        log.info("Dropped nodes :{}".format(",".join(map(str, ds))))
        log.info("Total distance:{}".format(td))
        log.info("Total load    :{}".format(tl))
        lines = []
        for uid in res:
            route, load = res[uid]
            route_str = "->".join(map(str, route))
            load_str = "->".join(map(str, load))
            lines.append("{}\t{}".format(route_str, load_str))
            log.info("user id:{}".format(uid))
            log.info("  route:{}".format(route_str))
            log.info("   load:{}".format(load_str))

        # 各ユーザの経路と積込(?)量をファイルとartifactsに出力 => 動かない…
        raw_path = os.path.join(os.getcwd(), "rawlog.txt")
        with open(raw_path, "w") as f:
            for line in lines:
                f.write("{}\n".format(line))

if __name__ == '__main__':
    run_experiment()

実行後のディレクトリ構造

実験を実行すると,次のようなディレクトリ構造になりました.

.
├── conf
│   └── default.yaml
├── experiment.py
├── experiment_with_mlflow.py
├── outputs
│   └── 2020-12-29
│       └── 14-51-35
│           ├── experiment_with_mlflow.log
│           ├── mlruns
│           │   └── 0 ()
│           │       └── meta.yaml
│           └── rawlog.txt
├── __pycache__
│   └── solver.cpython-36.pyc
├── README.md
└── solver.py

簡単なmlflow uiの利用例

mlflow uiというコマンドで見てくれるディレクトリは,mlruns以下らしいので,そちらに移動してからmlflow uiを動かしてみます (これのやり方はよく分からなかったです).

$ cd outputs/2020-12-29/14-51-35
$ mlflow ui

次のようなブラウザ画面にアクセスできます (via localhost:5000).

個別の実験を見ていると,正しくlogが描かれているように見えます.

mlflow uiでhydraのmultirunの結果を確認する例

今回は2つのパラメータ (penaltyとnum_vehicle) について,直積の組合せで実験をやってみようと思います.コマンドとしては以下の通りです.

$ python experiment_with_mlflow.py -m solver.penalty=500,750,1000 solver.num_vehicle=1,2,3,4

実行後のディレクトリ構造です.mutlirunを実行すると,特に設定のない状態ではmultirunディレクトリに結果が格納されているようです.その下に0から11までのディレクトリがありますね.これが各パラメータ設定に対応していそうです.

..
├── conf
│   └── default.yaml
├── experiment.py
├── experiment_with_mlflow.py
├── multirun
│   └── 2020-12-29
│       └── 15-03-32
│           ├── 0
│           ├── 1
│           ├── 10
│           ├── 11
│           ├── 2
│           ├── 3
│           ├── 4
│           ├── 5
│           ├── 6
│           ├── 7
│           ├── 8
│           ├── 9
│           └── multirun.yaml
├── outputs ()
├── README.md
└── solver.py

ここでmlflow uiを実行してみたいのですが,うまく描画されます.上にも書いたとおり特に設定していない状態ではmlrunsというディレクトリを見に行く様子ですが,この場合multirunした結果が0から11にばらまかれているので,これを回収してこないとブラウザで手抜き一言管理がうまく行かない様子です.

これの対応をするにはmlflowの使い方を真面目にやる必要がありそうですね (上の参考にした記事参照).というわけで,次回に続きます….上の参考にしたサイトに書いてある手法ではうまくまとまってハイパラ管理できなかったです (現状).

Discussion