IBMの基盤モデルSlateのカスタムモデルをデプロイする Runtime24.1

# 0. はじめに
watsonx.aiの学習済みモデルのSlateでカスタムモデルをWatson Studioで作成し、外部アプリからの実行を可能にするためにWatson Machine Learning（WML)にファンクションでラップしてデプロイします。
[以前Runtime23.1で作った記事](https://qiita.com/spssfun2017/items/0c44156e0513de6c45c4)のRuntime24.1版です。

![{AA49349D-BA3F-409D-A8D9-F2FD005CDA21}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7aed5426-9ddf-af08-82ed-1c314524f85a.png)


### サンプルソースとデータ

https://github.com/hkwd/220201watsonstudio/tree/master/241022DeploySlateCustomModel

* テスト環境
    * python 3.11
    * NLP+DO Runtime 24.1
    * CP4DaaS
    * jp-tok

# 1. 事前準備
## 1-1. APIキーの作成
Python関数の操作をibm_watsonx_aiのAPIで行うのでAPIキーが必要です。

IAMのAPIキーのメニューから「作成」でAPIキーを作ります。
https://cloud.ibm.com/iam/apikeys
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/26c6e153-8416-197c-aa7a-46832b4cf4ca.png)

任意の名前を付けて作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/3a99cc0c-07d9-e316-91b8-9843de43ca3c.png)

コピーやダウンロードして保存しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/816e7b7d-efb4-0092-0d98-a7fe100f08c5.png)


## 1-2. デプロイメント・スペースの作成
デプロイメント・スペースの管理のページから「新規デプロイメント・スペース」を作ります。
https://jp-tok.dataplatform.cloud.ibm.com/ml-runtime/spaces?context=cpdaas
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ff6d1898-0466-b469-0edb-a44fe70064c2.png)

任意の名前を付け、紐づけるWatson Machine Learningのサービスを選択し、作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/89eb2c16-8308-f9b8-c725-665fa084bf9d.png)

作成したデプロイメント・スペースの「管理」タブを開き、スペースGUIDをコピーしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/76b916e4-7601-5724-9b18-0303e7a8bec1.png)

# 2. Slateのカスタムモデルの作成
学習データsentiment.csvをアップロードしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/4335397b-1e04-a322-1670-b8aee9783532.png)


Watson NLPを利用できる環境が必要なので、Watson Studioで、ランタイムに「NLP+DO Runtime24.1 on Python 3.11」を選んでNotebookを作成してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/00521a6a-91ce-9ea6-67b8-5229803d70de.png)

「コード・スニペット」を選び、「データの読み取り」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ba52a565-9388-9142-9978-0fb9c1a5de10.png)
「プロジェクトからデータを選択します」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/ec2f3d19-f770-f8f0-05dc-b4e7d4b4ce10.png)

学習データ「sentiment.csv」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/132c77f5-38ea-484a-0e56-fef716633d2a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a0a044e9-472a-1b58-2760-a902e5472dd6.png)

「pandas DataFrame」を選び「セルにコードを挿入」します。

df_1のpandas DataFrameに読み込むコードが自動生成されます。
テキストに対して「ポジティブ」か「ネガティブ」かのlabelのついたデータです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/6cbc4949-0fda-be10-c43f-413b9b3edd56.png)


