グラフ機械学習と強化学習について

主にグラフ機械学習や強化学習手法を記載します。

ハイパーパラメータ管理:Mlflow と Hydra

機械学習を使ったプロジェクトに携わると、データ解析をしていくにつれて大量のモデルが構築され、これらのモデルを管理するだけでも大変です。IT企業のようなソフトウェアエンジニアリングの部署がある企業では、各社ベストプラクティスがあるのだと思いますが、私の場合、メーカー勤務かつ部署が研究開発ということもあり、ソフトウェアエンジニアリングのノウハウを学ぶことはできず、手探りで探索しているような状況です。となるとOSSを使うことになるのですが、機械学習のライフサイクル管理もかねてmlflowを本格的に使っていきたいと思います。

mlflow.org

また、facebook researchが作成しているHydraと呼ばれるconfig管理フレームワークを導入します。これはargparseで書いていた部分をyamlで書くことで簡略化できるようになっています。今まではスクリプト上で煩雑だったものが、yamlで管理できるため非常に見やすくなります。

hydra.cc

これら両方を用いて、ElasticNetのハイパーパラメータをグリッドサーチしていきたいと思います。

Hydra

インストール

pip install hydra-core --upgrade

使い方

まずはconfig.yamlに設定ファイルを記載していきます。

params:
  alpha: 0.5
  l1_ratio: 0.5

file:
  path: 'sklearn_elasticnet_wine/winequality-red.csv'

次に、python側では以下のようにして読み込みます。

import hydra
from omegaconf import DictConfig, OmegaConf

@hydra.main(config_name='config')
def main(cfg: DictConfig) -> None:
        print(OmegaConf.to_yaml(cfg))

if __name__ == "__main__":
    my_app()

コマンドライン引数を読み込みたい関数にhydraをデコレートしてあげます。実行すると以下のようになります。

$ my_app.py 
params:
  alpha: 0.5
  l1_ratio: 0.5

file:
  path: 'sklearn_elasticnet_wine/winequality-red.csv'

オーバーライドをするのも簡単です。

$ my_app.py params.alpha=1.0 params.l1_ratio=1.0
params:
  alpha: 1.0
  l1_ratio: 1.0

file:
  path: 'sklearn_elasticnet_wine/winequality-red.csv'

コマンドライン引数を変更した場合はoutputsディレクトリが自動で生成されログが出力されます。

.hydra
├── config.yaml
├── hydra.yaml
└── overrides.yaml

Multirun

また、複数の引数を実行することもできます。

$ my_app.py params.alpha=0.1,0.5,1.0 params.l1_ratio=0.1,0.5,1.0
[2021-03-06 15:49:32,780][HYDRA] Launching 9 jobs locally
[2021-03-06 15:49:32,781][HYDRA]        #0 : params.alpha=0.1 params.l1_ratio=0.1
[2021-03-06 15:49:32,979][HYDRA]        #1 : params.alpha=0.1 params.l1_ratio=0.5
[2021-03-06 15:49:33,156][HYDRA]        #2 : params.alpha=0.1 params.l1_ratio=1.0
[2021-03-06 15:49:33,351][HYDRA]        #3 : params.alpha=0.5 params.l1_ratio=0.1
[2021-03-06 15:49:33,535][HYDRA]        #4 : params.alpha=0.5 params.l1_ratio=0.5
[2021-03-06 15:49:33,722][HYDRA]        #5 : params.alpha=0.5 params.l1_ratio=1.0
[2021-03-06 15:49:33,919][HYDRA]        #6 : params.alpha=1.0 params.l1_ratio=0.1
[2021-03-06 15:49:34,095][HYDRA]        #7 : params.alpha=1.0 params.l1_ratio=0.5
[2021-03-06 15:49:34,285][HYDRA]        #8 : params.alpha=1.0 params.l1_ratio=1.0

このようにすることでグリッドサーチによるハイパーパラメータ探索のコードを簡単に記述することができます。

Mlflow

mlflowを用いれば各実行条件ごとのメトリック、モデル、ハイパーパラメータを適切にログを取ることができ、可視化をすることができます。今回はグリッドサーチですが、optunaのようなベイズ最適化を用いた探索の場合では、非常に簡単にログを取ることができます。

インストール

pip install mlflow

簡単な使い方

import os
from random import random, randint
import mlflow

