KubeFlow上で機械学習の環境を用意し、MLFlowでモデルを保存する

[更新] KubeFlow 1.0のCONFIG_URIに対応しました。

# 0. 目標

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/29ab6a46-6bbb-d9d8-d9a3-c180503c9ca4.png" width="300px"/>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/88668462-afdf-bba0-b0de-75ea544064d0.png" width="300px"/>

**KubeFlow**は、機械学習の開発・運用の環境を提供するOSSです。
KubeCon NA 2019でも多くのセッションで取り上げられました。Jupyterを必要なだけ起動したり、パイプラインを実行できたりします。v0.6.2では、複数ユーザをサポートできるようになりました。今年前半でv1.0が提供される予定で、開発が目下進められています。
また、**MLFlow models**も、機械学習モデルの保存という観点では、保存形式の一つとして非常に強力なツールと思います。

今回は、以下の2つをやってみたいと思います。

- **KubeFlowを構築。その上に、まずユーザAとユーザBそれぞれにJupyterを起動。**
- ユーザAがMLFlow modelsを使って、**学習済みモデルを保存**。それを、**ユーザBのJupyterに共有し復元**。

![kubeflow-diagram-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/acd6a5fe-04ae-a591-c339-436cfd4d459b.png)



# 1. KubeFlowの構築

KubeFlowの環境がすでに用意されている場合には、この章を読み飛ばして、`2.MLFlow`の章に移ってください。

### 必要な環境

前提となるリソース量が結構大きいです。

| リソース | 量 |
|----|----|
| CPU | 4コア |
| Memory | 12GB |
| ディスク | 50GB |

### 前提となるkindによるKubernetes環境の構築

KubeFlowがサポートするバージョンのKubernetesクラスタを用意します。
KubeFlow 0.7.1(最新の1.0も)がサポートするのは最新のKubernetesではなく、少し前の1.15です。
kindでは、以下のように1.15.7というノードイメージを指定してクラスタを構築します。

```sh
kind create cluster --image kindest/node:v1.15.xx
```

バージョンは都度書き換えてください。2020/6/23現在、`v1.15.11`が最新ですが、以下のサイトで最新のイメージを検索して利用することをおすすめします。
https://hub.docker.com/r/kindest/node/tags?page=1

kindについて詳しくは、["kindで軽量テスト用Kubernetesクラスタを作る＆運用する時のTIPS"](https://qiita.com/Hiroyuki_OSAKI/items/2395e6bbb98856df12f3)を参照してください。

### kfctlを使ったKubeFlowのインストール

Kubernetes上に、コマンドツール`kfctl v1.0rc`を使って、`kubeflow v0.7.1`をインストールします。

#### 失敗編

最初にドキュメントに記載されていた以下を試して見ましたが、私の環境 (Macbook) でこちらのバージョンを使用した際にはあとに出てくる`kfctl apply`で失敗しました。

```sh
wget https://github.com/kubeflow/kubeflow/releases/download/v0.7.0/kfctl_v0.7.0_darwin.tar.gz
tar zxf kfctl_v0.7.0_darwin.tar.gz
mv kfctl-darwin kfctl
chmod +x kfctl
```

#### 成功編

実は`kfctl`の本家はこちらに移っていました。こちらの最新版であるv1.0-rcを利用します。

https://github.com/kubeflow/kfctl/releases

ダウンロードしたファイルを展開し、`kfctl`を生成します。

以下Macの場合 (Linuxは上記URLにあるamd64にファイル名を変える)

```
mkdir $HOME/kubeflow
cd $HOME/kubeflow
curl -LO https://github.com/kubeflow/kfctl/releases/download/v1.0.1/kfctl_v1.0.1-0-gf3edb9b_darwin.tar.gz
tar zxf kfctl_v1.0.1-0-gf3edb9b_darwin.tar.gz
chmod +x kfctl
sudo mv kfctl /usr/local/bin/kfctl
```

### `kfctl`が使用する環境変数の作成

以下の`kubeflow.sh`を作成します。
先ほどダウンロードした`kfctl`にパスを通し、kubeflowに必要なファイル群を貯めるディレクトリなどの設定を記載します。
ポイントは、`KF_DIR`にkubeflowが自動で生成するkubernetes用設定ファイルを格納する場所（これから作成する空ディレクトリのパス）を設定することです。

```kubeflow.sh
export BASE_DIR=$HOME/kubeflow
export PATH=$PATH:$BASE_DIR
export KF_NAME=mykubeflow
export KF_DIR=${BASE_DIR}/${KF_NAME}
export CONFIG_URI="https://raw.githubusercontent.com/kubeflow/manifests/v1.0-branch/kfdef/kfctl_k8s_istio.v1.0.1.yaml"
```

構築するKubeFlowの構成（認証機能あり、なし）や、構築場所（AWS、Azure、GCP、既存Kubernetes）によって`CONFIG_URI`を変えます。

CONFIG_URIのリストは公式ドキュメントに記載されています。

https://www.kubeflow.org/docs/started/getting-started/#configuration-quick-reference

以下のコマンドで、上記設定を読み込みます。

```
source kubeflow.sh
```

### KubeFlowのデプロイ

```
mkdir -p ${KF_DIR}
cd ${KF_DIR}
kfctl apply -V -f ${CONFIG_URI}
```

このあと、KubeFlowに必要なIstioやさまざまなツールのコンテナイメージがダウンロードされるまでに相当の時間がかかります。

すべてのコンテナが起動したら、無事インストール終了です。

```sh
kubectl get po --all-namespaces
```

### ログイン & Jupyterの起動

```sh
kubectl port-forward -n istio-system svc/istio-ingressgateway 80:80 --address 0.0.0.0
```

`http://localhost`にアクセスします。
Jupyterを起動するには、以下のように画面遷移します。

| 初期画面→notebook serversをクリック| `+NEW SERVER`から以下のように名前やイメージを指定して起動 |
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/96aed069-998c-5b75-08f9-9f6a33eef35c.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/fa4c47cf-dee5-fefd-d364-6a01ad8bdfb0.png)|

