Watson Machine Learning上で外部ライブラリ使って予測モデルをデプロイしてみた

# はじめに
Watson Machine Learning（以下WML）には数多くのライブラリを内包（[参照](https://www.ibm.com/docs/en/cloud-paks/cp-data/4.6.x?topic=specifications-supported-frameworks-software)）している非常に便利なデプロイ製品である。
そこで今回は、Cloud Pak for Data as a Service（以下CP4D）プラットフォーム上のWMLに内包されていない外部ライブラリを使って、予測モデルを開発・デプロイメントできるか？を検証をしてみた。
■使用したクラウドサービス
　[CP4D](https://www.ibm.com/jp-ja/products/cloud-pak-for-data)・・・クラウドプラットフォームを使用して迅速にかつ場所を問わずデータを収集・編成・分析し、モデル開発を行い予測などもでき、無料でお試し可能（使い方やサンプルなど「[参考文献](#参考文献)」を参考にしてください）

■使用した外部ライブラリ
　[Catboost](https://catboost.ai/en/docs/concepts/installation)・・・2017年にYandex社から発表された機械学習ライブラリ「勾配ブースティングフレームワーク」
※なお、ここではWMLやCP4Dの使い方は本投稿では割愛する（参考文献を参照してください）
■使用したデータセット
　[Boston dataset](https://scikit-learn.org/stable/datasets/toy_dataset.html#boston-dataset)・・・ボストン住宅価格

:::note warn
警告
・デフォルトで導入済みのライブラリと競合する外部ライブラリを追加するとデプロイ時にエラーが発生
・外部ライブラリの追加手順を実施する前に、導入済みライブラリを確認し、追加したいライブラリが未導入であることを確認
:::

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [Watson Studioを使ってmodel開発](#1-watson_studioを使ってmodel開発)
1. [Watson Machine Learningを使ってデプロイ](#2-watson_machine_learningを使ってデプロイ)
1. [結果](#結果)
1. [参考文献](#参考文献)

<!-- 各チャプター -->
# 1-watson_studioを使ってmodel開発
（１）プロジェクトトークンの挿入
　　　※Notebookのヘッダメニューにある「プロジェクトトークンの挿入」をクリックすると、プロジェクトIDとアクセストークンが自動的に採番され、下記のcellが自動的に追加される。
```
# @hidden_cell
# The project token is an authorization token that is used to access project resources like data sources, connections, and used by platform APIs.
from project_lib import Project
project = Project(project_id='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', project_access_token='p-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
pc = project.project_context
```
（２）外部ライブラリのインストール（今回はCatboostを使用）
```
!pip install catboost
```
（３）modelを作成
```
!"""
推論モデル作成：catboostを使ってボストン住宅価格を予測
"""
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from catboost import CatBoost
from catboost import Pool

boston = load_boston()

df_X = pd.DataFrame(boston.data, columns=boston.feature_names)
df_y = pd.DataFrame(boston.target,columns=['PRICE'])

X_train, X_test, y_train, y_test = train_test_split(df_X, df_y,test_size=0.3)

# CatBoost が扱うデータセットの形式に直す
train_pool = Pool(X_train, y_train)
test_pool = Pool(X_test, y_test)

# 評価関数RMSE（二乗平方平方根誤差）を使ってハイパーパラメータを設定（100セットトレーニング）
params = {'loss_function': 'RMSE', 'num_boost_round': 100}

# 上記のパラメータでモデルを学習する
model = CatBoost(params) #汎用モデル
model.fit(train_pool)

# モデル保存(フォーマットはcbm形式)
# https://catboost.ai/en/docs/concepts/python-reference_catboost_save_model
model.save_model("model.cbm", format="cbm", export_parameters=None, pool=None)
```
（４）IBM Cloud Object Storage（通称：ICOS）にファイルをコピー
　　　※プロジェクトでデフォルト作成されたICOSのバケットにmodelを保管
```
with open('model.cbm','rb') as f:
    project.save_data('model.cbm', f, overwrite=True)```
<a id="#Chapter2"></a>
```
（５）プロジェクトバケットに保存されたモデルをダウンロードし、
　　　あらかじめ作成しておいた別バケットにアップロードしておく。

# 2-watson_machine_learningを使ってデプロイ
（１）APIキーとロケーションを設定しWMLの資格情報を作成
```
api_key = '<PASTE YOUR PLATFORM API KEY HERE>'
location = '<PASTE YOUR INSTANCE LOCATION HERE>'
wml_credentials = {
    "apikey": api_key,
    "url": 'https://' + location + '.ml.cloud.ibm.com'
}
```
（２）watson machine learningライブラリの導入
ここでは、あえてデプロイメントができたWMLのバージョンを指定（ちょっと古いが）
※バージョン指定をしないデフォルトでは、最新のWMLがインストールされるため。
[最新版のWMLはこちら](https://pypi.org/search/?q=ibm-watson-machine-learning)
```
#!pip install -U ibm-watson-machine-learning
!pip install -U ibm-watson-machine-learning==1.0.262 
```
（３）watson machine learningクライアント作成
```
from ibm_watson_machine_learning import APIClient
client = APIClient(wml_credentials)
```
（４）デプロイメント画面で作成したスペースIDを設定
```
space_id = '<PASTE YOUR SPACE ID HERE>'
client.spaces.list(limit=10)
client.set.default_space(space_id)
```
（５）icosライブラリ（ibm_boto3）の導入
```
!pip install ibm-cos-sdk
```
（６）外部ライブラリ（Catboost）の導入
途中、Notebook上で関数の実況状況を確認したければこちらをインストール。
確認したくなければ、最終的にはインストールは（８）のyamlファイルでしているので不要。インストールパッケージ衝突の原因でデプロイメントができなくなる時もあるので、、、
```
!pip install catboost
```
（７）modelをロードする関数を作成
```
"""
ロード関数：バケットに保管してあるcbmファイルをロードする
"""
def deployable_callable():
    try:
        import ibm_boto3
        from ibm_botocore.client import Config
        import os
        from catboost import CatBoost

        # icosバケット使用時に必要なcredencials
        credencials={
          "apikey": "<PASTE apikey HERE>",
          "endpoints": "<PASTE endpoints HERE>",
          "iam_apikey_description": "<PASTE iam_apikey_description HERE>",
          "iam_apikey_name": "<PASTE iam_apikey_name HERE>",
          "iam_role_crn": "<PASTE iam_role_crn HERE>",
          "iam_serviceid_crn": "<PASTE iam_serviceid_crn HERE>",
          "resource_instance_id": "<PASTE resource_instance_id HERE>"
        }
        
        # resourceの作成
        COS_ENDPOINT = "<PASTE apikey HERE>"
        COS_API_KEY_ID = credencials["apikey"] 
        COS_INSTANCE_CRN =  credencials["resource_instance_id"]  
        resource = ibm_boto3.resource("s3",
            ibm_api_key_id=COS_API_KEY_ID,
            ibm_service_instance_id=COS_INSTANCE_CRN,
            config=Config(signature_version="oauth"),
            endpoint_url=COS_ENDPOINT
        )
        
        #Bucketからモデルをダウンロード
        source_bucket = "<PASTE source_bucket HERE>" 
        source_file ="model.cbm"
        local_file ="model.cbm"
        object = resource.Object(source_bucket, source_file)
        object.download_file(local_file)

        #ダウンロード確認
        if(os.path.isfile(local_file) == True):
            print('Download OK')
        else:
            print('Download Failed')
        
        # 空のインスタンス作成
        model = CatBoost()
        model = model.load_model("./model.cbm", format = "cbm") # モデルファイルをロード 

    except ModuleNotFoundError as e:
        print(f"module not installed: {str(e)}")

    """
    Score method.
    """
    def score(payload):
        try:
            data = payload['input_data'][0]['values']
            y_pred = model.predict(data)
            return {'predictions': [{'values': (y_pred)}]}
        except Exception as e:
            return {'predictions': [{'values': [repr(e)]}]}        
    return score

"""
メイン関数
説明：deployable_callable関数内のscore関数に標準入力された値（payload）を入れて予測値を返す
    　ex) deployable_callable()(payload)
"""
import numpy as np

#初期化
data = np.zeros((1, 13))

data_description = deployable_callable()({"input_data": [{"values" : data}]})
print("data_description : ", data_description)
```
（８）拡張パッケージとして外部ライブラリ（Catboost）をyamlに記述
```
config_yml =\
"""name: python39
channels:
  - conda-forge    
  - nodefaults
dependencies:
  - catboost
prefix: /opt/anaconda3/envs/python39
"""
with open("config.yaml", "w", encoding="utf-8") as f:
    f.write(config_yml)```
```
（９）config.yamlファイルを使用して新しいパッケージ拡張を保存
```
base_sw_spec_uid = client.software_specifications.get_uid_by_name("runtime-22.1-py3.9")
meta_prop_pkg_extn = {
    client.package_extensions.ConfigurationMetaNames.NAME: "catboost env",
    client.package_extensions.ConfigurationMetaNames.DESCRIPTION: "Environment with catboost",
    client.package_extensions.ConfigurationMetaNames.TYPE: "conda_yml"
}
pkg_extn_details = client.package_extensions.store(meta_props=meta_prop_pkg_extn, file_path="config.yaml")
pkg_extn_uid = client.package_extensions.get_uid(pkg_extn_details)
pkg_extn_url = client.package_extensions.get_href(pkg_extn_details)
```
（１０）新しいソフトウェア仕様を作成し、作成した拡張パッケージを追加
```
meta_prop_sw_spec = {
    client.software_specifications.ConfigurationMetaNames.NAME: "catboost software_spec",
    client.software_specifications.ConfigurationMetaNames.DESCRIPTION: "Software specification for catboost",
    client.software_specifications.ConfigurationMetaNames.BASE_SOFTWARE_SPECIFICATION: {"guid": base_sw_spec_uid}
}
sw_spec_details = client.software_specifications.store(meta_props=meta_prop_sw_spec)
sw_spec_uid = client.software_specifications.get_uid(sw_spec_details)

client.software_specifications.add_package_extension(sw_spec_uid, pkg_extn_uid)
```
（１１）作成されたソフトウェア仕様の詳細を取得し、関数を保存
```
client.software_specifications.get_details(sw_spec_uid)
meta_props = {
    client.repository.FunctionMetaNames.NAME: "catboost file downloaded model",
    client.repository.FunctionMetaNames.SOFTWARE_SPEC_UID: sw_spec_uid
}
function_details = client.repository.store_function(meta_props=meta_props, function=deployable_callable)

import json
function_uid = client.repository.get_function_uid(function_details)
function_details = client.repository.get_details(function_uid)
print(json.dumps(function_details, indent=2))
client.repository.list_functions()
```
（１２）Python関数のオンラインデプロイメントを作成
```
metadata = {
    client.deployments.ConfigurationMetaNames.NAME: "Deployment of file downloaded model",
    client.deployments.ConfigurationMetaNames.ONLINE: {}
}
created_deployment = client.deployments.create(function_uid, meta_props=metadata)
```
※下記のメッセージが表示されたらデプロイメント成功！
Successfully finished deployment creation, deployment_uid='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

（１３）デプロイメントIDを作成してリストを表示
```
deployment_id = client.deployments.get_uid(created_deployment)
scoring_endpoint = client.deployments.get_scoring_href(created_deployment)
print(scoring_endpoint)
client.deployments.list(limit=10)
```
（１４）モデル検証手順
下記手順に従って、推論モデルを使いボストン住宅価格を予測。
１．マネジメントコンソール画面のナビゲーションメニューから「デプロイメント」をクリック。

２．「スペース」タブをクリックし該当のスペースをクリック。

３．「デプロイメント」タブをクリックし該当のモデルをクリック。

４．下図のように①「テスト」タブをクリックし、②「ローカル・ファイルの参照」リンクをクリックし、テストデータの入ったjsonファイルを選択し、入力ファイルが画面に表示されるので、最後に③右下の「予測」ボタンを押下
![tempsnip1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102468/345c0a70-054e-635b-e248-c3b974adffde.png)
例）説明変数（平均部屋数、犯罪率など13個）
　　テストに使用したjsonファイル内容は下記の通り。
　　{
    　　　"input_data": [
　　　      　  {
      　　　　　      "values": [0.00632, 18.0, 2.31, 0.0, 0.538, 6.575, 65.2, 4.09, 1.0, 296.0, 15.3, 396.9, 4.98]
        　　　　　}
    　　　]
　　}

５．今回作成した推論モデルによる予測値が出力。
例）目的変数（「住宅価格」（1000ドル単位））
![tempsnip2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/102468/aacbd952-d2ae-c5ac-0423-8919e544d05f.png)

※なお、今回は[IBMCloud](https://cloud.ibm.com/)の管理メニューにあるIAMから個人のAPIキーを使用したが、プロジェクトなどでは個人に紐づかない[サービスID](https://qiita.com/c_crisp/items/4c12ee8209283a6e7972)のAPIキーを使用したほうがベスト。

# 結果
WML上で外部ライブラリを使った予測モデルの開発・デプロイメントができた。

# 参考文献
- Catboostの保存方法とロード方法
[Catboostモデルの保存](https://catboost.ai/en/docs/concepts/python-reference_catboost_save_model)
[Catboostモデルのロード](https://catboost.ai/en/docs/concepts/python-reference_catboost_load_model)

- CP4Dの使い方は下記サイトを参考に。
[IBM Cloud Pak for Data as a Serviceを始めてみる](https://qiita.com/Asuka_Saito/items/df3467dc4c9919772c63)

- CP4Dには豊富なサンプル集揃ってるので下記サイトを参考に。
[Watson Machine Learning Python クライアントのサンプルと例](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-samples-overview.html?locale=ja&audience=wdp
)