PythonでつくったモデルをWatson Machine LearningでREST API化（CP4D編）

Cloud pak for Data 3.0.1(以下CP4D) でPythonでつくったモデルをREST API化します。
Watson Stuido Localという開発環境でモデルを作成し、Watson Machine Learningという実行環境にデプロイすることでREST API化することができます。
REST API化するとつくったモデルを外部のアプリケーションから呼び出すことが可能になります。例えば、Webの回遊行動から購入を予測するようなモデルを作れば、スマホのアプリにリアルタイムにその広告をだしたりすることができます。また、装置のセンサーデータから故障を予想するようなモデルを作れば、リアルタイムに故障の可能性があることを通知するようなことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/cbf49441-6e12-c6e0-93e9-e55682c4d091.png)

サポートされているフレームワークは以下になります。scikitlearnやKeras、xgboostなどがサポートされています。
Framework support details
https://www.ibm.com/support/knowledgecenter/en/SSQNUZ_3.0.1/wos/wos-frameworks-wml-cloud.html

■テスト環境
CP4D: 3.0.1
WML Client: 1.0.103
Scikitlearn: 0.22.1

# ①プロジェクト作成
最初にモデルを作成するNotebookを作るためにプロジェクトを作成します。すでにプロジェクトがある場合にはそれをつかっていただいて構いません。その場合は②に進んでください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e7d34de3-b3b5-030b-8bd7-9dfe7fe9053f.png)

CP4Dのメニューからプロジェクトを選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/65660257-3691-8119-5cdb-881872270ff4.png)

新しいプロジェクトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b206ed19-63ba-4df6-e649-d1937ebc846b.png)

分析プロジェクトを選択し、次へをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/c9aee123-3de2-7adb-9387-67652aa10147.png)

空のプロジェクトを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0db89b43-c1fa-a6c8-6a18-f8608ef58e9e.png)

名前を設定して、作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ed1148ec-9e60-bc8d-fbce-016558985c76.png)

#②プロジェクトのDeployment Space連携
Watson Machine LearningでREST API化するためにはWatson Machine Learning内にDeployment Spaceという場所を用意し、そこに作成したモデルを保管する必要があります。ここではWatson Studioのプロジェクトに対応するDeployment Spaceを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/1b8ff8cb-e187-7c3a-805c-39eb8f6170eb.png)

プロジェクトの設定タブに移ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/79ca3de2-af86-7b9e-054b-3a03a6c2244b.png)

デプロイメントスペースの関連付けボタンをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0210e06e-12aa-dea4-3354-2f42153fd8e6.png)

既存のDeployment Spaceがある場合はそれを選択しても構いませんが、ここでは新規に作成し、関連付けをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/383e04d6-a798-a7c5-38fe-44070156820d.png)

以下のように関連付けができたことを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/485c4f85-da4c-f91d-9e20-cfa6b2a71414.png)

注：正確に言えば、デプロイメント・スペースの作成は必須ですが、プロジェクトとデプロイメント・スペースを関連付けること自体は必須ではありません。関連付けられていないデプロイメント・スペースにもモデルを保存することはできます。ただ、関連付けをしておくことでデプロイメント・スペースのuidを見つけやすくすることができ、便利になります。

#③ScikitLearnモデル作成
Watson StudioのNotebookでScikitLearnの予測モデルを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b6a2cd95-01ef-4d8d-07c9-49047501f0ea.png)

資産タブに移り、プロジェクトに追加ボタンをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/29144e80-d9e0-b3eb-26c9-6c58a1223ee7.png)

Notebookをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0a16dc71-e87c-dba1-ef25-7f6a7f3e56a5.png)

「URLから」を選択し、任意の名前を指定します。
ノートブックURLに以下のURLを指定します。
https://raw.githubusercontent.com/hkwd/200701WML/master/cp4d301/iris_WMLCP4D301pub.ipynb
設定ができたら、ノートブックの作成をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/d5883b3e-4d65-ff1e-6867-ebae43decd11.png)

モデル作成のセルを実行してください。
Scikitlearnのライブラリに入っているirisのデータを読み込んでモデルを作っています。modelという変数にモデルが格納されました。