| Notebookの`CONNECT`を選択| Jupyter notebookの画面|
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/825a1c65-cba3-5933-f669-29680b1286f0.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/ef0fd060-8847-5a85-ee8f-1f3597e6285b.png)|

### 複数のJupyterで同じフォルダを共有する設定

複数のJupyterを起動すると、通常はまったく別のフォルダが見えます。これは、それぞれのJupyterがコンテナとして起動しており、コンテナはもともと隔離するための技術なのでそういうふうになってしまいます。

KubeFlowでは、複数のJupyterで同一フォルダを共有することができ、設定はかんたんです。

同じフォルダを見られるようにするには、2つ目のJupyter作成時の`Workspace Volume`に`Existing`設定をします。これは、既存のフォルダを選択するという意味です。1つ目のJupyter作成時にすでにフォルダができているので、それを指定すればそのフォルダが共有されるようになります。

![kubeflow-diagram2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/09f3a2f3-6f28-2502-e221-3d0ce15830d9.png)



| フォルダ使いまわし設定 <br/> (ユーザBのJupyter作成時) | 起動したNotebookに既存ディスクがマウントされる|
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/07e73784-d38c-c13e-bfc5-e820910c748e.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/4342a5e5-0203-8e32-d66c-ac25d7377f28.png)|

同じフォルダをみている二人べつべつのJupyter

| ユーザAのJupyter | ユーザBのJupyter|
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/09a3d2ec-3c11-7ec9-e5e7-37b7a7a160ed.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/d073a93e-d170-13ce-04be-dce5213362c2.png)|

# 2. MLFlow modelsの保存と復元

### (ユーザA) MNISTデータセットを学習したCNNモデルを作る

https://keras.io/examples/mnist_cnn/

こちらを参考に、Notebookに貼り付けるだけです。ただし、起動したてのNotebookのコンテナは、何もインストールされていません。なので、`pip`コマンドで必要なライブラリをインストールしてから実行します。

```sh
pip install mlflow --user
pip install keras --user
```

上記はJupyter notebook上で以下のように`!`をつけてからコマンドを入力することでも実行できますし、Jupyterでterminalを作ってからそこに打ち込んでも構いません。

```
!pip install mlflow --user
!pip install keras --user
```

### (ユーザA) MLFlow modelを使って保存する

Notebookが動作している間は、メモリ上にモデルを保持しています。`model`という変数です。このモデルは、一度Notebookを終了すると消えてしまい、再度作り直すためにはもう一度学習し直さなければなりません。時間もかかるしコストもかかります。

この学習済みモデルを一度ファイルに書き出し、いつでも復元するための仕掛けを提供するのが、`MLFlow model`です。
`MLFlow model`は、`save_model`でファイルに保存し、`load_model`でファイルから復元します。

さきほどの学習済みモデル`model`を`save_model`で保存します。

```py
import mlflow.keras
path = "./model"
mlflow.keras.save_model(model, path)
```

実行すると、`./model`というフォルダが新規作成され、中身に以下のようなファイルが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/6b4e8028-fdad-c286-4853-74f596493838.png)

Notebookは[こちら](https://github.com/onelittlenightmusic/mlflow-keras-mnist/blob/master/SaveModel.ipynb)にあります。

### (ユーザB) load_modelを使って復元する

つぎに、それをユーザBのJupyter上に`load_model`を使って復元します。

[前述](#複数のJupyterで同じフォルダを共有する設定)のユーザAとユーザBのJupyterのフォルダ共有設定が終わっているとします。すると、ユーザBには、ユーザAが保存したモデルが`./model`フォルダに見えます。

そこでこれを利用する`LoadModel`というNotebookを作成し、以下のようにモデルを復元し、その`evaluate`関数を呼び出して推論を実行します。

```py
import mlflow.keras
path = "./model"
model = mlflow.keras.load_model(path)

score = model.evaluate(x_test, y_test, verbose=0)
```

Notebookは、[こちら](https://github.com/onelittlenightmusic/mlflow-keras-mnist/blob/master/LoadModel.ipynb)においてあります。

| モデルの復元と推論の実行| |
|---|---|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/c99cd9ee-907b-f13f-39cc-40d486efb8bb.png)||
| ユーザBの推論時のフォルダ構成 <br/> (`./model`と`LoadModel.ipynb`を使用)| ユーザAのフォルダ構成 <br/> (`./model`と`SaveModel.ipynb`を使用)
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/6f2ae79e-8d03-ebb8-62cb-3600a1a30769.png)|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96277/12caadc7-7699-f177-2cce-44928760b153.png)|

これにより、無事`./model`内のファイルから学習済みモデルが復元されて、推論ができたことがわかります。

# まとめ

`KubeFlow`は、ユーザごとの環境をかんたんに作り、フォルダの共有などもできるので、開発・運用が楽になります。
`MLFlow models`は、さまざまな機械学習フレームワークのモデルに対応していて、保存・復元を実現します。モデル共有に適していますね。ただし、モデルだけ共有しても完結しないことも多い（再チューニングなど）ので、一般的な運用ではソースコード(モデルを生成したNotebook自体)を一緒に共有する必要があると思います。

この後、完成したモデルを本番系にデプロイさせるのは、次の機会にしたいと思います。