えんじにあのじゆうちょう

勉強したことを中心にアウトプットしていきます。

MLflowのTracking機能を試してみた

はじめに

今回はMLflowを使って機械学習の実験管理について少し試したのでまとめてみました。
本当はこのエントリが新年一発目になる予定だったのですが、諸事情あって別のエントリになりました

環境の用意

MLflow

MLflowは機械学習で必要なプロセスのうち、「実験の管理」「プロジェクト管理」「モデルのサービング」を行うことができるソフトウェアです。
mlflow.org


私自身mlflowは全く触ったことがなかったものの、いくつかのブログを読んでいて興味を持ったので試してみようと思います。
今回は「実験の管理」について書こうと思います。

作る環境

今回は検証なのでMacbook上で実施します。
リモートサーバとして使うところまで試したかったので、以下のような構成にします。

f:id:marufeuillex:20200103130755p:plain

MLflowを動かすだけならMLflowとテスト用のjupyter*1があればよいのですが、model repositoryを使うためにはbackend-storeにローカルストレージ以外を使う必要がある*2ため、簡単のためsqlite3としています。
また、artifactの格納先はクラウドのオブジェクトストレージ等必要ですが、コストの兼ね合いもあるのでS3互換オブジェクトストレージである、minio*3を利用します。

ネットワークの作成 (2019/1/7追記: これ以下のコマンドもこれに伴い少し変えています)

dockerコンテナ間の通信は昔はlinkオプションなどありましたが、現在では非推奨となっているようです。
当初めんどくさかったのでこの記事ではコンテナのIPを調べて接続していましたが、よりスマートにコンテナ名で名前解決する方法をQiitaの記事*4にて発見しましたので、その方法にアップデートします。

dockerのコンテナ間を名前解決させるにはブリッジネットワークを作り接続すればOKとのことなので、まずはネットワークを作成しておきます。
ここではmlflow_networkという名前にします。

docker network create mlflow_network

minioコンテナの作成

公式のDockerイメージ*5があるので、これを利用します。

docker container run -d --name minio -p 9000:9000 --net mlflow_network minio/minio server /data

http://localhost:9000/にアクセスすると、WebUIが見られます。
今回はデフォルトのままセットアップを行ったので、ID: minioadmin, pass: minioadminです。
ちゃんと使うときは注意してください。

f:id:marufeuillex:20200103124103p:plain

こんな感じです。
右下の「+」ボタンからバケットが作れるので、一つ作っておきます。
今回は「mlflow」としました。

MLflowコンテナの作成

まずMLflowを立ち上げます。
先に断っておきますが、今回は可搬性のあるコンテナを作ることを目標としていなくて、まずはMLfowの動作テストのみなので、コンテナに入ってセットアップする方向にします。

docker run -itd --name mflow -p 5000:5000  --env MLFLOW_S3_ENDPOINT_URL=http://minio:9000 --env AWS_ACCESS_KEY_ID=minioadmin --env AWS_SECRET_ACCESS_KEY=minioadmin --net mlflow_network continuumio/anaconda3
docker exec -it mflow /bin/bash

ここで重要なのは以下3つの環境変数をセットアップすることです。

環境変数名 設定する値
MLFLOW_S3_ENDPOINT_URL minioのエンドポイントURLを指定します。今回の場合http://minio:9000です。
AWS_ACCESS_KEY_ID minioのユーザIDを指定します。今回の場合はminioadminです。
AWS_SECRET_ACCESS_KEY minioのパスワードを指定します。今回の場合はminioadminです。

ここから先はコンテナ内で作業します。

# コンテナ最新化
apt -y update && apt -y upgrade

# mflow, botoのインストール
pip install mlflow boto3

では、MLflowを起動します。注意点として、オプションなしで起動すると127.0.0.1でlistenしてしまうので、0.0.0.0でlistenするようにオプションを渡します。

mlflow server --host 0.0.0.0 --backend-store-uri sqlite:///mlflow.db --default-artifact-root s3://mlflow
...前略...
[2020-01-03 03:23:46 +0000] [94] [INFO] Starting gunicorn 20.0.4
[2020-01-03 03:23:46 +0000] [94] [INFO] Listening at: http://0.0.0.0:5000 (94)
[2020-01-03 03:23:46 +0000] [94] [INFO] Using worker: sync
[2020-01-03 03:23:46 +0000] [97] [INFO] Booting worker with pid: 97
[2020-01-03 03:23:46 +0000] [98] [INFO] Booting worker with pid: 98
[2020-01-03 03:23:46 +0000] [99] [INFO] Booting worker with pid: 99
[2020-01-03 03:23:46 +0000] [100] [INFO] Booting worker with pid: 100

こうなれば起動してます。Macbook上のブラウザからhttp://127.0.0.1:5000/へアクセスしてみましょう。

f:id:marufeuillex:20200103125513p:plain

このような画面が出てくればOKです。少し読み込みに時間がかかるかもしれません。

jupyter-notebookコンテナの作成

これは公式のコンテナイメージ*6を使います。
こちらもMLflowのときと同様にminio関連の情報を環境変数として渡す必要があります。

docker run -p 8900:8888 --name jupyter --env MLFLOW_S3_ENDPOINT_URL=http://minio:9000 --env AWS_ACCESS_KEY_ID=minioadmin --env AWS_SECRET_ACCESS_KEY=minioadmin --net mlflow_network jupyter/scipy-notebook

立ち上がると以下のような画面になります。tokenをコピーしておいてください。
ちなみに、本来token情報は公開したらダメですが、今回は実験用ですぐ壊すことと、外部からアクセスする方法がないので載せてしまっています。
十分取り扱いには気をつけてください。

f:id:marufeuillex:20200101112543p:plain

