【コンペに役立つ？】Google Colabで作成した機械学習モデルにMLOpsを追加してみた。

# 概要

## 投稿目的

本記事では、Google Colabで作成した機械学習モデルにMLOpsを実装する方法を解説します。
MLOpsについての概要記事はたくさんあるのですが、実装に関する記事があまりないなと思い、自分で調べながら作成しました。


**「MLOpsを個人的にやってみたい！」** という方はもちろんですが、
**「コンペに勝つための1ツール」** としても見ていただけると思います。

## 本記事に書いていること

- 機械学習モデルを作成した後、MLOpsを追加する方法
- ベストなパラメータ・メトリクスのモデルをMLOpsから確認する方法
- 上記をGoogle Colabから作成する方法


## 本記事に書いていないこと

- 機械学習モデル(LightGBM)の実装に関する説明
- SIGNATEのコンペに勝つ方法
- ngrokの詳しい解説


## 今回参加しているコンペ

最近登録したSIGNATEで開催されている機械学習Beginnersコンペです。
テーマは、国勢調査のデータから対象データの年収を分類予測するというよくある内容です。
ランキング上位を目指しているわけではありませんが、現在スコアは0.847です。


https://signate.jp/competitions/1392


## 使用した機械学習モデル

モデルにはLightGBMを使用しました。
今後のMLOpsの状態がわかりやすくなるよう、
LightGBMモデルのパラメータや訓練のコーディングだけ簡単に記載しておきます。
（環境設定・データの前処理・特徴量の追加は省略）

```モデル実装〜訓練.py
# インポート
import lightgbm as lgb

# データ分割
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=42)

# LightGBMのデータセットに変換
train_data = lgb.Dataset(X_train, label=y_train)
val_data = lgb.Dataset(X_val, label=y_val, reference=train_data)


# パラメータ設定
params = {
    'objective': 'binary',  # 二値分類
    'metric': 'binary_error',  # 評価指標
    'boosting_type': 'gbdt',  # 勾配ブースティング
    'num_leaves': 31,  # 木の葉の数
    'learning_rate': 0.05,  # 学習率
    'feature_fraction': 0.9  # 特徴量のサンプル
}

# モデルの訓練
evals_result = {}  # 訓練結果を保存する辞書
gbm = lgb.train(
    params,
    train_data,
    valid_sets=[train_data, val_data],  # 訓練データと検証データ
    valid_names=['train', 'val'],  
    num_boost_round=500,  # 最大反復回数
    callbacks=[
        lgb.early_stopping(stopping_rounds=50),  # early_stoppingを使用
        lgb.record_evaluation(evals_result)  # 結果を保存
    ]
)
```



## その他ツール



### MLflowフレームワーク

MLflowは機械学習モデルのライフサイクルを管理するために設計されたオープンソースのフレームワークです。MLflowを使うことで、機械学習モデルのトラッキング、バージョン管理が簡単にできます。
```インストール方法.
!pip install mlflow
```

`mlflow.start_run()` や `mlflow.log_param()`, `mlflow.log_metric()`, `mlflow.log_artifact()` などを使って、モデルやパラメータを記録します。

### Google Colab

今更ですが実行環境はGoogle Colabを使用します。


### nglok

本来はローカルサーバーで以下を実行するだけで、MLOpsによる機械学習モデルのインターフェースが確認できます。

```MLflow UIの出力.
!mlflow ui 
```

ただしGoogle Colab上では、ローカルサーバーを実行してもサーバーが開かない場合があります。

今回ここが1つの肝となるわけですが、Google Colab環境から直接ブラウザにはアクセスできません。そのため、**ngrok(エングロック)** を使用して外部からアクセスできるようにする方法を取っています。

ngrokとは、開発者がローカル環境で開発したサーバーをインターネットに公開することを可能にするトンネリング/リバース・プロキシツールです。
ngrokを私は今回初めて知ったのですが、QiitaにもZennにも解説記事はたくさんあるので随時参照ください。

