ABEJA Platformでscikit-learnを使った機械学習モデルを学習・デプロイする

## はじめに

本記事は、以下の手順をまとめた記事の後編です。

（前編）
1. コンソールを用いて、ローカル環境からABEJA Platformにファイルをアップロードする
2. ABEJA PlatoformのNotebook上からDataLakeのファイルを参照する
（後編）
3. ABEJA PlatformのJobを用いて、scikit-learnを使った機械学習モデルを学習する
4. モデルをWeb APIとしてデプロイする

前編はこちらの記事を参照して下さい。
https://qiita.com/yushin_n/items/adb48e5ffd2229d1eacf


## ABEJA PlatformのJobを用いて、scikit-learnを使った機械学習モデルを学習する
### 学習用コードの用意
ABEJA PlatformのJobで学習を実行するために、まず、学習用コードを所定の[トレイニングハンドラー関数の形式](https://developers.abeja.io/organization/training-handler/)で用意します。

今回使用したサンプルコードは、こちらから取得可能です。
https://github.com/abeja-inc/Platform_handson/tree/master/iris_scikit-learn

### Jobのバージョンの作成
Job DefinitionのCreate Versionをクリックして、学習用コードをzip圧縮したファイルをドラッグ＆ドロップします。
Runtimeには、使用するイメージを選択します。Handlerには、`ファイル名.ハンドラー関数名`を指定します。今回はtrain.pyのhandler関数を呼び出すため、`train.handler`を入力します。

<img width="740" alt="Screen Shot 2019-05-22 at 17.27.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/c2ffa8cc-552e-db16-52ca-99e6372e907c.png">

Version 1が作成されました。
<img width="807" alt="Screen Shot 2019-05-22 at 17.28.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/b286cdd7-c740-31c6-8e55-9bf2e2a62fed.png">

コンソールからパラメータを変更するため、エポック数、C、ABEJA PlatformのDataLakeのチャンネルIDを環境変数から取得するようにしています。
また、コンソール上にAccuracyとLossを表示するために、ABEJA Platform SDKのStatisticsを使用しました。

```python:train.py
# import library
import numpy as np
import os
import pandas as pd
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.metrics import log_loss
from sklearn.externals import joblib

from abeja.datalake import Client as DatalakeClient
from abeja.train import Client as TrainClient
from abeja.train.statistics import Statistics as ABEJAStatistics

# define training result dir
ABEJA_TRAINING_RESULT_DIR = os.environ.get('ABEJA_TRAINING_RESULT_DIR', '.')

# define parameters
epochs = int(os.environ.get('NUM_EPOCHS', 1))
c = float(os.environ.get('C', 1))

# define datalake channel_id and file_id
channel_id = os.environ.get('CHANNEL_ID', 'XXXXXXXXXX')


def load_latest_file_from_datalake(channel_id):
    datalake_client = DatalakeClient()
    channel = datalake_client.get_channel(channel_id)

    # load latest file path
    for f in channel.list_files(sort='-uploaded_at'):
        latest_file_path = f.download_url
        latest_file_datetime = f.uploaded_at
        print('load file uploaded at {} (UTC time).'.format(latest_file_datetime))
        break

    return latest_file_path


def handler(context):
    """
    the following csv file should be stored in the datalake channel.
    ---
    sepal_lenght (cm), sepal_width (cm), petal_lenght (cm), petal_width (cm), target
    float, float, float, float, int
    ---
    """
    iris = datasets.load_iris()
    file_path = load_latest_file_from_datalake(channel_id)
    data = pd.read_csv(file_path, sep=',')
    X = data[iris.feature_names].values.astype('float64')
    Y = data['target'].values.astype('int64')
    print('successfully load datalake channel file.')

    # train test split
    X_train, X_test, Y_train, Y_test = train_test_split(X, Y, train_size=0.7)

    # define model
    model = LogisticRegression(solver='lbfgs', C=c,
                               multi_class='multinomial', max_iter=epochs)

    # train model
    model.fit(X_train, Y_train)

    # evaluate model
    train_acc = accuracy_score(Y_train, model.predict(X_train))
    train_loss = log_loss(Y_train, model.predict_proba(X_train))
    valid_acc = accuracy_score(Y_test, model.predict(X_test))
    valid_loss = log_loss(Y_test, model.predict_proba(X_test))

    # update ABEJA statisctics
    train_client = TrainClient()
    statistics = ABEJAStatistics(num_epochs=epochs, epoch=epochs)
    statistics.add_stage(name=ABEJAStatistics.STAGE_TRAIN,
                         accuracy=train_acc,
                         loss=train_loss)
    statistics.add_stage(name=ABEJAStatistics.STAGE_VALIDATION,
                         accuracy=valid_acc,
                         loss=valid_loss)
    train_client.update_statistics(statistics)
    print('Train accuracy is {:.3f}.'.format(train_acc))
    print('Train loss is {:.3f}.'.format(train_loss))
    print('Valid accuracy is {:.3f}.'.format(valid_acc))
    print('Valid loss is {:.3f}.'.format(valid_loss))

    # save model
    joblib.dump(model, os.path.join(ABEJA_TRAINING_RESULT_DIR, 'model.pkl'))

```

### Training Jobの作成
続いて、Jobsをクリックして、Create Training Jobをクリックします。
ファイルが保存されているDataLakeのCHANNEL_IDを、環境変数として設定します。

<img width="729" alt="Screen Shot 2019-05-22 at 17.31.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/5b4e5d30-312c-3f66-b4ed-25e66a8f1dc7.png">

ジョブを作成すると、学習が開始されます。

![ABEJA Platform Console 2019-05-23 22-38-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/cad24b12-a564-9878-7c54-35813e62d027.png)


Accuracyが低いため、パラメータを変更して、再度学習をしてみます。先ほどのCreate Training Jobの画面から、`NUM_EPOCHS`と`C`を指定します。

<img width="728" alt="Screen Shot 2019-05-22 at 17.33.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/222a55bf-c326-2313-2b50-cf87a91c0ccb.png">

Validation Accuracyが93.3%まで上がりました！
指定した環境変数は、Ver.1の右隣のアイコンをクリックして確認することができます。

![ABEJA Platform Console 2019-05-23 22-37-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/634c4f97-2fff-137a-d7d0-6ef0bc1c6581.png)

## モデルをWeb APIとしてデプロイする
### 推論用コードの用意
先ほど学習したモデルをWeb APIとしてデプロイします。
まず、推論用コードを[モデルハンドラー関数の形式](https://developers.abeja.io/organization/model-handler/)で用意します。

```python:predict.py
# prediction
import os
import pandas as pd
import json
import numpy as np
from sklearn.externals import joblib

model = joblib.load(os.path.join(os.environ.get('ABEJA_TRAINING_RESULT_DIR', '.'), 'model.pkl'))


def decode_predictions(result):
    categories = {
        0: 'setosa',
        1: 'versicolor',
        2: 'virginica'
    }
    result_with_labels = [{"label": categories[i], "probability": score} for i, score in enumerate(result)]
    return sorted(result_with_labels, key=lambda x: x['probability'], reverse=True)


def handler(_iter, ctx):
    '''
    _iter: json file
     {"iris": {"sepal_length (cm)": "XX",
               "sepal_width (cm)": "XX",
               "petal_length (cm)": "XX",
               "petal_width (cm)": "XX"}}
    '''
    for iter in _iter:
        sepal_length =iter['iris']['sepal_length (cm)']
        sepal_width = iter['iris']['sepal_width (cm)']
        petal_length = iter['iris']['petal_length (cm)']
        petal_width = iter['iris']['petal_width (cm)']
        x = np.array([[sepal_length, sepal_width, petal_length, petal_width]]).astype('float64')
        result = model.predict_proba(x)[0]
        sorted_result = decode_predictions(result.tolist())
        yield {"result": sorted_result}

```

### モデルの作成
コンソールのModelのCreate Modelをクリックします。Jobと同様に、Runtimeには、使用するイメージを選択して、Handlerには、`ファイル名.ハンドラー関数名`を指定します。また、Deploy after creatingにチェックを入れます。

<img width="830" alt="Screen Shot 2019-05-22 at 17.37.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/d4f17225-2a98-60ac-3ccf-cda7bace73f8.png">

Training Jobから、デプロイしたいモデルが保存されているJobを選択した後、推論用コードをzip圧縮したファイルをドラッグ＆ドロップします。
JobのDescriptionにメモを書いておくと、いい感じに見分けることができます。

![ABEJA Platform Console 2019-05-23 23-18-19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/ca79356f-50d4-9a47-ffcd-668dd66538c6.png)

### モデルのデプロイ
コンソールのDeploymentから、先ほど作成したデプロイメントをクリックします。そしてCreate HTTP Serviceをクリックします。

![ABEJA Platform Console 2019-05-23 23-22-32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/2fc52fb1-a51c-e402-2ae3-8ea6231f3f35.png)

モデルがWeb APIとしてデプロイされました。
![ABEJA Platform Console 2019-05-23 23-13-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/a513e64c-a980-85ec-5168-ed6ef137710a.png)

### モデルのテスト
curlコマンドでエンドポイントにリクエストを送ったり、Checkをクリックして、ブラウザ上でテストを行ったりすることができます。

　<img width="909" alt="Screen Shot 2019-05-22 at 18.05.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/376869/89f5463e-86bf-0805-0a29-f7130e6d0c93.png">

## まとめ
本記事では、ABEJA Platformで、DataLakeにファイルを保存して、scikit-learnを使った機械学習モデルを学習・デプロイする手順をまとめました。ABEJA Platformには、データ蓄積〜モデル学習〜運用のサイクルを回してモデルのバージョンが更新された場合に、Web APIを容易に切り替えることができる機能もあります。詳しくは公式ドキュメントを参照下さい :dog:
https://developers.abeja.io/getting-started-guide/continuous-machine-learning-delivery/redeploy-http-service/

## 参考
サンプルコード
https://github.com/abeja-inc/Platform_handson/tree/master/iris_scikit-learn