Macbook上のブラウザからhttp://127.0.0.1:8900/へアクセスしましょう。以下のような画面が表示されますので、先程入力したtokenを貼り付けてください。

f:id:marufeuillex:20200101112824p:plain

以下のような画面に遷移すれば成功です!

f:id:marufeuillex:20200101112937p:plain

では続いて、こっちのコンテナにもmlflowをセットアップしていきます。

まずは以下のようにしてターミナルを立ち上げます。
f:id:marufeuillex:20200101113205p:plain

立ち上がったターミナルで以下のように入力し、MLflow(とboto)をインストールします。

pip install mlflow boto3

これで準備は完了です。試していきましょう。

実験

実際にコードを用意する

JupyterNotebook上でコードを書いていきます。
まずはインポートや初期設定です。

import mlflow
mlflow.set_tracking_uri("http://mlflow:5000")

172.17.0.4:5000というのは、前の方で調べたMLflowのIPアドレスです。個人ごとの環境によって書き換えてください。
また、もし同一サーバで動かすような場合はmlflow.set_tracking_uriは不要です。

次に実際の学習コードを用意します。
今回は公式にある以下コードを少し改変して使います。
mlflow.org

import os
import warnings
import sys

import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import ElasticNet

import mlflow
import mlflow.sklearn

import logging
logging.basicConfig(level=logging.WARN)
logger = logging.getLogger(__name__)


def eval_metrics(actual, pred):
    rmse = np.sqrt(mean_squared_error(actual, pred))
    mae = mean_absolute_error(actual, pred)
    r2 = r2_score(actual, pred)
    return rmse, mae, r2

warnings.filterwarnings("ignore")
np.random.seed(40)

# Read the wine-quality csv file from the URL
csv_url =\
    'http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv'
try:
    data = pd.read_csv(csv_url, sep=';')
except Exception as e:
    logger.exception(
        "Unable to download training & test CSV, check your internet connection. Error: %s", e)

# Split the data into training and test sets. (0.75, 0.25) split.
train, test = train_test_split(data)

# The predicted column is "quality" which is a scalar from [3, 9]
train_x = train.drop(["quality"], axis=1)
test_x = test.drop(["quality"], axis=1)
train_y = train[["quality"]]
test_y = test[["quality"]]

# ここのパラメータを書き換える
alpha = 0.5
l1_ratio = 0.5

with mlflow.start_run():
    lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)
    lr.fit(train_x, train_y)

    predicted_qualities = lr.predict(test_x)

    (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)

    print("Elasticnet model (alpha=%f, l1_ratio=%f):" % (alpha, l1_ratio))
    print("  RMSE: %s" % rmse)
    print("  MAE: %s" % mae)
    print("  R2: %s" % r2)

    mlflow.log_param("alpha", alpha)
    mlflow.log_param("l1_ratio", l1_ratio)
    mlflow.log_metric("rmse", rmse)
    mlflow.log_metric("r2", r2)
    mlflow.log_metric("mae", mae)

    mlflow.sklearn.log_model(lr, "model")

このコードはwine qualityデータセットをElasticNetを利用して回帰モデルをフィッティングするコードです。
下の方にあるalpha, l1_ratioがパラメータとなっていて、ここをいじる感じになります。

まずはそれぞれ0.5のままで、実行してみましょう。
以下のような出力が得られます。

Elasticnet model (alpha=0.500000, l1_ratio=0.500000):
  RMSE: 0.7931640229276851
  MAE: 0.6271946374319586
  R2: 0.10862644997792614

今回の学習ではRMSE: 0.79, MAE: 0.63, R2係数: 0.11程度です。
これがパラメータ等込ですでにmlflowのほうに記録されています。
再度MLflowを開いて、画面を更新しましょう。すると、以下のようになっているはずです。

f:id:marufeuillex:20200103130032p:plain

学習時のパラメータや結果が保存されていることがわかります。
Dateのところをクリックすると、より詳細な情報を見ることができます。

f:id:marufeuillex:20200103130414p:plain

先程のコードで指定したパラメータやモデルそのものが保存されているのがわかります。
わかりやすくていいですね。

何度かパラメータを変えて実験してみる

適当にパラメータを変えて何度かやってみます。

f:id:marufeuillex:20200103130131p:plain

そうすると、このような感じでどんどん記録されていきます。

複数を選択して比較することも可能です。例えばこんな感じ。

f:id:marufeuillex:20200101114608p:plain

下の方にあるように、グラフで表示することできるのでわかりやすくていいですね。

改めてコードを見る

ではどのように記録しているのか、実際にコードを見ていきます。
このあたりですね。

    mlflow.log_param("alpha", alpha)
    mlflow.log_param("l1_ratio", l1_ratio)
    mlflow.log_metric("rmse", rmse)
    mlflow.log_metric("r2", r2)
    mlflow.log_metric("mae", mae)

    mlflow.sklearn.log_model(lr, "model")

こういうコードを元のコードに挟んでいくことで、パラメータを記録していけるようですね。
なんのロギングができるかは、以下を見るのがいいと思います。

mlflow.org

今回使った、パラメータ、メトリックはわかりやすいですが、他にもtagをつけたり、ローカルにあるファイル(アーティファクト)も保管できるようです。

あとモデルの保存はそれぞれのフレームワークごとに用意されているようです。
例えば以下はsklearnのマニュアルです。

mlflow.org

他にもtensorflowやpytorch含め、メジャーどころは網羅されているように見えます。

まとめ

今回は環境構築から、まずは実験の管理という観点でMLflowを使ってみました。
個人レベルだとパラメータの管理ができるだけでとても便利なのですが、他にもプロジェクト管理やモデルのサービング機能があります。
せっかくなので、正月休み中に一通り触ってまとめておきたいと思います。