https://ngrok.com/



# MLOpsの構築方法

## 1.ngrokの会員登録＆トークン取得(事前準備)

まずコーディングの前にあらかじめngrokの会員登録を済ませておきましょう。
GoogleアカウントかGitHubアカウントで無料ですぐに作成できます。

https://ngrok.com/


下記Authtokenから自分のトークンを取得できます。
後からでも確認できるため、メモしておく必要はありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/e6cbc4b2-17f5-43ea-af9c-10529b34c0af.png)

https://dashboard.ngrok.com/get-started/your-authtoken


## 2. 必要なライブラリのインポート

ここからコーディングです。
想定している箇所は、前処理・特徴量エンジニアリングを行った後のタイミングです。

```インストール.py
# MLflow
!pip install mlflow 

# ngrok
!pip install pyngrok 
```

```ライブラリのインポート.py
import mlflow
import mlflow.lightgbm
import subprocess
from pyngrok import ngrok
```


## 3. MLflow UIを使えるようColab上での設定

ここで先ほど取得したトークンを使用します。
以下のコードを実行すると、MLflow UIをColab上で使えるようにすることができます。

```MLflow UIの公開.py
# ngrokの認証トークンを設定
ngrok.set_auth_token('1で取得したトークンをペースト')

# MLflow UIをバックグラウンドで実行
subprocess.Popen(["mlflow", "ui", "--host", "0.0.0.0", "--port", "5000"])

# ngrokで5000ポートにトンネルを作成
public_url = ngrok.connect(5000)
print(f"MLflow UI is available at: {public_url}")
```

`public_url`として、MLflow UIへアクセスするためのURLが出力されます。

本来MLflow UIはローカルサーバーに作成されるのですが、ngrokを使うことでColab上の5000ポートをインターネットに公開します。
こうすることで、MLflow UIがColab外部からもアクセスできるようになるということです。

## 4. 新しいMLflowのランを実行


MLflowフレームワークにある `mlflow.start_run()` を使って、MLflowの実験（Run）を記録しながら、機械学習モデル(LightGBM)を訓練します。
訓練の際のパラメータやメトリクスをここで記録できます。
機械学習モデルは随時みなさんが使用するものに置き換えられてください。

```モデルの訓練・ログ.py
# MLflowのトラッキングURIを設定
mlflow.set_tracking_uri("http://localhost:5000")

# モデルの訓練後にログ記録
with mlflow.start_run():

    # LightGBMの訓練
    gbm = lgb.train(
        params,
        train_data,
        valid_sets=[train_data, val_data],
        valid_names=['train', 'val'],
        num_boost_round=500,
        callbacks=[
            lgb.early_stopping(stopping_rounds=50),
            lgb.record_evaluation(evals_result)
        ]
    )
    
 # ※X_trainから1行を抽出
    input_example = pd.DataFrame(X_train.iloc[0]).T  # 最初の1行を入力例として指定

    # モデルをMLflowに保存
    mlflow.lightgbm.log_model(gbm, "lightgbm_model", input_example=input_example)

    # パラメータをMLflowに記録
    for param, value in params.items():
        mlflow.log_param(param, value)

    # メトリクスをMLflowに記録
    mlflow.log_metric("best_iteration", gbm.best_iteration)
    mlflow.log_metric("train_error", evals_result['train']['binary_error'][gbm.best_iteration])
    mlflow.log_metric("val_error", evals_result['val']['binary_error'][gbm.best_iteration])

```


冒頭1行目ですが、`with` を使うことで、ブロックが終了すると mlflow.end_run() が自動的に呼ばれるようになっています。私は初めて使用しました。

### 4の補足：モデルの入力データやデータの型を推測させる

上記4のコードの中の※部分です。
```データの推測.py
 # ※X_trainから1行を抽出
    input_example = pd.DataFrame(X_train.iloc[0]).T  
```

この行のコードを書いていなかった際、以下の警告が起きていました。

`mlflow.models.model: Model logged without a signature and input examplez`


