iMind Developers Blog

iMind開発者ブログ

MLFlowを使ってみる2 - projects

概要

MLFlowの機能をざっと試す第二弾。前回はtrackingを扱ったので今回はprojects。

projectsはdockerやcondaでプロジェクトの管理ができる。本稿ではdockerは扱わずcondaを利用する。

バージョン情報

  • mlflow==1.0.0

experimentの生成

今回の例で利用するexperimentを用意しておく。

MNISTの手書き数字分類を行うので、experiment名をmnist_digitとしてCLIから生成してみる。

$ mlflow experiments create --experiment-name mnist_digit

tensorflowのプロジェクトを作る

tensorflow(CPU版)のプロジェクトをmlflowで作ってみる。

まずは必要な依存関係をyamlで記述する。

# conda.yaml

name: tf_example

channels:
  - defaults
  - anaconda

dependencies:
  - python==3.7
  - numpy==1.16.1
  - pandas
  - tensorflow==1.13.1
  - keras
  - pip:
    - mlflow==1.0.0
    - click

続いてmlflowのプロジェクトファイルを生成する。こちらもyaml。ファイル名はMLprojectとすること。

下記の記述ではプロジェクト名、先ほど書いたcondaの設定のパス、それから実行するコマンドとパラメータを記述している。

# MLproject

name: tensorflow_mnist_digit

# 先ほど書いたconda.yamlのパスを指定
conda_env: conda.yaml

entry_points:
  main:
    parameters: # コマンドラインに渡す引数
      epochs: {type: int, default: 20}
      batch_size: {type: int, default: 32}
    command: # 実行コマンド
      "python train.py --epochs {epochs} --batch-size {batch_size}"

最後にtensorflowで何かしらtrainするコードを用意する。

kerasでMNISTの数字認識を学習させて、引数やaccuracyの情報をmlflowに記録している。

mlflow側でstart_runしていないけどそのあたりはmlflow runをすればよしなにやってくれる。

import click
import mlflow
import mlflow.keras
import tensorflow as tf

@click.command()
@click.option('--epochs', type=int)
@click.option('--batch-size', type=int)
def main(epochs, batch_size):

    # 何かしらtensorflowで学習
    mnist = tf.keras.datasets.mnist
    (x_train, y_train),(x_test, y_test) = mnist.load_data()
    x_train, x_test = x_train / 255.0, x_test / 255.0
    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(input_shape=(28, 28)),
        tf.keras.layers.Dense(512, activation=tf.nn.relu),
        tf.keras.layers.Dropout(0.2),
        tf.keras.layers.Dense(10, activation=tf.nn.softmax)
    ])
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size)
    accuracy = model.evaluate(x_test, y_test)

    # paramとmetricsの保存
    mlflow.log_metrics({'accuracy': accuracy[1]})
    # modelの保存
    mlflow.keras.log_model(model, 'mnist_digit')

if __name__ == "__main__":
    main()

コマンドライン引数としてepochsとbatch-sizeの2つを取るようにしている。

階層としてはこんな風にファイルが格納されているものとする。

tf_example/
├── MLproject
├── conda.yaml
└── train.py

作成したプロジェクトを実行するコマンド。

$ mlflow run --experiment-name mnist_digit -P batch-size=64 tf_example

引数の意味は --experiment-nameでexperimentを指定。実行コマンドに渡す引数は -P param-name=value のような形で指定できる。最後のtf_exampleは作成したプロジェクトが入っているディレクトリのパス。

mlflow uiを起動してtrackingされた結果を表示。

f:id:imind:20190526004134p:plain

train.py側でrun_id, experiment_id等を取得

MLprojectをrunした場合、 mlflow.start_run をしなくても勝手に引数で設定したexperiment_idやtracking_uriが設定されて処理が実行される。

これらの値は環境変数に設定されているのでスクリプトからも参照可能。

env name env nameが入ってる定数
MLFLOW_RUN_ID mlflow.tracking._RUN_ID_ENV_VAR
MLFLOW_EXPERIMENT_ID mlflow.tracking._EXPERIMENT_ID_ENV_VAR
MLFLOW_TRACKING_URI mlflow.tracking._TRACKING_URI_ENV_VAR

MLprojectから呼ばれるコマンドに下記のようなprint文を追加。

print( 'run_id=', os.getenv(mlflow.tracking._RUN_ID_ENV_VAR) )
print( 'experiment_id=', os.getenv(mlflow.tracking._EXPERIMENT_ID_ENV_VAR) )
print( 'tracking_url=', os.getenv(mlflow.tracking._TRACKING_URI_ENV_VAR) )

実行結果

run_id= 7ce8d27eb53b4acc9978e035dc4edbf4
experiment_id= 1
tracking_url= /home/user/work/tf_example/mlruns

entry_pointの指定

上で利用したサンプルでは、entry_pointの名前がmainになっている。

entry_points:
  main: # ←ココ
    command:
      "python train.py

entry_point のデフォルトはmain。それ以外のentry_pointを記述して、実行時にどれを呼び出すか引数 --entry-point で指定することもできる。

下記は前処理と学習処理を分けた例。前処理は1度だけ実行し、その後の学習処理は引数を変えながら回すような場合はentry-pointを分けておくと楽そう。