"sepallength"(ガクの長さ), "sepalwidth"(ガクの幅), "petallength"(花弁の長さ), "petalwidth"(花弁の幅)をつかって、アヤメの種類であるSetosa、Virginica、Versicolorのいずれかを判別予測するモデルになっています。predictionが判別結果で、0がSetosa、1がVirginica、2がVersicolorを意味しています。Setosa_prob、Virginica_prob、Versicolor_probの各列はそれぞれのアヤメの種類である確率を意味しています。以下の例の0番目のアヤメで言うと80％の確率でVirginica種であると予測したことになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3b458d44-9f9b-77e3-4ef5-414a78b01f75.png)

#④WML Clientの接続
プロジェクトやデプロイメント・スペースにモデルを格納したり、デプロイしたりするために、NotebookからWatson Machine Learningのクライアント・ライブラリを読み込み、Watson Machine Learningのサービスに接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b48faee2-23a4-d0bd-fcbb-0d4c65f63cd9.png)

Watson Machine Learningのクライアント・ライブラリ（watson-machine-learning-client-V4）はCP4DのPython環境では読み込まれているのですが、バージョンが1.0.95とやや古くCP4D3.0.1に対応できていないので、まず、pipで更新してください。ここではバージョン1.0.103を使いました。

```python
!pip install --upgrade  watson-machine-learning-client-V4
```

以下でWMLクライアントでWatson Machine Learningのサービスに接続します。
clientというオブジェクトでWatson Machine Learningの操作が可能になります。

```python
import sys,os,os.path

token = os.environ['USER_ACCESS_TOKEN']
from watson_machine_learning_client import WatsonMachineLearningAPIClient

wml_credentials = {
   "token": token,
   "instance_id" : "wml_local",
   "url": os.environ['RUNTIME_ENV_APSX_URL'],
   "version": "3.0.1"
}

client = WatsonMachineLearningAPIClient(wml_credentials)
```

#⑤プロジェクトにモデル登録
先ほどインスタンス化したWMLのクライアントであるclientをつかって、プロジェクトに①でつくったirisの予測モデルであるmodelを保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/4164bca7-ce72-94ba-2703-e8de6f1e23ae.png)

まず、set.default_projectでWMLクライアントでプロジェクトにつなぎます。

```python
project_id = os.environ['PROJECT_ID']
client.set.default_project(project_id)
```

次にモデルのメタデータを作っていきます。メタデータにどんなものがあるかはclient.repository.ModelMetaNames.show()で調べることができます。

ここではいくつか主要なものを設定します。
以下は説明変数のスキーマを設定しています。この設定がされているとWatson Machine LearningのGUIでテスト実行をするUIが使えるようになります。
説明変数の入ったpandasのDataFrameであるx_trainから列名とデータ型を取得しています。

```python
x_fields=[{'name': i, 'type': str(v)} for i,v in x_train.dtypes.items()]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/932141da-26bd-a800-c9d4-2d4a2252c5bb.png)

以下は出力結果のスキーマを設定しています。基本的には定型文ですが、目的変数の入ったpandasのDataFrameであるy_trainからデータ型を取得しています。

```python
y_fields=[{'name': 'prediction', 'type': str(y_train.dtypes[0]),'metadata': {'modeling_role': 'prediction'}}]
y_fields.append({'name': 'probability', 'type': 'list','metadata': {'modeling_role': 'probability'}})
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/671075c7-9402-bd58-9770-cf9043ecbc93.png)

その他、NAMEやTYPEなどのメタデータも設定し、ディクショナリにまとめます。なお、scikit learnが0.20ではRUNTIME_UIDを指定していましたが、今回モデルを作成したscikit learnが0.22の場合はRUNTIME_UIDではなくSOFTWARE_SPEC_UIDの指定が必要でした。

```python
model_name = 'iris_skl_model'
#scikit learnが0.22の場合はRUNTIME_UIDではなくSOFTWARE_SPEC_UIDの指定が必要
sw_spec_uid = client.software_specifications.get_uid_by_name("scikit-learn_0.22-py3.6")

#モデルのメタデータを定義
pro_model_meta_props = {
    client.repository.ModelMetaNames.NAME: model_name,
    client.repository.ModelMetaNames.SOFTWARE_SPEC_UID: sw_spec_uid,
    client.repository.ModelMetaNames.TYPE: "scikit-learn_0.22",
    client.repository.ModelMetaNames.INPUT_DATA_SCHEMA:[{
        "id":"input1",
        "fields":x_fields
    }],
    client.repository.ModelMetaNames.OUTPUT_DATA_SCHEMA: {
        "id":"output",
        "fields": y_fields}
}
```

