IBM Cloudで機械学習/深層学習モデルを開発・運用するプログラム集

この記事は、IBM Cloudで機械学習/深層学習モデル(AIモデル)を開発・運用するためのPythonプログラムについて記載しています。

## 概要

IBM CloudでAIモデルを開発・運用するためのステップの一例として、以下が挙げられます。

![ws_wml_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/aac0b593-c040-c31f-cf22-86f52f41fda7.png)

1. AIモデルの開発はIBM CloudのWatson Studioサービスで行い、AIモデルの管理はIBM CloudのWatson Machine Learningサービスで行うため、これらのサービスを作成します
2. AIモデルを開発するためにプロジェクトを作成します
3. 作成したプロジェクトで、Jupyterノートブック・エディターを使用してAIモデルの開発を行います
4. AIモデルの学習に必要なデータはデータベースやオブジェクト・ストレージなどから取得します。それらのデータ・ソースへの接続情報は、プロジェクトに定義します
5. 作成したAIモデルをプロジェクトに保存します
6. AIモデルを管理するために、デプロイメント・スペースを作成します
7. プロジェクトに保存されたAIモデルをデプロイメント・スペースに取り込みます(プロモート)
8. デプロイメント・スペースにプロモートされたAIモデルを同じデプロイメント・スペース上にデプロイします。デプロイすると、そのAIモデルに対してREST APIを使って推論できるようになります
9. REST APIで、デプロイされたAIモデルを使って推論します

