0. はじめに
色々Pythonで機械学習の検証をしているときに、1回目と2回目の検証でどんなパラメータを使用して、どんな精度が出たのか?を記録するのにゴッチャで疲れてしまった。よって今更ながらMLFlowに入門してみたので初心者でも分かるように記録としてメモしておく。
- 動作環境
- OS : Windows10 pro
- python: 3.9.6 (pyenv)
- mlflow: 2.1.1
- PyGWalker: 0.1.4.3
- jupyter notebook(vscode)
1. MLOps
MLOpsとは、DevOps + Machine Learning の造語である。
簡単にだが説明する
1-1. DevOps(デブオプス)とは?
名前の通りですが、「Dev」と「Ops」の合体造語である。
つまり開発(Development)と運用(Operations)を組み合わせた言葉であり、開発におけるバージョン管理やデプロイ等を自動化する考え方である。
1-2. MLOps(エムエルオプス)とは?
DevOpsのDevを機械学習を表すMLに置き換えた造語である。
以下図はHidden Technical Debt in Machine Learning Systemsより図を抜粋したもので、これが概念を表すらしい。
細かいことは専門の解説記事に譲るとして、私含めMLOps初心者は機械学習(ML)モデルの運用効率化だと思えばいいと思う。※AI系は「PoC」で躓くことがあり、試行錯誤を繰り返す必要が多い分野なのでこのような考えが生まれたんだと思う
2. MLFlow
2-1. MLFlowとは?
機械学習のワークフローの支援ツールでDatabrics社より提供されているOSS。
実験の試行錯誤の過程で生成されるものをまとめて視覚化可能で全部で4つの機能が存在するが、本記事ではMLFlow Trackingの部分を説明
する。
- MLFlow
- MLFlow Tracking : MLの実験結果整理と視覚化
- MLFlow Projects : 学習環境のパッケージ化
- MLFlow Models : デプロイ環境を再現可能な形でパッケージ化
- MLFlow Registry : モデルのバージョンやリリースの管理
2-2. 検証用サンプルデータ準備
今までよくこの手の記事でのサンプルとしてボストン住宅価格を使ってきた。が、これ無くなったみたいですね。。よって同じsklearnからアヤメのデータセットを準備します。
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import pandas as pd
iris_data = load_iris()
iris_data_x = pd.DataFrame(iris_data.data, columns=iris_data.feature_names)
iris_data_y = pd.DataFrame(iris_data.target, columns=["y"])
# 後で使用するのでここで分けておく
X_train, X_test, y_train, y_test = train_test_split(iris_data_x, iris_data_y)
# これは2-3.のPyGWalker用に準備するだけでmlflowには不要
iris_df = pd.concat([iris_data_x, iris_data_y], axis=1)
iris_df.head(2)
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | y | |
---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
1 | 4.9 | 3.0 | 1.4 | 1.4 | 0 |
2-3. PyGWalkerでTableau風にデータ確認
せっかくなので、自分用のメモを兼ねてこのアヤメデータを最近話題になったPyGWalkerを使って確認してみる。
ただし、ここはこの記事の本質とは違うので折りたたみにしました。
興味ある方は開いて確認ください。
PyGWalkerでTableau風にデータ確認してみる
ここだけGoogle Colabを使用しました。
vscodeのjupyterだと可視化まではできますが、フィルタ調整できなかったり色々不具合があったので..
import pygwalker as pyg
# 上で作成したアヤメのdfを指定するだけでOK。セルに表示が出てくる
gwalker = pyg.walk(iris_df)
2-3-1.Dataを編集
まずはDataを編集する。これをやらないと思うように可視化できないので注意
●dimention/measureの設定
measureは連続変数でdimentionは不連続な離散変数。今回は「分類」問題なので目的変数yは「カテゴリ」に設定しなおしておくこと。(数字なのでデフォルトは回帰問題と同じmeasureになっている)
●nominal/ordinal/qualitative/temporalの設定
nominalは離散変数の「名義尺度」のことで、分類の為にとりあえず数字を割り当てたものを指す。まさに今回のyがこれに当たる(数字は便宜上降られたものなので)
ordinalはnominalの「数字の大小の関係がある」バージョン。例えば成績の5段階評価の分類カテゴリ等が該当する(同じく離散変数)
qualitativeは↑2つとは違い、連続変数を指すもの。今回目的変数以外はこれに当たる。
temporalは時系列の概念。時間軸データに適応させる
2-3-2.Visualizationで可視化
クドクド説明しても仕方ないので図を見てください。
ぱっと見の分布とかを確認したい場合はかなり有用ですがカスタマイズがほぼできないと思った方がいいです(当然ですがTableauの劣化版)。
図のフィルタ部分見ても変なレンジになってますが、数字を直接書き込めないスライダ調整式なので、5~7にしたかったんですがこうなってしまいました(笑)
2-4. MLFlowで実験管理
MLFlowでは実験結果を格納する場所が必要。今回はPythonにデフォルトで付属しているSQLite3
をデータベースとして利用することにする。
https://mlflow.org/docs/latest/tracking.html
インストールはpip install mlflow
で行う。
2-4-1. MLFlowのExperimentsを設定する
検証する実験環境をExperimentsと呼ぶ
今回は「ランダムフォレスト」を使ってアヤメを分類する検証環境を作成することにする。
なおフォルダ構成は以下のようになっている(dbフォルダを直下に事前作成してる
)
import mlflow
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import r2_score, mean_squared_error, confusion_matrix, accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
"""
各種設定を行う
Experiments名称は好きにつければいい
DB名称はmlruns.db ※今回はdbフォルダ直下に作成
loggingフォルダ名はmlrunsとして直下に勝手に作成される(ので指定しない)
実験を記録するバックエンドの場所(URI(Uniform Resource Locator))としてsqliteを指定
"""
# Experiments名称
EXPERIMENT_NAME = "ayame_RandomForest"
#トラッキングサーバのURI場所を指定してmlflowに設定
DB_PATH = 'db/mlruns.db'
tracking_uri = f'sqlite:///{DB_PATH}'
mlflow.set_tracking_uri(tracking_uri)
# 実験名を取得する ※初回だから存在しない
experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME)
# 当該Experiment存在しないときは新たに作成してidを取得し、すでに存在する場合はidを取得する
if experiment is None:
experiment_id = mlflow.create_experiment(name=EXPERIMENT_NAME)
else:
experiment_id = experiment.experiment_id
これを実行するとExperimentsが作成されるので、ここで確認してみる。Mlflowがインストールされている仮想環境内で以下コマンドを入力する(今回はdb/mlruns.dbなので指定)
(.venv) mlflow ui --backend-store-uri sqlite:///db/mlruns.db
INFO:waitress:Serving on http://127.0.0.1:5000
サーバーURLが表示されるのでhttp://127.0.0.1:5000
へアクセスすると以下のような画面がブラウザ表示される。
デフォルトでDefault
という環境ができてしまうが、今回設定したayame_RandomForest
の環境も確認できる。
なお、experiment_idはこのExperiments管理番号的なやつで一応確認してみる。
EXPERIMENT_NAME = "Default"
experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME)
experiment_id = experiment.experiment_id
print(f"{EXPERIMENT_NAME}の管理番号は{experiment_id}です")
EXPERIMENT_NAME = "ayame_RandomForest"
experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME)
experiment_id = experiment.experiment_id
print(f"{EXPERIMENT_NAME}の管理番号は{experiment_id}です")
Defaultの管理番号は0です
ayame_RandomForestの管理番号は1です
2-4-2. MLFlowのRunに検証記録を残す(1回目)
Experimentの下で管理される実行環境をRunという
ここに1回目の検証を記録してみる(run_nameにそれを分かるように命名しました)
# この検証を記録するExperiment_idを取得する
EXPERIMENT_NAME = "ayame_RandomForest"
experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME)
experiment_id = experiment.experiment_id
with mlflow.start_run(experiment_id=experiment_id, run_name="RandomForest_1回目") as run:
# 1回目の実験パラメータ
n_estimators = 100
max_depth = 5
max_features = 2
random_state = 77
# Autologの無効化(Autologは後ほど・・)
mlflow.sklearn.autolog(disable=True)
rf = RandomForestClassifier(n_estimators = n_estimators, max_depth = max_depth, max_features = max_features,random_state=random_state)
rf_model = rf.fit(X_train, y_train)
# パラメータをmlflowに記録する
mlflow.log_param("num_trees", n_estimators)
mlflow.log_param("maxdepth", max_depth)
mlflow.log_param("features", max_features)
mlflow.log_param("seed", random_state)
# モデルをmlflowに記録する
mlflow.sklearn.log_model(rf_model, artifact_path=EXPERIMENT_NAME, input_example=X_train)
# モデルで予測する
pred_test = rf.predict(X_test)
# 評価指標をmlflowに記録
acc = accuracy_score(y_test, pred_test)
mlflow.log_metric("acc", acc)
# 混同行列をSeabornで作成
fig = plt.figure()
cm = confusion_matrix(y_test, pred_test)
sns.heatmap(cm, square=True, cbar=True, annot=True, cmap='Blues')
plt.xlabel("Pred", fontsize=13)
plt.ylabel("True", fontsize=13)
# log_figureで混同行列をmlflowに記録する
mlflow.log_figure(fig, "test_confusion_matrix.png")
さてこの後ブラウザを更新してみると、RunとしてRandomForest_1回目が記録されていることがわかる。
さらにRun(RandomForest_1回目)をクリックすると、先程記録したものが格納されていることがわかる。
なお、モデルと画像は実際にはローカルPCの./mlruns/Experiment_id/Run_id/artifacts
に格納されている
2-4-3. autologで全パラメータを勝手に記録する(2回目)
2回目以降の検証も同様であるが、今回はmlflow.**.autolog
の機能を使ってみる。
基本的に学習時の記録がすべて残る設定だと考えればいいと思う。
なおlog_param
やlog_metric
等も書かなくていいが、下記例の様に追加で書いてもいい。
なおmlflow.**.autolog(disable=True)
でAutologを無効化可能
with mlflow.start_run(experiment_id=experiment_id, run_name="RandomForest_2回目") as run:
# 2回目の実験パラメータ
n_estimators = 100
max_depth = 5
max_features = "auto"
random_state = 7
# Autologのを有効にする ※log_modelと同じsklearnを指定する
mlflow.sklearn.autolog()
rf = RandomForestClassifier(n_estimators = n_estimators, max_depth = max_depth, max_features = max_features,random_state=random_state)
rf_model = rf.fit(X_train, y_train)
# パラメータをmlflowに「追加で」記録する(こんな感じでメモとかも書ける)
mlflow.log_param("Memo", "seedとmax_features変えてみた")
# モデルで予測する
pred_test = rf.predict(X_test)
# 評価指標をmlflowに「追加で」記録
acc = accuracy_score(y_test, pred_test)
mlflow.log_metric("test_acc", acc)
# 混同行列をSeabornで作成
fig = plt.figure()
cm = confusion_matrix(y_test, pred_test)
sns.heatmap(cm, square=True, cbar=True, annot=True, cmap='Blues')
plt.xlabel("Pred", fontsize=13)
plt.ylabel("True", fontsize=13)
# log_figureで混同行列をmlflowに「追加で」記録する
mlflow.log_figure(fig, "test_confusion_matrix.png")
すると、2回目は以下のように記録されている数が増えていることがわかる。
Metrixは学習後の評価指標が色々出ており、さらに学習時の混同行列まで保存されてますね。
ただ正直数が多いのであまりお勧めできないかも・・??
2-4-5. 1回目の学習モデルを呼び出して予測する
モデルの呼び出しは簡単で、ブラウザの1回目の部分に呼び出し方が書いてあるのでコピペするだけでいい。
model_name = "ayame_RandomForest"
run_id = "********"
logged_model = f'runs:/{run_id}/{model_name}'
# Load model as a PyFuncModel.
loaded_model = mlflow.pyfunc.load_model(logged_model)
# 予測する
pred_test = loaded_model.predict(X_test)
acc = accuracy_score(y_test, pred_test)
print(acc)
実行結果は当然1回目にmlflow.log_metric("acc", acc)
として記録している正解率と同じことがわかる。
0.9736842105263158
3 おわりに
割と詳細に書いてきたので、思ったより簡単に記録できることが理解していただければ幸いである。忘れっぽい私も今後はこれを活用することで正しく検証の管理をしていこうと考えている。
それでは今回はここまで。
参考