WMLのクライアントのrepository.store_modelのメソッドでプロジェクトの資産としてmodelを保存します。
戻り値の中にIDが含まれているので取っておきます。

```python
#プロジェクトにモデルを保存
stored_pro_model_details = client.repository.store_model(model,
                                               meta_props=pro_model_meta_props,
                                               training_data=x_train,
                                               training_target=y_train)

pro_model_uid=stored_pro_model_details['metadata']['guid']
```

この時点でプロジェクトの資産をみてみるとモデルとして保存されているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ad371581-ce61-9e90-0b89-f88a2dcde602.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ba26ce3a-72fb-b8d7-f2d8-5435b3cc947f.png)

WMLクライアントのrepository.list_models()メソッドでも同様に確認ができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f9733bb2-827b-c5f7-c138-44eaf9c450a9.png)


注：正確に言えば、プロジェクトへのモデル保存は、REST API化するという目的のためには必須ではありませんが、デプロイメント・スペースに保存するモデルをプロジェクトにも保存しておくことで、Watson Studio上でもテストをしたり、GUIでデプロイメント・スペースに保存する（プロモート機能）ことができるようになります。

#⑥デプロイメントスペースにモデルを登録
いよいよmodelをWatson Machine Learning内のデプロイメント・スペースに登録していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/543015d4-4c65-3f99-d699-78754248e061.png)

Watson Data APIというREST APIをつかって②で関連付けたデプロイメント・スペースのIDを取得し、set.default_spaceメソッドで接続します。

```python
#関連付けられたデプロイメント・スペースのIDを取得
import json, requests
# get project info
r = requests.get(os.environ['RUNTIME_ENV_APSX_URL'] + "/v2/projects/" + os.environ['PROJECT_ID'], headers = {"Authorization": "Bearer " + os.environ['USER_ACCESS_TOKEN']})
wmlProjectCompute = [c for c in r.json()["entity"]["compute"] if c["type"] == "machine_learning"][0]
space_uid = wmlProjectCompute["properties"]["space_guid"]
print(space_uid)

#デプロイメントスペースに接続
client.set.default_space(space_uid)
```
- 参考
 - Watson Data API (Beta) - IBM Cloud API Docs https://cloud.ibm.com/apidocs/watson-data-api#get-project

先ほど⑤で作成したモデルのメタデータに加えて、上で取得したデプロイメント・スペースのuidを設定します。

```python
# モデルのメタデータにデプロイメントスペースのIDを追加
ds_model_meta_props=pro_model_meta_props
ds_model_meta_props[client.repository.ModelMetaNames.SPACE_UID]= space_uid
```
先ほどの⑤と同様にrepository.store_modelでmodelを保存し、戻り値からモデルのuidを取得します。

```python
#デプロイメントスペースにモデルを保存
stored_ds_model_details = client.repository.store_model(model,
                                               meta_props=ds_model_meta_props,
                                               training_data=x_train,
                                               training_target=y_train)
ds_model_uid = stored_ds_model_details["metadata"]["guid"]
```

これでデプロイメント・スペースにもモデルが保存されました。
分析デプロイメントを開いてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3a0b7418-b738-6ffa-1b85-e8b34f45013b.png)

②でプロジェクトと関連付けたデプロイメント・スペースをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/da12086d-d18c-9aa4-0a41-c5f8a0ff9df9.png)

以下のようにモデルが登録されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/5aa3728a-ea72-5009-fbc4-db5d35a8293c.png)

#⑦モデルをオンラインスコアリングとしてデプロイ
デプロイメント・スペースに保存したモデルをデプロイしてREST API化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ee96e21f-8cd4-7ad7-4013-52ad94d97a6f.png)

分析デプロイメントのGUIでもデプロイは可能ですが、ここではWMLクライアントのAPIで行ってみます。