この@Jungobuさんの[記事](https://qiita.com/Jungobu/items/fc9b3505b595e6e4ee5b)を参考にカスタムモデルを作ります

CSVをJSONに変換します。labelsは事前に配列に変換しておきます。
```python: 学習データをJSONファイルとして保存
def prepare_data(df):
    # textカラムと分類カテゴリの*labels*が必要
    df_out = df[['text', 'labels']].reset_index(drop=True)
    # labels列は配列でなければなりません (1つしかない場合でも同様)
    df_out['labels'] = df_out['labels'].map(lambda label: [label,])
    return df_out
    
train_df = prepare_data(df_1)
train_json_file = './train_data.json'
train_df.to_json(train_json_file, orient='records')
```

[マニュアル](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?context=wx&locale=ja#train-slate)を参考に、以下のコードでSlateのカスタム学習モデルを作成します。`pretrained-model_slate.153m.distilled_many_transformer_multilingual_uncased`をベースの学習済みモデルとして選んでいます。他にも[事前学習済みのモデル](https://www.ibm.com/docs/ja/watsonx/saas?topic=models-extracting-sentiment-custom-transformer-model)があるので、用途に応じて選ぶことができます。
ここは`num_train_epochs=25`の設定にもよりますが、それなりに時間がかかります。私がテストしたときには約15分かかりました。
なお、`DataStreamResolver`はRuntime24.1ではなくなっていますので`DataStream.from_file`と`prepare_stream_of_train_records`を使う必要があります。

```python:Slateのカスタム学習モデルを作成
# Slateのカスタム学習モデルを作成
import watson_nlp

from watson_nlp.blocks.classification.transformer import Transformer
#from watson_core.data_model.streams.resolver import DataStreamResolver
from watson_nlp.toolkit.classification_utils.train_util import prepare_stream_of_train_records
from watson_nlp import data_model as dm


# 学習データからデータストリームを作成
#data_stream_resolver = DataStreamResolver(target_stream_type=list, expected_keys={'text': str, 'labels': list})
#train_stream = data_stream_resolver.as_data_stream(train_json_file)

train_data_stream = dm.DataStream.from_file(train_json_file)
train_record_data_stream = prepare_stream_of_train_records(train_data_stream)


# 学習済みのSlateモデルをロード
pretrained_model_resource = watson_nlp.load('pretrained-model_slate.153m.distilled_many_transformer_multilingual_uncased')

# Slateのカスタムモデルを作成。学習回数はnum_train_cpochsで指定
classification_model = Transformer.train(train_data_stream=train_record_data_stream, 
                                         pretrained_model_resource=pretrained_model_resource, num_train_epochs=25)
```

カスタムモデルで分類をテストしてみます。
```python:Sカスタムモデルのテスト
text = 'ディスプレイは色鮮やかで、映画やゲームを楽しむのに最適です。'
slate_preds = classification_model.run(text)
slate_preds
```
うまくポジティブに分類されています。
```json
{
  "classes": [
    {
      "class_name": "ポジティブ",
      "confidence": 0.9874120950698853
    },
    {
      "class_name": "ネガティブ",
      "confidence": 0.012587935663759708
    }
  ],
  "producer_id": {
    "name": "Transformer-based Text Classifier",
    "version": "0.0.1"
  }
}
```


# 3. 関数のデプロイ
## 3-1. 環境設定

先につくったAPIキーとWMLのエンドポイントを使って、APIクライアントを作ります。
エンドポイントの情報は以下にあります。今回はtokyoのWatson Machine Learningサービスのデプロイメントスペースを使ったのでtokyoのエンドポイントを選びました。

https://ibm.github.io/watsonx-ai-python-sdk/setup_cloud.html

```python:APIクライアント作成
api_key = 'PASTE YOUR API_KEY HERE'
#url = 'PASTE YOUR ENDPOINT HERE'
# tokyo Public endpoint
url = 'https://jp-tok.ml.cloud.ibm.com'
space_id = 'PASTE YOUR SPACE ID HERE'

from ibm_watsonx_ai import APIClient
from ibm_watsonx_ai import Credentials

#APIキーを使用してクライアントを作成する
credentials = Credentials(
    api_key = api_key,
    url= url
)

client = APIClient(credentials)

```

次にPython関数をデプロイするデプロイメント・スペースを指定します。
先ほど作ったデプロイメント・スペースのGUIDを入力します。
デプロイメントスペースのAPIクライアントを作ります。

```python:デプロイメント・スペースの指定
client_space = APIClient(credentials, space_id = space_id)
```

## 3-2. モデルのシリアライズと保存
`classification_model`をpickleでシリアライズします。
```python:カスタムモデルをpickle化
import pickle
modelpickle='classification_model.pickle'

with open(modelpickle', 'wb') as f:
    pickle.dump(classification_model, f)
```

`client_space.data_assets.create`でデプロイメント・スペースに保存します。
```python:デプロイメントスペースに保存
input_assest_detail=client_space.data_assets.create(name=modelpickle,file_path=modelpickle)
```

以下のようにデプロイメント・スペースに保存されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/34b4f0d9-c393-fa4a-fac3-e38b7b5babb7.png)

以下でアセットIDを取得します。
```python:Asset_idを取得
print(input_assest_detail['metadata']['asset_id'])
```
```python:結果
c7acd4e6-a65c-401e-9240-d14675ddfc49
```

## 3-3. 関数の作成
この[記事](https://qiita.com/spssfun2017/items/1a223cb07d8e6c4f86aa)を参考にして、関数でラッピングして、デプロイします。

- `APIClient`でWMLのデプロイメントスペースに接続しています。
- `modelpickle_assest_id`はハードコードするか、`client_space.data_assets.list().query('NAME== @modelpickle')['ASSET_ID'][0]`で名前をつかって取得します。
- `client_space.data_assets.download`でデプロイメントスペースからカスタムモデルをpickleでダウンロードします。
- `classified_model = pickle.load(f)`でモデルをロードします。
- `def score(payload)`で関数を定義して、`classified_model.run`でカスタムモデルで分類結果を取得しています。

```python:Python関数の定義
def classifyContext():
    import watson_nlp
    import os
    import pickle

    api_key = 'PASTE YOUR PLATFORM API KEY HERE'
    url = 'PASTE YOUR ENDPOINT HERE'
    #url = 'https://jp-tok.ml.cloud.ibm.com'
    space_id = 'PASTE YOUR SPACE ID HERE'

    modelpickle='classification_model.pickle'
    #modelpickle_assest_id='c7acd4e6-a65c-401e-9240-d14675ddfc49'
    
    
    # watsonxクライアントの接続
    from ibm_watsonx_ai import APIClient
    from ibm_watsonx_ai import Credentials
    credentials = Credentials(
        api_key = api_key,
        url= url
    )

    client_space = APIClient(credentials, space_id = space_id)
    # modelのpickleのIDを名前から取得
    modelpickle_assest_id=client_space.data_assets.list().query('NAME== @modelpickle')['ASSET_ID'][0]
    # デプロイメントスペースからダウンロード
    client_space.data_assets.download(modelpickle_assest_id,modelpickle)
    
    #ダウンロード確認
    if(os.path.isfile(modelpickle) == True):
         print('Download OK')
    else:
         print('Download Failed')
    

    # pickleからモデルを復元
    with open(modelpickle, 'rb') as f:
        classified_model = pickle.load(f)    
        
    # スコアリングファンクション
    def score(payload):
        #カスタムモデルでの分類
        c_prediction = classified_model.run(payload['input_data'][0]["values"][0][0])       
        return {'predictions': [c_prediction.to_dict()]}
       
    return score
```

WMLにデプロイする前にローカルでテストしてみます。

```python:ファンクションのテスト
response = classifyContext()({
    "input_data": [{
        "values" :[["ディスプレイは色鮮やかで、映画やゲームを楽しむのに最適です。"]]
    }]
})
import pprint
pprint.pprint(response["predictions"])
```
ポジティブに分類できています。
```json:結果
[{'classes': [{'class_name': 'ポジティブ', 'confidence': 0.9943501353263855},
              {'class_name': 'ネガティブ', 'confidence': 0.00564983207732439}],
  'producer_id': {'name': 'Transformer-based Text Classifier',
                  'version': '0.0.1'}}]
```
## 3-4. Python 関数のアップロード
作成したPython 関数`classifyContext`をDeployment Spaceにアップロードします。

まず、Python関数`classifyContext`を実行するソフトウェア仕様を選びます。
[マニュアル](https://www.ibm.com/docs/ja/watsonx/saas?topic=specifications-supported-frameworks-software)に一覧があります。



ここでは、「NLP+DO Runtime24.1 on Python 3.11」のWatson Studioのランタイムをつかってモデルを作成したので、runtime-24.1-py3.11を使います。watson_nlpも含まれていました。ソフトウェア仕様のIDを取得します。

```python:ソフトウェア仕様のIDを取得
sw_spec_uid = client.software_specifications.get_id_by_name("runtime-24.1-py3.11")
```

関数my_deployable_functionを`client_space.repository.store_function`でデプロイメント・スペースに保存します。

```python:関数を保存
meta_props = {
    client.repository.FunctionMetaNames.NAME: "Slate Test Simple",
    client.repository.FunctionMetaNames.SOFTWARE_SPEC_UID: sw_spec_uid
}

function_details = client_space.repository.store_function(meta_props=meta_props, function=classifyContext)
function_id = client_space.repository.get_function_id(function_details)
```

Webのデプロイメント・スペースの管理コンソールでみると「資産」タブに関数が登録されていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/dd467720-4ea9-aca7-e453-2f5ccf6d5262.png)


## 3-5. オンライン デプロイメントを作成する
`client_space.deployments.create`でオンラインデプロイメントを作成し、登録したPython関数をWebサービスとしてスコアリング実行可能にします。ここでは`HARDWARE_SPEC`に`S`を選んでいます。「2 個の vCPU と 8 GB RAM」になります。スペックは[ここ](https://cloud.ibm.com/catalog/services/watson-machine-learning)にのっています。

```python:ファンクションをデプロイ
metadata = {
    client_space.deployments.ConfigurationMetaNames.NAME: "Deployment of function Slate Test Simple",
    client_space.deployments.ConfigurationMetaNames.HARDWARE_SPEC: { 'name': 'S'},  
    client_space.deployments.ConfigurationMetaNames.ONLINE: {}
}

function_deployment_details  = client_space.deployments.create(function_id, meta_props=metadata)
```
以下のように作成されました。これでWebサービスでのスコアリング実行が可能になりました。
```python:結果
######################################################################################

Synchronous deployment creation for id: '7c831292-961e-4660-99e7-db00647cd485' started

######################################################################################


initializing
Note: online_url and serving_urls are deprecated and will be removed in a future release. Use inference instead.
......
ready


-----------------------------------------------------------------------------------------------
Successfully finished deployment creation, deployment_id='ab6f4106-b0b3-409a-95dd-ac9f185905e3'
-----------------------------------------------------------------------------------------------
```

Webのデプロイメント・スペースの管理コンソールでみると「デプロイメント」タブに関数が登録されていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/b4919520-e733-1310-b3c0-e106bdcdbd61.png)


デプロイメントIDを取得します。
```python:デプロイメントIDの取得
deployment_id=function_deployment_details['metadata']['id']
print(deployment_id)
```
```python:結果
ab6f4106-b0b3-409a-95dd-ac9f185905e3
```

Webのデプロイメント・スペースの管理コンソールではデプロイメントIDやソフトウェア仕様やハードウェア仕様も確認できます。
![uploading...0]()



## 3-6. スコアリング実行

`client_space.deployments.score` メソッドを使用して、スコアリング レコードを Web サービス デプロイメントに送信できます。

```python:スコアリング実行
scoring_payload = {
    "input_data": [{
        "values" :[["ディスプレイは色鮮やかで、映画やゲームを楽しむのに最適です。"]]
    }]
}

response = client_space.deployments.score(deployment_id, scoring_payload)
import pprint
pprint.pprint(response["predictions"])
```
以下のように結果を得ることができました。
```python:結果
[{'classes': [{'class_name': 'ポジティブ', 'confidence': 0.9943501353263855},
              {'class_name': 'ネガティブ', 'confidence': 0.00564983207732439}],
  'producer_id': {'name': 'Transformer-based Text Classifier',
                  'version': '0.0.1'}}]
```

# 4 アプリからのスコアリング
pythonのアプリからも同様にスコアリング可能です。
ibm_watsonx_aiをpipで導入してください。
```
py -m pip install ibm_watsonx_ai
```

スコアリングのみを行うpythonコードは以下です。api_key, url, space_id, deployment_idを指定してください。

```python:02ScoreSlateCustomModel.py
# 環境設定
import pprint
from ibm_watsonx_ai import Credentials
from ibm_watsonx_ai import APIClient
api_key = 'PASTE YOUR PLATFORM API KEY HERE'
url = 'PASTE YOUR ENDPOINT HERE'
#url = 'https://jp-tok.ml.cloud.ibm.com'

space_id = 'PASTE YOUR SPACE ID HERE'
deployment_id = 'PASTE YOUR DEPLOYMENT ID HERE'


# WMLへの接続
credentials = Credentials(
    api_key=api_key,
    url=url
)

client_space = APIClient(credentials, space_id=space_id)

# 分類したい文章
scoring_payload = {
    "input_data": [{
        "values": [["ディスプレイは色鮮やかで、映画やゲームを楽しむのに最適です。"]]
    }]
}

# 分類実行
response = client_space.deployments.score(deployment_id, scoring_payload)
pprint.pprint(response["predictions"])
```

```JSON:結果例
PS python.exe c:/02ScoreSlateCustomModel.py
[{'classes': [{'class_name': 'ポジティブ', 'confidence': 0.9907686114311218},
              {'class_name': 'ネガティブ', 'confidence': 0.00923138577491045}],
  'producer_id': {'name': 'Transformer-based Text Classifier',
                  'version': '0.0.1'}}]
```

# 参考

### IBMの基盤モデルSlateでカスタム分類モデルを試してみた #Watson-Studio - Qiita 
https://qiita.com/Jungobu/items/fc9b3505b595e6e4ee5b

### Watson Machine LearningにPython関数をデプロイする #watsonx.ai - Qiita 
https://qiita.com/spssfun2017/items/1a223cb07d8e6c4f86aa


### カスタム分類モデルによるテキストの分類 — Docs | IBM watsonx 
https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-classify-text.html?context=wx&locale=ja

### 独自のモデルの作成 — Docs | IBM watsonx 

https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/watson-nlp-create-model.html?context=wx&locale=ja

### 関数のデプロイ - Docs | IBM Cloud Pak for Data as a Service 

https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-deploy-functions.html?locale=ja&context=cpdaas

### Watson Machine Learning での Python 関数のデプロイ - Docs | IBM Cloud Pak for Data as a Service 

https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/ml-deploy-py-function.html?context=cpdaas&context=cpdaas&audience=wdp&locale=ja