この記事では、上記、4, 5, 7, 8, 9 について、Pythonでどのように実装するのかについて述べます。7と8はIBM CloudのWeb画面を使って実現可能ですが、Pythonのプログラムでできるようにしておくと自動化に利用できて便利です。
Pythonのライブラリは、[ibm-watson-machine-learing](https://ibm.github.io/watson-machine-learning-sdk/) を使用します。
AIモデルの例として、scikit-learnを使った機械学習モデル(住宅ローンの許可/却下の分類)、Kerasを使った深層学習モデル(画像の分類)、PyTorchを使った深層学習モデル(画像の分類)を取り上げます。

## 1. ベースとなるAPIClientオブジェクトの作成
Pythonのライブラリ `ibm-watson-machine-learning` で各種処理を行うためには、ベースとなるAPIClientオブジェクトを作成する必要があります。
この作成時に認証も行いますが、IBM CloudのSaaS環境 (Cloud Pak for Data as a Service) か、オンプレ環境 (Cloud Pak for Data) かで、やり方が異なります。

### 1.1. SaaS環境 (Cloud Pak for Data as a Service)
オブジェクトを作成する際の認証情報として、APIキーとSaaS環境のURLが必要となります。APIキーは、IBM CloudのWeb画面を使用して作成することができます。
IBM Cloudにログインし、管理 > アクセス(IAM) を選択

![apikey_saas_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/f6598406-8ebc-fec2-bf26-4175676f370d.png)

画面左メニューの APIキー を選択し、画面右にある作成ボタンを選択してAPIキーを作成

![apikey_saas_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/79ec28ef-10b3-8ebf-efb8-69f88ecabf11.png)

SaaS環境のURLは、https://[ロケーション].ml.cloud.ibm.com で、ロケーション情報は、IBM Cloud の Jupyter ノートブック・エディターを使用する場合、`RUNTIME_ENV_REGION` 環境変数から得られます。
APIClientオブジェクトを作成するプログラムは以下になります。
```python
import os
from ibm_watson_machine_learning import APIClient

location = os.environ['RUNTIME_ENV_REGION'] 
apikey = "[APIキー]"
wml_credentials = {
    "apikey": apikey,
    "url": 'https://' + location + '.ml.cloud.ibm.com'
}
wml_client = APIClient(wml_credentials)
```

### 1.2. オンプレ環境 (Cloud Pak for Data)
オンプレでAPIClientオブジェクトを作成するためには、url, username, apikey, instance_id, version が必要となります。
- URLは、https://[Cloud Pak for Data のホスト名]
- usernameは、Cloud Pak for Data のユーザー名
- apikeyは、上記ユーザーのAPIキー。上記ユーザーでCloud Pak for Data にログインしたWeb画面で、上部のメニュー右から2つ目のアイコンをクリックし、「プロファイルと設定」を選択。切り替わった画面右にある「API鍵」をクリックして「新規鍵の生成」を選択するとAPIキーを作成することができます

 ![apikey_private_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/86565b6f-a281-7c27-0d6a-a1f5ec6568c7.png)

![apikey_private_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/b7316dbc-44d6-b5de-2228-f46e0e0d3dab.png)
  
- instance_idは、**openshift**
- versionは、Cloud Pak for Dataのバージョン(例: "5.0")

APIClientオブジェクトを作成するプログラムは以下になります。
```python
cp4d_url = "https://[Cloud Pak for Data のホスト名]"
cp4d_user = "[ユーザー名]"
cp4d_apikey = "[APIキー]"
cp4d_version = "Cloud Pak for Data のバージョン(例: 5.0)"
wml_credentials = {
    "url": cp4d_url,
    "username": cp4d_user,
    "apikey": cp4d_apikey,
    "instance_id": "openshift",
    "version": cp4d_version
}
wml_client = APIClient(wml_credentials)
```

以降、生成したAPIClientオブジェクトの変数名を`wml_client`として説明します。

## 2. 作業する対象環境(特定のプロジェクトまたはデプロイメント・スペース)を指定
プロジェクトにAIモデルを保存したり、プロジェクトのAIモデルをデプロイメント・スペースにプロモートする場合、プロジェクトIDを以下のようにセットします。
```python
project_id = "[プロジェクトID]"
wml_client.set.default_project(project_id)
```
プロジェクトIDは、プロジェクトの「管理」タブの「一般」に表示されています。

![project_id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/e7140748-72eb-0fea-f149-ba32bda6355c.png)

デプロイメント・スペースにプロモートされたAIモデルをデプロイする場合、デプロイメント・スペースのID(スペースID)を以下のようにセットします。
```python
space_id="[スペースID]"
wml_client.set.default_space(space_id)
```
スペースIDは、以下のプログラムで、デプロイメント・スペース名から特定することができます。
```python
space_name = "[デプロイメント・スペース名]"
space_id = None
for space in wml_client.spaces.get_details()['resources']:
    # print(space['metadata']['id'], space['entity']['name'], space['metadata']['created_at'])
    if space['entity']['name'] == space_name:
        space_id = space['metadata']['id']
        break
if space_id:
    print(f'Deployment space name: "{space_name}", Deployment space ID: "{space_id}"')
else:
    emsg = f'Deployment space name "{space_name}" not found.'
    raise Exception(emsg)
```
IBM CloudのWeb画面からも得ることができ、デプロイメント・スペースの「管理」タブの「一般」に表示されています(`スペースGUID`)。

![space_id.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/bf4575c3-6d21-b10a-d0e3-5f18175f918b.png)

## 3. AIモデル開発に使用するデータ・アクセス

### 3.1. Db2データベースに保存されたデータ取得

プロジェクトに保存したDb2接続情報の取得とSELECT文の結果をPandasのデータフレームに保存するプログラム例を以下に述べます。
```python
import ibm_db_dbi
import pandas as pd

wml_client.set.default_project(project_id)

db2_name = "Db2 Mortgage Approval Data"    # Db2接続情報名
db2_info = None
for detail in wml_client.connections.get_details()["resources"]:
    if detail["entity"]["name"] == db2_name:
        db2_info = wml_client.connections.get_details(detail["metadata"]["id"])["entity"]["properties"]
print(f"db2_info={db2_info}")
con_url = f'DATABASE={db2_info["database"]};HOSTNAME={db2_info["host"]};PORT={db2_info["port"]};PROTOCOL=TCPIP;UID={db2_info["username"]};PWD={db2_info["password"]}'
if db2_info["ssl"] == "true":
    con_url = con_url + ";Security=SSL"
print(f"con_url={con_url}")
con = ibm_db_dbi.connect(con_url,'','')
sql_query = "select * from AI_MORTGAGE.MORTGAGE_APPROVAL_VIEW"
df_mortgage = pd.read_sql_query(sql_query, con)
df_mortgage.head()
```
画面出力例
```
db2_info={'database': 'BLUDB', 'auth_method': 'username_password', 'password': 'xxx', 'port': '50001', 'host': 'xxx.us-south.db2w.cloud.ibm.com', 'ssl': 'true', 'username': 'xxx'}

ID 	NAME 	STREET_ADDRESS 	CITY 	STATE 	STATE_CODE 	ZIP_CODE 	EMAIL_ADDRESS 	PHONE_NUMBER 	GENDER 	... 	NUMBER_OF_CARDS 	CREDITCARD_DEBT 	LOANS 	LOAN_AMOUNT 	CREDIT_SCORE 	COMMERCIAL_CLIENT 	COMM_FRAUD_INV 	PROPERTY_VALUE 	AREA_AVG_PRICE 	MORTGAGE_APPROVAL
0 	110424 	Adah Marks 	4355-5 Pelham South 	Park Ridge 	Illinois 	IL 	41227 	Adah.Marks@donny.ca 	206-220-3128 	Male 	... 	2 	1168.0 	1 	7478.0 	547 	FALSE 	FALSE 	548618.0 	NaN 	0
1 	110425 	Jerrell Eichmann 	5642-1 Hwy 78 East 	Reston 	Virginia 	VA 	41198 	Jerrell.Eichmann@trycia.biz 	972-510-7295 	Male 	... 	2 	1167.0 	1 	7476.0 	547 	TRUE 	FALSE 	548638.0 	NaN 	0
2 	110426 	Glennie Barrows 	6736-1 Whitfield Ave 	Houston 	Texas 	TX 	41169 	Glennie.Barrows@elwin.us 	609-564-9894 	Female 	... 	2 	1166.0 	1 	7474.0 	547 	FALSE 	FALSE 	548658.0 	NaN 	0
3 	110427 	Susanna Schaefer 	4529-0 Madison Blvd 	Woodridge 	Illinois 	IL 	41140 	Susanna.Schaefer@elta.ca 	484-261-6798 	Female 	... 	2 	1165.0 	1 	7471.0 	547 	FALSE 	FALSE 	548678.0 	NaN 	0
4 	110428 	Jettie Waters 	3698-Kelley Blvd 	Truckee 	California 	CA 	41110 	Jettie.Waters@kacie.co.uk 	253-973-4793 	Male 	... 	2 	1164.0 	1 	7469.0 	546 	TRUE 	FALSE 	548698.0 	721244.0 	0

5 rows × 29 columns
```

### 3.2. IBM Cloud Object Storage (ICOS) に保存されたデータ取得

認証方法を「リソース・インスタンスIDとAPIキー」として登録した場合のプログラム例を以下に示します。
```python
import ibm_boto3
from ibm_botocore.client import Config

wml_client.set.default_project(project_id)

icos_name = "icos1"    # ICOS接続情報名
bucket_name = "cloud-object-storage-mortgage-2021-jp"
file_name = "mortgage_approval_view.csv"
icos_info = None
for detail in wml_client.connections.get_details()["resources"]:
    if detail["entity"]["name"] == icos_name:
        icos_info = wml_client.connections.get_details(detail["metadata"]["id"])["entity"]["properties"]
print(f"icos_info={icos_info}")
if not icos_info["url"].startswith("http"):
    icos_info["url"] = "https://" + icos_info["url"]
cos = ibm_boto3.client(service_name="s3",
    ibm_api_key_id=icos_info["api_key"],
    ibm_service_instance_id=icos_info["resource_instance_id"],
    ibm_auth_endpoint='https://iam.bluemix.net/oidc/token',
    config=Config(signature_version='oauth'),
    endpoint_url=icos_info["url"])
cos.download_file(Bucket=bucket_name, Key=file_name, Filename=file_name)
!ls -l $file_name
```
画面出力例
```
icos_info={'trust_all_ssl_cert': 'false', 'auth_method': 'instanceid_apikey', 'iam_url': 'https://iam.cloud.ibm.com/identity/token', 'api_key': 'xxx', 'resource_instance_id': 'crn:v1:bluemix:public:cloud-object-storage:global:xxx', 'url': 's3.us-south.cloud-object-storage.appdomain.cloud'}
-rw-rw---- 1 wsuser wscommon 1281702 Jan 25 05:42 mortgage_approval_view.csv
```

Jupyter ノートブックのファイルシステムにあるファイルをICOSにアップロードするには、`upload_file` メソッドをコールします。引数は、上記の例で使用している `download_file` メソッドと同じです。

## 4. AIモデルの保存

### 4.1. scikit-learnを使った機械学習モデルの保存
scikit-leanのpiplelineを使った機械学習モデルを例として述べます。
機械学習モデルを開発するプログラムの詳細は省略しますが、その中で、モデルの保存に必要な情報は以下になります。
```python
target_col='MORTGAGE_APPROVAL'   # 推論結果を表す列
X = df_mortgage.drop([target_col], axis=1)
model = Pipeline(steps=[('preprocessor', preprocessor),
    ('model', RandomForestClassifier(n_estimators=top_ne, max_depth=top_md, max_features=top_mf, random_state=0))])
```
AIモデルを新規保存またはバージョンアップするプログラムの例は以下になります。AIモデルの保存は `wml_client.repository.store_model` メソッドを利用します。このメソッドでAIモデルを保存すると、同じ名前でもIDの異なる新規のAIモデルが作成されてしまいます。同じ名前の場合は、`wml_client.repository.update_model` メソッドを使ってバージョンアップするのがお勧めです。
```python
model_name = "Mortgage Approval Model"
# AIモデルが既に保存されているか確認
models = wml_client.repository.get_model_details()['resources']
model_ids = [m['metadata']['id'] for m in models if m['metadata']['name'] == model_name]
model_id = None
if len(model_ids) > 1:
    emsg = 'Multiple models with the same model name "{}" exist: {}'.format(model_name, model_ids)
    raise KeyError(emsg)
elif len(model_ids) == 1:
    model_id = model_ids[0]
print(f"existing model_id: {model_id}")
# AIモデルが保存されていなかったら新規作成、保存されていたらバージョンアップ
if model_id == None:
    # 実行環境情報のセット
    software_spec_uid = wml_client.software_specifications.get_id_by_name("runtime-24.1-py3.11")
    print(software_spec_uid)
    # training_data_references情報のセット
    db2_name = "Db2 Mortgage Approval Data"    # Db2接続情報名
    connection_id = None
    for detail in wml_client.connections.get_details()["resources"]:
        if detail["entity"]["name"] == db2_name:
            connection_id = detail["metadata"]["id"]
    sql_query = "select * from AI_MORTGAGE.MORTGAGE_APPROVAL_VIEW"
    training_data_references = [{
        "id": "Mortgage_data",
        "type": "connection_asset",
        "connection": {
            "id": connection_id,
        },
        "location": {
            "select_statement": sql_query,
            "table_name": "Mortgage_Approval_view"
        }
    }]
    # モデル・プロパティ値のセット
    model_props = {
        wml_client._models.ConfigurationMetaNames.NAME:model_name,
        wml_client._models.ConfigurationMetaNames.TYPE: "scikit-learn_1.3",
        wml_client._models.ConfigurationMetaNames.SOFTWARE_SPEC_UID: software_spec_uid,
        wml_client._models.ConfigurationMetaNames.LABEL_FIELD:target_col,
        wml_client._models.ConfigurationMetaNames.INPUT_DATA_SCHEMA:[{'id': '1', 'type': 'struct', 'fields': [{"name":column_name,"type":str(column_type[0])} for column_name,column_type in pd.DataFrame(X.dtypes).T.to_dict('list').items()]}],
        wml_client.repository.ModelMetaNames.TRAINING_DATA_REFERENCES: training_data_references
    }
    # モデルをプロジェクトに保存
    model_detail = wml_client.repository.store_model(model=model, meta_props=model_props, 
        training_data=X_train, training_target=y_train)
    print(f'The model "{model_name}" successfully stored in the project.')
else:
    model_props = {
        wml_client._models.ConfigurationMetaNames.NAME:model_name
    }
    # モデルの更新(バージョン・アップ)
    model_detail = wml_client.repository.update_model(
        model_uid=model_id,
        updated_meta_props=model_props,
        update_model=model
    )
    print(f'The model "{model_name}" successfully updated in the project.')
model_id = wml_client.repository.get_model_id(model_detail)
new_model_revision = wml_client.repository.create_model_revision(model_id)
rev_id = new_model_revision["metadata"].get("rev")
print(f"Model ID: {model_id}")
print(f"Revison ID: {rev_id}")
```

### 4.2. Keras を使った深層学習モデルの保存

AIモデルのバージョンアップについては [4.1. scikit-learnを使った機械学習モデルの保存](##41-scikit-learnを使った機械学習モデルの保存) と同じようにできるので、ここでは、新規保存についてのみ述べます。
以下のモデルを例に述べます。
```python
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, Activation

def cnn_model(x_train, class_labels_count):
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding="same", input_shape=x_train.shape[1:]))
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3))) 
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    model.add(Conv2D(64, (3, 3), padding='same'))
    model.add(Activation('relu'))
    model.add(Conv2D(64, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(class_labels_count))
    model.add(Activation('softmax'))

    model.compile(
        loss='categorical_crossentropy',
        optimizer='adam',
        metrics=['accuracy']
    )
    return model
```
モデルの保存は以下のようになります。
```python
h5file = 'mnist-keras.h5'
gzfile = 'mnist-keras.tar.gz'
model.save(h5file)
import tarfile
with tarfile.open(gzfile, 'w:gz') as tf:
    tf.add(h5file)

model_name = "mnist-keras"
software_spec_uid = wml_client.software_specifications.get_uid_by_name("runtime-24.1-py3.11")
model_props = {
    wml_client._models.ConfigurationMetaNames.NAME:model_name,
    wml_client._models.ConfigurationMetaNames.TYPE: "tensorflow_2.14",
    wml_client._models.ConfigurationMetaNames.SOFTWARE_SPEC_UID: software_spec_uid,
}
model_detail = wml_client.repository.store_model(model=gzfile, meta_props=model_props)
model_id = wml_client.repository.get_model_id(model_detail)
new_model_revision = wml_client.repository.create_model_revision(model_id)
rev_id = new_model_revision["metadata"].get("rev")
print(f"Model ID: {model_id}")
print(f"Revison ID: {rev_id}")
```

### 4.3. PyTorch を使った深層学習モデルの保存

こちらも、AIモデルのバージョンアップについては [4.1. scikit-learnを使った機械学習モデルの保存](##41-scikit-learnを使った機械学習モデルの保存) と同じようにできるので、ここでは、新規保存についてのみ述べます。
以下のモデルを例に述べます。
```python
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms

class cifar10_cnn(nn.Module):
  def __init__(self, num_classes):
    super(cifar10_cnn,self).__init__()
    self.conv1 = nn.Conv2d(3, 32, 3, padding=(1,1), padding_mode='replicate')
    self.conv2 = nn.Conv2d(32, 32, 3)
    self.conv3 = nn.Conv2d(32, 64, 3, padding=(1,1), padding_mode='replicate')
    self.conv4 = nn.Conv2d(64, 64, 3)
    self.relu = nn.ReLU(inplace=True)
    self.dropout1 = nn.Dropout(0.25)
    self.dropout2 = nn.Dropout(0.5)
    self.maxpool = nn.MaxPool2d((2,2))
    self.classifier1 = nn.Linear(2304, 512)
    self.classifier2 = nn.Linear(512, num_classes)

    self.features = nn.Sequential(
        self.conv1,
        self.relu,
        self.conv2,
        self.relu,
        self.maxpool,
        self.dropout1,
        self.conv3,
        self.relu,
        self.conv4,
        self.relu,
        self.dropout1,
        self.maxpool)
    
    self.classifier = nn.Sequential(
       self.classifier1,
       self.relu,
       self.dropout2,
       self.classifier2)

  def forward(self, x):
    x = self.features(x)
    x = x.view(x.size(0), -1)
    x = self.classifier(x)
    return x
```
モデルの保存は以下のようになります。
```python
# ダミーデータの作成
dummy_input = torch.randn((1, 3, 32, 32)).to(device)

# onyx形式でexport
torch.onnx.export(net, dummy_input, "mnist-pytorch.onnx", 
                  keep_initializers_as_inputs=True, verbose=True, opset_version=9)

# gz形式に圧縮
!tar czvf mnist-pytorch.gz mnist-pytorch.onnx

gzfile = "mnist-pytorch.gz"
model_name = "mnist-pytorch"
software_spec_uid = wml_client.software_specifications.get_uid_by_name("runtime-24.1-py3.11")
model_props = {
    wml_client._models.ConfigurationMetaNames.NAME:model_name,
    wml_client._models.ConfigurationMetaNames.TYPE: "pytorch-onnx_2.1",
    wml_client._models.ConfigurationMetaNames.SOFTWARE_SPEC_UID: software_spec_uid,
}
model_detail = wml_client.repository.store_model(model=gzfile, meta_props=model_props)
model_id = wml_client.repository.get_model_id(model_detail)
new_model_revision = wml_client.repository.create_model_revision(model_id)
rev_id = new_model_revision["metadata"].get("rev")
print(f"Model ID: {model_id}")
print(f"Revison ID: {rev_id}")
```
:::note warn
torch.onnx.exportメソッドの引数 `opset_version` を指定しないと、モデルのデプロイでエラーとなるようです。
:::

## 5. AIモデルのプロモート

以下のように行います。
```python
# wml_clientにプロジェクトIDのセット
project_id = "[プロジェクトID]"
wml_client.set.default_project(project_id)

# 以下に記載の [モデル名] を、以下に記載の [デプロイメント・スペース名] にプロモート
model_name = "[モデル名]"
space_name = "[デプロイメント・スペース名]"

# プロジェクトに保存されているモデルIDの取得
entities = wml_client.repository.get_details()['models']['resources']
model_id = None
for entity in entities:
    name = entity['metadata']['name']
    if name == model_name:
        model_id = entity['metadata']['id']
        break
if model_id:
    print(f'Model name: "{model_name}", Model ID: "{model_id}"')
else:
    emsg = f'Model name "{model_name}" not found.'
    raise Exception(emsg)

# プロモート先となるデプロイメントスペースIDの取得
space_id = None
for space in wml_client.spaces.get_details()['resources']:
    # print(space['metadata']['id'], space['entity']['name'], space['metadata']['created_at'])
    if space['entity']['name'] == space_name:
        space_id = space['metadata']['id']
        break
if space_id:
    print(f'Deployment space name: "{space_name}", Deployment space ID: "{space_id}"')
else:
    emsg = f'Deployment space name "{space_name}" not found.'
    raise Exception(emsg)

# モデルのプロモート
if model_id and space_id:
    space_model_id = wml_client.spaces.promote(asset_id=model_id, source_project_id=project_id, target_space_id=space_id)
    print(f'Model asset ID in deployment space "{space_name}": "{space_model_id}"')
```

## 6. モデルのデプロイ

以下のように行います。
```python
# 以下に記載の [デプロイメント・スペース名] にプロモートされている [モデル名] をデプロイ 
space_name = "[デプロイメント・スペース名]"
space_model_name = "[モデル名]"

# デプロイメントスペースIDの取得
space_id = None
for space in wml_client.spaces.get_details()['resources']:
    # print(space['metadata']['id'], space['entity']['name'], space['metadata']['created_at'])
    if space['entity']['name'] == space_name:
        space_id = space['metadata']['id']
        break
if space_id:
    print(f'Deployment space name: "{space_name}", Deployment space ID: "{space_id}"')
else:
    emsg = f'Deployment space name "{space_name}" not found.'
    raise Exception(emsg)

# wml_clientにスペースIDのセット
wml_client.set.default_space(space_id)

# モデルIDの取得
entities = wml_client.repository.get_details()["models"]["resources"]
space_model_id = None
for entity in entities:
    name = entity['metadata']['name']
    if name == space_model_name:
        space_model_id = entity['metadata']['id']
        break
if space_model_id:
    print(f'Space Model name: "{space_model_name}", Space Model ID: "{space_model_id}"')
else:
    emsg = f'Space Model name "{space_model_name}" not found.'
    raise Exception(emsg)

# モデルのデプロイ
meta_props={
    wml_client.deployments.ConfigurationMetaNames.NAME: space_model_deployment_name,
    wml_client.deployments.ConfigurationMetaNames.ONLINE: {}
}

if space_id and space_model_id:
    deployment_id = wml_client.deployments.create(artifact_uid=space_model_id, meta_props=meta_props, target_space_id=space_id)
    space_model_deployment_id = deployment_id['entity']['asset']['id']
    print(f'Deployment name: "{space_model_deployment_name}", Deployment  ID: "{space_model_deployment_id}"')
```

## 7. デプロイされたAIモデルの利用

SaaS環境 (Cloud Pak for Data as a Service) も、オンプレ環境 (Cloud Pak for Data) も、デプロイされたモデルをWeb画面で開くと、`APIリファレンス`タブがあり、REST APIで推論要求するためのプログラムの断片(コード・スニペット)が記載されています。

SaaS環境の例

![code_snippet_ml_saas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/be966cf2-c18a-823e-01e1-99042be3f88f.png)

オンプレ環境の例

![code_snippet_ml.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/9557a762-b4c5-08cf-52a1-5314634277eb.png)

SaaS環境とオンプレ環境の違いは認証方法のみで、それ以外は呼び出し先のURLが異なるだけで、プログラムは同じです。

### 7.1. 認証方法

#### 7.1.1. SaaS環境 (Cloud Pak for Data as a Service) の認証方法

コード・スニペットに記載の通り、以下のようにプログラムします。
```python
API_KEY = "[APIキー]"
token_response = requests.post('https://iam.cloud.ibm.com/identity/token', data={"apikey":
 API_KEY, "grant_type": 'urn:ibm:params:oauth:grant-type:apikey'})
mltoken = token_response.json()["access_token"]

header = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + mltoken}
```

#### 7.1.2. オンプレ環境 (Cloud Pak for Data)

認証情報として、ログインしているユーザー名とそのAPIキーを利用して以下を実行します。
```shell
$ echo "[ユーザー名]:[APIキー]" | base64
```
ユーザー名を `cpadmin` 、APIキーを `ABCDE12345` とすると、以下のようになります。
```shell
$ echo "cpadmin:ABCDE12345" | base64
Y3BhZG1pbjpBQkNERTEyMzQ1Cg==
```
上記の値をmltokenにセットして、headerを以下のようにします。
```python
mltoken = "Y3BhZG1pbjpBQkNERTEyMzQ1Cg=="
header = {'Content-Type': 'application/json', 'Authorization': 'ZenApiKey ' + mltoken}
```

### 7.2. モデルの推論

headerは、上記で述べたようにSaaS環境とオンプレ環境で異なります。ここでは、headerがセットされた前提で、それ以降のプログラムを、SaaSのURLを用いて説明します。
SaaSのURLは、プライベート・エンドポイントとパブリック・エンドポイントがあり、スニペットはプライベート・エンドポイントが記載されています。Watson Studio の Jupyter ノードブック等、IBM Cloud内からのコールの場合、プライベート・エンドポイントを利用します。IBM Cloud外からのアクセスの場合は、パブリック・エンドポイントに変更します。
以下の3つのモデル(scikit-learn機械学習モデル、Keras深層学習モデル、PyTorch深層学習モデル)の例では、IBM Cloud外からパブリック・エンドポイントに変更して実行しています。

#### 7.2.1. scikit-learn機械学習モデル

プログラム
```python
payload_scoring = {
  "input_data": [
    {
      "fields": [
        "INCOME",
        "YRS_AT_CURRENT_ADDRESS",
        "YRS_WITH_CURRENT_EMPLOYER",
        "NUMBER_OF_CARDS",
        "CREDITCARD_DEBT",
        "LOAN_AMOUNT",
        "CREDIT_SCORE",
        "PROPERTY_VALUE",
        "AREA_AVG_PRICE",
        "LOANS",
        "GENDER",
        "EDUCATION",
        "EMPLOYMENT_STATUS",
        "MARITAL_STATUS",
        "APPLIEDONLINE",
        "RESIDENCE",
        "COMMERCIAL_CLIENT",
        "COMM_FRAUD_INV"
      ],
      "values": [
        [
          53115,
          12,
          1,
          2,
          674,
          10455,
          539,
          538588,
          528267,
          0,
          "Female",
          "High School or Below",
          "Employed",
          "Married",
          "NO",
          "Owner Occupier",
          "TRUE",
          "FALSE"
        ]
      ]
    }
  ]
}

response_scoring = requests.post('https://us-south.ml.cloud.ibm.com/ml/v4/deployments/a40b45aa-c8b6-4f9d-b43b-8c4ef46c109d/predictions?version=2021-05-01', json=payload_scoring,
 headers={'Authorization': 'Bearer ' + mltoken})
print("Scoring response")
print(response_scoring.json())
```
実行結果
```
Scoring response
{'predictions': [{'fields': ['prediction', 'probability'], 'values': [[0, [0.8599447513812154, 0.14005524861878452]]]}]}
```

#### 7.2.2. Keras深層学習モデル

以下の画像を分類します。
```python
img_car = np.load("img_car.npy")
print(f"ndim: {img_car.ndim}")
print(f"shape: {img_car.shape}")
```
```
ndim: 3
spape: (32, 32, 3)
```
```python
import matplotlib.pyplot as plt
plt.imshow(img_car)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507588/f1aafe80-751b-86b7-a19a-8a63b7c3685b.png" width=30%>

Kerasの場合、`payload_scoring` に `fields` は不要で、以下のフォーマットになります。
```json
{"input_data": [{"values": [イメージの配列]}]}
```

プログラム
```python
payload_scoring = {"input_data": [{"values": [img_car.tolist()]}]}

response_scoring = requests.post('https://us-south.ml.cloud.ibm.com/ml/v4/deployments/912ac537-d1d9-4009-8dbb-0df9e0015d76/predictions?version=2021-05-01', json=payload_scoring,
 headers={'Authorization': 'Bearer ' + mltoken})
print("Scoring response")
print(response_scoring.json())
```
実行結果
```
Scoring response
{'predictions': [{'fields': ['prediction'], 'id': 'activation_5', 'values': [[0.0017128867330029607, 0.7528261542320251, 9.030097135109827e-05, 0.00012985964713152498, 0.00011039885430363938, 0.0002107438340317458, 0.00011447971337474883, 0.0006360796396620572, 0.004830101504921913, 0.23933905363082886]]}]}
```

#### 7.2.3. PyTorch深層学習モデル

こちらもKerasと同じ画像を使用します。
次元は、Kerasで使った画像の3次元目のchannelが最初になるので、以下のように変換しています。
```python
np.transpose(img_car, (2,0,1))
```
PyTorchの場合、`payload_scoring` に `fields` が必要となり、以下の値となります。
```python
import onnxruntime
sess = onnxruntime.InferenceSession('mnist-pytorch.onnx')
print(f"input_name: {sess.get_inputs()[0].name}")
```
```
input_name: input.1
```
`payload_scoring`は、以下のフォーマットとなります。
```json
{"input_data": [{"fields": "input.1", "values": [イメージの配列]}]}
```

プログラム
```python
payload_scoring = {"input_data": [{"fields": "input.1", "values": [np.transpose(img_car, (2,0,1)).tolist()]}]}

response_scoring = requests.post('https://us-south.ml.cloud.ibm.com/ml/v4/deployments/bd2a7a2b-3440-434b-83c6-944cba26aba3/predictions?version=2021-05-01', json=payload_scoring,
 headers={'Authorization': 'Bearer ' + mltoken})
print("Scoring response")
print(response_scoring.json())
```
実行結果
```
Scoring response
{'predictions': [{'values': [[90.16368865966797, 132.31080627441406, -152.7025146484375, -148.4767608642578, -170.6203155517578, -235.80233764648438, -228.36398315429688, -156.88336181640625, 35.36519241333008, 105.59580993652344]]}]}
```

## 参考文献
- scikit-learn機械学習モデルは以下のtutorialで作成しているモデルを利用しました
    [AI governance tutorial: Build and deploy a model](https://www.ibm.com/docs/en/cloud-paks/cp-data/5.1.x?topic=tutorials-build-deploy-model#step00)