まずデプロイメントのメタデータを定義します。
client.deployments.ConfigurationMetaNames.get()でどんなメタデータがあるかは確認ができます。
ここではNAMEとONLINEを指定します。
ONLINEを指定するとREST APIで説明変数を入力すると、リアルタイムに予測結果の目的変数を返すことができます。
その他にもBATCHなどのデプロイ方法があります。

```python
deployment_on_name = 'iris_skl_model_rt'
# オンラインスコアリングのメタデータ定義
deploy_on_meta_props = {
    client.deployments.ConfigurationMetaNames.NAME: deployment_on_name,
    client.deployments.ConfigurationMetaNames.ONLINE: {}
}
```

- 参考
 - https://www.ibm.com/support/knowledgecenter/en/SSQNUZ_3.0.1/wsj/wmls/deploy-models.html

deployments.createメソッドでモデルをデプロイします。artifact_uidにはデプロイメントスペースに登録したモデルのuidを指定します。プロジェクトに保存したモデルのuidではないことに注意してください。

```python
# モデルのデプロイ
deployment_on_details = client.deployments.create(
    artifact_uid=ds_model_uid, 
    meta_props=deploy_on_meta_props)
```
少し時間がかかり、以下のようなメッセージがでて、モデルがデプロイされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/373cc2f7-779f-cdc6-23b3-44dc86d017ab.png)

戻り値にデプロイメントのuidが含まれるのでそれを取得します。

```python
deployment_on_uid = client.deployments.get_uid(deployment_on_details)
```
分析デプロイメントに移って、デプロイされていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/85a4663b-fe39-d609-bde1-da093994145a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/1c3a1fce-3831-7151-81eb-c7dc807132b7.png)

先ほど登録したモデルをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/90bcf3a9-f382-d9b2-d6cf-d0b96b336f63.png)

以下のようにオンラインタイプとしてデプロイされていることがわかります。
デプロイメントをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7e78bdf2-cd5d-dda5-bc02-2f0f2bbb737a.png)

このデプロイメントのREST APIのエンドポイントURLや様々な言語でのサンプルコードが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7a65d255-a3c3-25bd-d655-ae24fd37f04d.png)

#⑧スコアリング

先ほどのサンプルコードなどを使って、以下の図のように外部のアプリケーションからスコアリングするのが本来の使い方ですが、ここではテストとして、WMLクライアントをつかってNotebookからスコアリングしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0b37b34b-dc2c-684e-c8aa-8e16be9a2230.png)

まずインプットデータである説明変数をテストデータのpandasから作ります。ここではx_test[0:2]で先頭から2行分を抜き出しています。

```python
payload = {
    client.deployments.ScoringMetaNames.INPUT_DATA:[{
        "fields": x_test.columns.tolist(),
        "values": x_test[0:2].values.tolist()
    }]
}
```

以下のようなデータをインプットデータとして作りました。
"sepallength"(ガクの長さ), "sepalwidth"(ガクの幅), "petallength"(花弁の長さ), "petalwidth"(花弁の幅)という説明変数名とその値を[4.5, 2.3, 1.3, 0.3], [6.0, 3.4, 4.5, 1.6]という2件分与えています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ba681a1a-f022-f031-f4f0-42b0d564f603.png)


スコアリングはdeployments.scoreのメソッドで行います。デプロイメントのuidとインプットデータを与えます。

```python
predict=client.deployments.score(deployment_on_uid, payload )
}
```
以下の結果が返ってきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0e398023-f4c6-e89a-5d91-050c0fa1c3f9.png)

1件目の結果は[1, [0.0, 0.99, 0.01]]です。まずpredictionとして1が返っていますのでVirginicaと予測しています。次のリスト[0.0, 0.99, 0.01]はSetosa_prob、Virginica_prob、Versicolor_probです。Virginicaが99%の確率だと予測しています。同様に2件目は100%の確率でSentosaだと予測しています。

#まとめ
PythonのScikitLearnでつくったモデルがREST APIになり、アプリケーションから利用できるようになりました。

完成したNotebookはこちらです。
https://github.com/hkwd/200701WML/blob/master/cp4d301/iris_WMLCP4D301pub.ipynb


#参考

PythonでのCloud Pak for Dataのオブジェクト操作例 (WML client, project_lib) - Qiita https://qiita.com/ttsuzuku/items/eac3e4bedc020da93bc1