if __name__ == '__main__':
    with mlflow.start_run(run_id=0):
        mlflow.log_param("param1", randint(0, 100))
        for i in range(3):
            mlflow.log_metric("foo", random() + float(i))

        # Log an artifact (output file)
        if not os.path.exists("outputs"):
            os.makedirs("outputs")
        with open("outputs/test.txt", "w") as f:
            f.write("hello world!")
        mlflow.log_artifacts("outputs")

各計算結果をlog_metriclog_param等で保存していきます。run_idは自動で生成されますが自分でつけることもできます。

mlflow + hydra

ネットで調べてみると、グリッドサーチを試してみたという例は複数ありました。私もすごく単純な例で試していきます。

ベースはmlflow/examples/sklearn_elasticnet_wineのサンプルコードです。 https://github.com/mlflow/mlflow/tree/master/examples/sklearn_elasticnet_wine

Metric

import os
import warnings
import logging

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 hydra
from omegaconf import DictConfig

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

EXPERIMENT_NAME = 'elasticnet_wine'

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': rmse, 'mae': mae, 'r2': r2}

def fetch_data(cfg: DictConfig):
    data = pd.read_csv(hydra.utils.to_absolute_path(cfg.file.path), sep=";")
    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"]]
    return train_x, test_x, train_y, test_y

まずは基本的なパッケージを読み込みます。

mlflowで使う実験名も最初に指定しています。データやモデルの条件が変わった場合は実験名を変えるのかなと思います。 メトリックを計算する関数と、データを読み込む関数を指定します。hydraの注意点ですが、実行時にパスが自動的に変更されてしまいますので、hydra.utils.to_absolute_path()を使って実行時のパスを取得してあげる必要があります。

Main

@hydra.main(config_name='config')
def main(cfg: DictConfig):
    warnings.filterwarnings('ignore')
    np.random.seed(40)
    hydra_path = os.getcwd()
    train_x, test_x, train_y, test_y = fetch_data(cfg)

    alpha = float(cfg.params.alpha)
    l1_ratio = float(cfg.params.l1_ratio)
    lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)
    lr.fit(train_x, train_y)
    predicted_qualities = lr.predict(test_x)
    metrics = eval_metrics(test_y, predicted_qualities)

    # MLFLOW
    os.chdir(hydra.utils.get_original_cwd())
    client = mlflow.tracking.MlflowClient()

    # データごとにExperimentを作成
    for exp in client.list_experiments():
        if EXPERIMENT_NAME == exp.name:
            experiment_id = exp.experiment_id
            break
    else:
        experiment_id = client.create_experiment(EXPERIMENT_NAME)

    run_id = client.create_run(experiment_id).info.run_id

    for k, v in cfg.params.items():
        client.log_param(run_id, k, v)

    for k, v in metrics.items():
        client.log_metric(run_id, k, v)

    hydra_files = ['.hydra/config.yaml', '.hydra/hydra.yaml', '.hydra/overrides.yaml']
    for hydra_file in hydra_files:
        client.log_artifact(run_id, os.path.join(hydra_path, hydra_file))

    with mlflow.start_run(run_id=run_id):
        mlflow.sklearn.log_model(lr, "model")

    client.set_terminated(run_id)

注意点は、先ほどと同様にhydraが自動でログを作成するのでパスが変わってしまうことです。 os.chdir(hydra.utils.get_original_cwd())でmlflowが生成するmlrusのディレクトリに保存されるようにします。

また、実験名が一致するディレクトリから実験idを取得します。run_idは実験idから自動で作成されます。

そのあとは、各ハイパーパラメータの値や、メトリックスの値をclientのメソッド方に渡してあげるだけです。 ついでにhydraの出力ファイルもアーティファクトの方に保存できるようにしています。

実行結果

下記コマンドでmlflowのUIが描画されます。内部ではreact.jsを用いています。

mlflow ui

f:id:udnp:20210306162823j:plain
mlflow

以前にoptunaを試した例も載っていますが、計算結果がすべて保存されていることが分かります。

Scatter Plot, Contour Plot, Parallel Cordinates Plotも見れるようになっています。非常に便利です。

f:id:udnp:20210306163201j:plain
mlflow2

最後に

mlflowとhydraを使ってハイパーパラメータ探索を行いモデル管理まで行いました。効率よく機械学習モデルを作成して、デプロイまで考えるとなるとこういった技術は早く使えこなせるようになっていきたいと思います。

保存先もfileではなくてデータベースに保存したり、分散学習した場合のハイパーパラメータ管理など、こちらも手を付けていきたいと思います。

参考にさせていただいたサイト