この警告が出ているのは、MLflowがモデルを記録する際に、モデルの入力データの例やデータの型を自動的に推測できなかったことを意味しているそうです。
処理自体に影響はしないだったのですが、MLflowの管理機能をより効果的に使うためにも、※の`input_example`を設定することをおすすめします。
これにより、最初の1行を入力例として自動でデータを推測してくれます。

以上でコーディングは終了です。

## 5. MLflow UIを確認する。

あとは3で取得したURLを踏むことで、MLflow UIを確認できます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/5457aa2e-f907-4054-bf85-e80f716e1705.png)


こちらはOverView(概要)の画面。念のためRunIDのみ隠しています。

# 今回のMLOpsからわかること

それではModel metricsの画面を見ていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/a5bd7069-60a8-4fd3-a0f1-7a0f230497e2.png)


## best_iteration

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/9ba657bd-6c23-48b1-b1ac-a146939596f1.png)

最も良いパフォーマンスを発揮した訓練のイテレーション回数です。
この場合、51が表示されているので、訓練の途中で早期停止（`early stopping`）が適用され、最もパフォーマンスが良かったのは51回目のイテレーションであったことを示しています。
このモデル使用時に工夫していた点の1つだったため、それが可視化できたのは嬉しいですね。

## train_error

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/4ff0bad7-bb7e-4b63-95db-1921a4119122.png)


訓練データに対するエラー率（binary_error）です。
ここでは `train_error = 0.14` となっており、訓練データに対する予測の誤りが約14%であることを示しています。
訓練データに対してモデルが約86%の精度ということで、訓練誤差は比較的低く、モデルは訓練データにうまく適合していると解釈できます。

## val_error

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/c3d7fa43-ed26-4316-aaea-a99e114a7676.png)

検証データに対するエラー率（binary_error）です。
ここでは val_error = 0.15 となっており、検証データに対する予測の誤りが約15%であることを示しています。
検証データに対してもモデルは約85%の精度で予測しているということですが、訓練データに対する誤差と比較すると若干誤差が増えているため、モデルが若干過学習している可能性もありそうです。



# 個人でMLOpsを追加すると何がいいのか?


では今回のMLOpsの何がいいのかというと、
**「モデルのパラメータ・メトリクスの変化による比較ができること」** です。

例えば、lightGBMの以下2つのハイパーパラメータを組み合わせる９種類を出力してみます。
learning_rates = [0.01, 0.05, 0.1]
num_leaves_values = [31, 50, 100]

runを実行すると、先ほど公開したサーバに9つのrunが反映されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/8b0e8fdf-c590-436c-89e4-0b8cd058f597.png)


選択して比較してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/2cd0f7a5-fbc0-495d-9d09-7248dd357af4.png)

インターフェースかっけ〜〜〜。
テーブルとグラフによって比較してくれています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/451e781e-556a-41e7-a1ea-38fa825d38fb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/6bbc7327-d77f-4276-afea-c6889df2403d.png)

パラメータ単体でもそれぞれのrunで比較することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/1f9b64d8-f1af-4eb6-aee2-e32a5c586249.png)



`lgbm_nl_50_lr_0.01` は `train_error` が低めで `val_error` も他のモデルに比べて適度に低いです。訓練と検証の誤差のバランスが良いため、このなかでは最も良いモデルであると考えられそうです。

# まとめ

今回はGoogle Colabで作成した機械学習モデルにMLOpsを追加してみました。
MLOpsって聞くとなんだか難しく、どちらかというとDevOps的な「保守！」のイメージがあったため個人の場では関係ないと思っていました。
実際にビジネスの場で使われる言葉としては、データの最新化やプロジェクトの円滑な進め方の場で使われる大きな意味の言葉だと思います。

ただ、今回作成してみてMLOpsは **個人でも実装できる簡単さ** を持っていること と **良いモデルを作成するための効率化ツール** になることがわかりました。
これからkaggleやSIGNATEの機械学習コンペで使用してみたいと思います。