【機械学習】MLflowのQuickstartやってみた（3）

# 前回の記事
[前回の記事](https://qiita.com/panda531/items/6100c7124c8a9f6bc500)の続きです。

# 環境
- macOS High Sierra
- pyenv 1.2.1
- anaconda3-5.0.1
- Python 3.6.8
- MLflow==0.8.2

# モデルを保存する
`MLflow`では通常通りに機械学習のモデルファイルをそのまま`Artifacts`に保存することが出来ます。
それに加えて`MLmodel`と呼ばれる形式でモデルを保存することが可能です。

## `MLmodel`とは
いくつかのプラットフォームのモデルを統一的に扱えるようにしたもの。
[ここ](https://mlflow.org/docs/latest/python_api/index.html)を見た感じでは、`azuleml`、`h2o`、`keras`、`pytorch`、`sagemaker`、`sklearn`、`tensorflow`などなど（知らないものもいくつか。。。）で保存したモデルについては統一的な扱いが出来そうな気がします。
[前回の記事](https://qiita.com/panda531/items/6100c7124c8a9f6bc500)で使用したサンプルプロジェクトでは以下のようなコードで`sklearn`のモデルを保存していました。

```python:mlflow-example/train.py
mlflow.sklearn.log_model(lr, "model")
```

保存される内容としては以下の3ファイルのようです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/245628/0606ac66-4840-e1fb-4f0f-256c9a70d8f6.png)

### `MLmodel`
統一的な呼び出しを実現するための管理ファイルのようです。
ライブラリのバージョンやどのプラットフォームで保存したモデルかなどが記載されています。

```bash:MLmodel
artifact_path: model
flavors:
  python_function:
    data: model.pkl
    env: conda.yaml
    loader_module: mlflow.sklearn
    python_version: 3.6.8
  sklearn:
    pickled_model: model.pkl
    serialization_format: cloudpickle
    sklearn_version: 0.20.2
run_id: 156398506a8d4a94a27a7bbef191ac39
utc_time_created: '2019-03-02 07:07:43.692121'
```

### `conda.yaml`
実行に必要な環境が記載されています。
`MLproject`の時と同じように、この呼出しの時にも新しい仮想環境が作成されそうです。

```bash:conda.yaml
channels:
- defaults
dependencies:
- python=3.6.8
- scikit-learn=0.20.2
- cloudpickle==0.8.0
name: mlflow-env
```

### `model.pkl`
モデル本体です。

# 保存したモデルを推論に使う
では[Quickstart](https://mlflow.org/docs/latest/quickstart.html#saving-and-serving-models)に沿ってやってみます。

以下を実行すると`sklearn`のロジスティック回帰を使用したモデルが保存されます。

```bash
$ python sklearn_logistic_regression/train.py
```
（`sklearn_logistic_regression/train.py`は[このリポジトリ](https://github.com/mlflow/mlflow)の`example`ディレクトリにあります）

出力はこんな感じ

```bash
~/.local/lib/python3.6/site-packages/sklearn/linear_model/logistic.py:433: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
  FutureWarning)
Score: 0.6666666666666666
Model saved in run edf260a6f5ec4813b7ae38a7cef4e19a
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/245628/839d3a62-d4f3-f3ad-36c6-0e42d0005870.png)

ちゃんと実行されてモデルも保存されているようです。
このモデルを使用するには、上記で`edf260a6f5ec4813b7ae38a7cef4e19a`となっている`<RUN_ID>`を使って以下のようにします。

```bash
$ mlflow pyfunc serve -r <RUN_ID> -m model
# $ mlflow pyfunc serve -r edf260a6f5ec4813b7ae38a7cef4e19a -m model
```

※`mlflow ui`などを起動している場合はデフォルトで`5000`番ポートを使用しています。`mlflow pyfunc serve`もデフォルトで`5000`番ポートを使用するので同時に起動させると以下のようなエラーが発生します。

```python
Traceback (most recent call last):
#　　︙
#(長いので中略)
#　　︙
5.0.1/envs/ml/lib/python3.6/socketserver.py", line 470, in server_bind
    self.socket.bind(self.server_address)
OSError: [Errno 48] Address already in use
```

このような場合は以下のように使用されていないポート番号(`<PORT>`)を指定するようにコマンドの変更が必要です。

```bash
$ mlflow pyfunc serve --port <PORT> -r <RUN_ID> -m model
# $ mlflow pyfunc serve --port 1234 -r edf260a6f5ec4813b7ae38a7cef4e19a -m model
```

これで以下のように仮想環境を作成後に推論サーバーが起動します。

```bash
2019/03/03 16:17:36 INFO mlflow.projects: === Creating conda environment mlflow-f49dbaff98259ddf94bbf2f77d8d5d47b925c1b3 ===
Using Anaconda API: https://api.anaconda.org
Solving environment: done


==> WARNING: A newer version of conda exists. <==
  current version: 4.4.7
  latest version: 4.6.7

Please update conda by running

    $ conda update -n base conda



Downloading and Extracting Packages
mkl 2019.1: ############################################################################################################################################################## | 100% 
mkl_fft 1.0.10: ########################################################################################################################################################## | 100% 
scikit-learn 0.20.2: ##################################################################################################################################################### | 100% 
mkl_random 1.0.2: ######################################################################################################################################################## | 100% 
scipy 1.2.1: ############################################################################################################################################################# | 100% 
numpy 1.16.2: ############################################################################################################################################################ | 100% 
numpy-base 1.16.2: ####################################################################################################################################################### | 100% 
cloudpickle 0.8.0: ####################################################################################################################################################### | 100% 
Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate mlflow-f49dbaff98259ddf94bbf2f77d8d5d47b925c1b3
#
# To deactivate an active environment, use
#
#     $ conda deactivate

2019/03/03 16:19:45 INFO mlflow.pyfunc.cli: === Running command 'source activate mlflow-f49dbaff98259ddf94bbf2f77d8d5d47b925c1b3 && ~/.local/bin/mlflow pyfunc serve -r edf260a6f5ec4813b7ae38a7cef4e19a -m model --no-conda'
 * Serving Flask app "mlflow.pyfunc.scoring_server" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
```

あとはお好きなようにアクセスしてあげると結果を返してくれるようです。
以下は`curl`を使う例です。

```bash
# ポート番号をデフォルトから変更している場合は`localhost:5000/invocations`の`5000`を設定した値に変更すること
$ curl -d '{"columns":["x"], "data":[[1], [-1]]}' -H 'Content-Type: application/json; format=pandas-split' -X POST localhost:5000/invocations
127.0.0.1 - - [03/Mar/2019 16:35:54] "POST /invocations HTTP/1.1" 200 -
[1, 0]
```

おー何やら結果が返ってきました。
機械学習API完成です。

また、注意点として、モデルを保存するコマンド(今回の例では `$ python sklearn_logistic_regression/train.py`)と使用するコマンド(今回の例では `mlflow pyfunc serve -r <RUN_ID> -m model`)を実行する`Python`のバージョンが同じでない場合は以下のようなエラーが発生するようです。

```python
File "/usr/local/lib/python3.6/site-packages/mlflow/sklearn.py", line 54, in _load_model_from_local_file
return pickle.load(f)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc6 in position 0: ordinal not in range(128)
```

# まとめ
`MLmodel`を使って、機械学習モデルの保存と推論サーバーの使用方法について実施してみました。

まだQuickstartをやってみただけですが、これくらいの知識でも最低限実用に足りる知識は得たような感覚があります。（もちろん詳細はもっといろいろあるはずですが）

また、`MLflow`は現時点ではβ番ということなので、本記事の内容は正式リリースの時点では変更になっている可能性がありますので予めご了承ください。