name: tensorflow_mnist_digit

conda_env: conda.yaml

entry_points:
  pre-proc: #前処理
    parameters: 
      path: {type: string, default: None}
    command:
      "python pre_proc.py"
  main: #メインの学習処理
    parameters: 
      epochs: {type: int, default: 20}
    command:
      "python train.py --epochs {epochs}"

前処理を実行後、mainの処理を引数を変えながら実行する例。

$ mlflow run --entry-point pre-proc -P path=foo tf_example

$ mlflow run -P epochs=30 tf_example
$ mlflow run -P epochs=40 tf_example
$ mlflow run -P epochs=50 tf_example

tracking_uriを指定してmlflow run

上述のコマンドではローカルファイルにtracking関連のデータが生成される。mlflow serverでサーバを立ち上げてそれに対してtrackingしてみる。

適当な場所でmlflow serverを立ち上げる。

$ mlflow server \
        --backend-store-uri sqlite:///mlruns.sqlite3 \
        --default-artifact-root artifact

tracking_uriを指定してexperimentを生成。

$ MLFLOW_TRACKING_URI=http://localhost:5000/ \
        mlflow experiments create --experiment-name mnist_digit

mlflow runを実行。

$ MLFLOW_TRACKING_URI=http://localhost:5000/ \
        MLFLOW_EXPERIMENT_NAME=mnist_digit \
        mlflow run -P epochs=30 tf_example

gitにあるコードに対して実行する

gitにプロジェクトを置いておいて直接実行することもできる。複数のブランチを作ってどのブランチが勝つかといった比較もできる。

例として下記のgithubプロジェクトにdev1とdev2、2つのブランチが切ってありそれぞれ手書き文字認識のコードを実行していみる。

repository https://github.com/imind-inc/mlflow_example
branch dev1, dev2
path mnist_digit/

dev1/dev2、それぞれを実行してみる。下記のようにコマンドを記述する。

$ mlflow run {githubのレポジトリ}#{MLprojectのあるディレクトリのパス} --version {ブランチ名}

実行例

# dev1の実行
$ mlflow run https://github.com/imind-inc/mlflow_example.git#mnist_digit \
        --version dev1

# dev2の実行
$ mlflow run https://github.com/imind-inc/mlflow_example.git#mnist_digit \
        --version dev2

# dev1(epochs=50)の実行
$ mlflow run https://github.com/imind-inc/mlflow_example.git#mnist_digit \
        --version dev1 -P epochs=50

# dev2(epochs=50)の実行
$ mlflow run https://github.com/imind-inc/mlflow_example.git#mnist_digit \
        --version dev2 -P epochs=50

実行後にuiを見ると4つのrunが登録されている。

f:id:imind:20190528124509p:plain

versionはブランチ名ではなくコミットIDで表示される。

347ca5はCNNで実装した方でepochs=50を指定した場合がaccuracy=0.994ともっとも良い結果になっている。

versionのリンクからgithubの当該バージョンに飛べる。

githubのprivateレポジトリの利用

githubのprivateレポジトリを利用している場合、毎回ユーザー名とパスワードの入力が求められる。

下記は mlflow_example_private というprivateレポジトリを利用した場合。ユーザー名とパスワードの入力が要求されている。

$ mlflow run https://github.com/imind-inc/mlflow_example_private.git#mnist_digit

Username for 'https://github.com': mwsoft
Password for 'https://mwsoft@github.com': 

.netrc等を使ってパスワードの省略をしていればこの要求は発生しない。

pythonから実行

CLIでmlflow runする以外にもPython上からも叩ける。

mlflow.projects.run(
    uri='https://github.com/imind-inc/mlflow_example.git#mnist_digit',
    entry_point='main',
    version='dev1',
    parameters={'epochs': 10},
    experiment_name='mnist_digit',
    use_conda=True
)

引数の意味

引数名 意味
uri localやgitのuri
entry_point
version gitのcommit idとかブランチ名とか
parameters -P で渡していた引数
experiment_name
experiment_id experimentはnameかidのいずれかを指定可能
user_conda condaで仮想環境を作って実行するか(default=True)
storage_dir 一時ファイル等を保存するディレクトリ

--no-condaオプション

mlflow runはデフォルトでは個別にcondaのenvを生成するので気がつくとmlflow-{uuid}なenvが溜まっていく。

$ conda env list | cut -f1 -d" "

base
mlflow-23c8fb8fdd7a6f021919868f8cc531b8f3a01939
mlflow-351a306d17b5ac3470475a6e0d1fd1ac75d0eb9c
mlflow-6b07d473e5f9dd40eb717254ee64b1dfdbec4a76

必要なライブラリが既に用意されている環境であれば、 --no-conda を指定して新規にenvを作らないように指定できる。

$ mlflow run https://github.com/imind-inc/mlflow_example.git#mnist_digit \
        --version dev1 \
        --no-conda

メモ。mlflowが作ったenvを滅ぼすコマンド(非推奨)。

for e in `conda env list | grep mlflow- | cut -f1 -d' '`
do
    echo remove $e
    conda env remove -n $e
done

改定履歴

Author: Masato Watanabe, Date: 2019-06-14, 記事投稿