IBM Cloud の Watson Studio と Watson Machine learningサービスでトレーニングしたモデルをローカルで利用する（API編, Pytorch）

### 本記事の目的

本記事では、IBM Cloud上で提供される Cloud Pak for Data as a Service（以降、CP4DaaSと表記） を対象に、Watson Studio および Watson Machine learning サービスでトレーニングしたモデルをローカルにダウンロードして利用する例を紹介します。

### 1. モデルのトレーニングおよびプロジェクトへの保存

まず、Watson Studio および Watson Machine learning サービスでモデルをトレーニングし、プロジェクトへ保存します。
本記事ではモデルのトレーニングおよびプロジェクトへの保存手順については取り扱いません。製品資料や別Qiita記事をご参照ください。

- モデルのトレーニングおよびプロジェクトへの保存方法
    - Auto AI
        - [製品資料：AutoAI モデルの選択](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/autoai-view-results.html?context=cpdaas&audience=wdp)
    - Federated Learning
        - [製品資料：統合学習の Tensorflow チュートリアル](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fl-tf2-tutorial.html?context=cpdaas&audience=wdp#step-3)
        - [Qiita記事：IBM Cloud の Watson Studio と Watson Machine learningサービスで、Federated Learningを動かす（API編, 準同型暗号オプション有効化）](https://qiita.com/1000aki/items/81d91e1b090e5896ac9d)
    - Watson Studio Notebook
        - [IBM Developer Learning Paths 日本語サイト：Watson Studio上のJupyter Notebookでモデルをビルドする](https://ibm.github.io/japan-technology/Code-Tutorials/watson-studio-using-jupyter-notebook/)

### 2. プロジェクトへ保存されたモデルのダウンロード

**モデルのフレームワークに応じ、ダウンロードされるモデルファイルの形式は異なります。**
本節では、以下のQiita記事の手順にてトレーニングおよび保存したPytorchのモデルを例として利用します。

[Qiita記事：IBM Cloud の Watson Studio と Watson Machine learningサービスで、Federated Learningを動かす（API編, 準同型暗号オプション有効化）](https://qiita.com/1000aki/items/81d91e1b090e5896ac9d)

- 事前準備
    - API keys のページでAPIキーを生成し、取得します。
        - [IBM Cloud APIKEYの作成(取得)方法](https://qiita.com/nishikyon/items/fa8b951267efbe911fe7)
    - モデルが保存されているプロジェクトへアクセスし、 プロジェクトのID を取得します。
        - [プロジェクトID の確認方法](https://qiita.com/1000aki/items/cee1fdf52ca56f5b62ec)
    - Watson Machine Learning サービスインスタンスのロケーション名を確認します。
        |WMLサービス・インスタンスの作成リージョン  |ロケーション名|
        |---|---|
        |米国（ダラス）  |us-south  |
        |英国（ロンドン）  |eu-gb  |
        |アジア太平洋（東京）  |jp-tok  |
        |ヨーロッパ（フランクフルト）  |eu-de  | 
    - インターネットに接続可能な任意のPython環境へNoteobookを作成します。
        - 以降の手順は、Notebookにセルを追加して実行します。

- 手順
    - Noteobookのセルへ変数として事前準備した値をセットします。
    ``` python:セル内の必須TBD
    PROJECT_ID = '控えておいたPROJECT_ID'
    IAM_APIKEY = '控えておいたAPIキー'
    WML_SERVICES_LOCATION = '控えておいたロケーション' # WMLインスタンスのロケーション. us-south, eu-gb, eu-de, jp-tok のいずれか
    WML_SERVICES_URL = 'https://' + WML_SERVICES_LOCATION + '.ml.cloud.ibm.com'
    ```
    - WMLクライアントのパッケージをインストールします。
    ```python:
    pip install 'ibm_watson_machine_learning'
    ```
    - WMLクライアントをセットアップします。
    ```python:
    # WMLクライアントのセットアップ
    from ibm_watson_machine_learning import APIClient
    
    wml_credentials = {
        "url": WML_SERVICES_URL,
        "apikey": IAM_APIKEY
    }
    wml_client = APIClient(wml_credentials)
    wml_client.set.default_project(PROJECT_ID) # モデルが保存されているプロジェクト
    ```
    - プロジェクトへ保存されているモデルを確認し、ダウンロードしたいモデルのIDを控えます。
    ```python: プロジェクトへ保存されたモデルの一覧
    # モデル一覧
    wml_client.repository.list_models()
    ```
    - 本例では、NAMEが`pytorch_bba9a5de-0f8d-4364-88fe-f62b54cea62f`のPytorchのモデルをダウンロードできるように、ID `b3cfb015-5c34-4320-aa0c-4280092bbfa1`を控えます。
    ```text: 実行例：プロジェクトへ保存されたモデルの一覧
    ------------------------------------  ---------------------------------------------------------------  ------------------------  -----------------  ----------  ----------------
    ID                                    NAME                                                             CREATED                   TYPE               SPEC_STATE  SPEC_REPLACEMENT
    4813469b-7f00-46fe-b9d6-5307e8b67e0d  pt_mnist_init_model                                              2023-06-16T11:13:04.002Z  pytorch-onnx_1.12  supported
    b3cfb015-5c34-4320-aa0c-4280092bbfa1  pytorch_bba9a5de-0f8d-4364-88fe-f62b54cea62f                     2023-06-08T11:49:22.002Z  pytorch-onnx_1.12  supported
    ```

    - 控えておいたダウンロードしたいモデルのIDを変数にセットします。
    ``` python:モデルのID
    model_id = '控えておいたダウンロードしたいモデルのID'
    # model_id = 'b3cfb015-5c34-4320-aa0c-4280092bbfa1' 一つ前の手順で控えた値を設定した例
    ```    
    - プロジェクトからローカルへモデルファイルをダウンロードし、解凍します。
        - ダウンロードされるモデルは圧縮されているので、解凍することが必要です。
    ```python:モデルファイルのダウンロード・解凍
    # モデルファイルをダウンロード
    wml_client.repository.download(model_id, "model.tar.gz")
    
    # ダウンロードしたモデルファイルを解凍
    import subprocess
    command = ["unzip", "-o", "model.tar.gz"]
    subprocess.call(command)
    ```
    - 解凍されたモデルファイルを確認します。
        - 本例ではPytorchのモデルをダウンロードしたため、`PyTorch_1686224194.6340466.onnx`となっています。ファイル形式はONNX形式が使用されています。
        - モデルのフレームワークに応じ、ダウンロードしたモデルのファイル形式は異なります。
    ``` shell:
    !ls -l
    ```
    ``` text: 確認例
    total 1752
    -rw-r--r-- 1 wsluser1 wsluser1 930016 Jul 23 11:36 PyTorch_1686224194.6340466.onnx
    -rw-r--r-- 1 wsluser1 wsluser1 857735 Jul 23 17:03 model.tar.gz
    ```    

### 3. ダウンロードしたモデルファイルによる予測の実行例（ONNX形式のPytorchモデルを使用）

**モデルのフレームワークやデータセットの構造に応じ、モデルのスコアリング方法は異なります。**
本節では、以下のQiita記事の手順にてトレーニングおよび保存したPytorchのモデルを例として利用します。

[Qiita記事：IBM Cloud の Watson Studio と Watson Machine learningサービスで、Federated Learningを動かす（API編, 準同型暗号オプション有効化）](https://qiita.com/1000aki/items/81d91e1b090e5896ac9d)このモデルではMNISTデータセットを利用しています。

- 手順
    - onnxのパッケージをインストール
        - 前節で例としてダウンロードしたPytorchモデルのファイルはONNX形式でした。ONNX形式のファイルを利用するために本パッケージをインストールします。
    ```python:
    pip install onnx onnxruntime
    ```    
    - モデルファイルのパス指定
        - 前節で解凍したモデルファイルのパスを指定します。
    ```python:
    model_path = "<解凍したモデルファイルのパス>"
    # model_path = "./PyTorch_1686224194.6340466.onnx" 前節で解凍したモデルのパスを指定した例
    ```
    - onnxパッケージを利用したモデルファイルの読込
    ```python:
    import onnx
    model = onnx.load(model_path)
    onnx.checker.check_model(model)
    ```     
    - モデルの構造確認
    ```python
    # モデルの構造が表示可能
    print(onnx.helper.printable_graph(model.graph))
    ```
    ```text: モデルの構造の確認例
    graph torch_jit (
      %onnx::Flatten_0[FLOAT, 1x1x28x28]
    ) optional inputs with matching initializers (
      %1.weight[FLOAT, 256x784]
      %1.bias[FLOAT, 256]
      %3.weight[FLOAT, 100x256]
      %3.bias[FLOAT, 100]
      %5.weight[FLOAT, 50x100]
      %5.bias[FLOAT, 50]
      %7.weight[FLOAT, 10x50]
      %7.bias[FLOAT, 10]
    ) {
      %onnx::Gemm_9 = Flatten[axis = 1](%onnx::Flatten_0)
      %input = Gemm[alpha = 1, beta = 1, transB = 1](%onnx::Gemm_9, %1.weight, %1.bias)
      %onnx::Gemm_11 = Relu(%input)
      %input.3 = Gemm[alpha = 1, beta = 1, transB = 1](%onnx::Gemm_11, %3.weight, %3.bias)
      %onnx::Gemm_13 = Relu(%input.3)
      %input.7 = Gemm[alpha = 1, beta = 1, transB = 1](%onnx::Gemm_13, %5.weight, %5.bias)
      %onnx::Gemm_15 = Relu(%input.7)
      %input.11 = Gemm[alpha = 1, beta = 1, transB = 1](%onnx::Gemm_15, %7.weight, %7.bias)
      %17 = LogSoftmax[axis = 1](%input.11)
      return %17
    }
    ```
    - スコアリング用のデータセット読込
        - MNISTデータセットをダウンロード後、読込して前処理を実行
    ```python
    import os
    import requests
    import numpy as np
    import shutil
    
    # ファイルのダウンロード
    DATASET_URL = 'https://api.dataplatform.cloud.ibm.com/v2/gallery-assets/entries/85ae67d0cf85df6cf114d0664194dc3b/data'
    DATASET_FILE_NAME = 'mnist.npz'
    
    local_file = os.path.join("./", DATASET_FILE_NAME)
    with requests.get(DATASET_URL, stream=True) as r:
            with open(local_file, 'wb') as f:
                shutil.copyfileobj(r.raw, f)
    
    # ローカルファイルの読込
    with np.load(local_file, allow_pickle=True) as mnist:
            x_train, y_train = mnist['x_train'], mnist['y_train']
            x_test, y_test = mnist['x_test'], mnist['y_test'] 
    
    # 画像データ前処理用の関数
    def preprocess(x, y):
        """
        Preprocesses the training and testing dataset, \
        e.g., reshape the images according to self.channels_first; \
        convert the labels to binary class matrices.
    
        :return: None
        """
        img_rows, img_cols = 28, 28
        x = x.astype('float32').reshape(x.shape[0], 1, img_rows, img_cols)
        y = y.astype('int64')
    
        return (x, y)
    
    # スコアリングの動作確認用に、テストデータセットの先頭10枚の画像のみを取り出して前処理を実行
    x_input, y_true = preprocess(x_test[:10], y_test[:10])
    ```

    - スコアリングの実行
    ```python
    import onnxruntime

    # スコアリング用にモデル読込
    sess = onnxruntime.InferenceSession(model_path, 
                                        providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
    
    # モデルの入出力定義の確認
    input_name = sess.get_inputs()[0].name
    print("Input name  :", input_name)  
    input_shape= sess.get_inputs()[0].shape
    print("Input shape  :", input_shape)  # 今回のモデルは一枚の画像を入力にとるため、複数の画像を同時に入力することはできない。for文で1枚ずつのスコアリングを繰り返し実行する。
    output_name = sess.get_outputs()[0].name
    print("Output name  :", output_name) 
    output_shape = sess.get_outputs()[0].shape
    print("Output shape :", output_shape)
    output_type = sess.get_outputs()[0].type
    print("Output type  :", output_type)
    
    y_pred=[] # 予測結果の格納
    for i in range(0, x_input.shape[0]):
        #print(i)
        x = np.expand_dims(x_input[i], 0) # [1, 1, 28, 28] になるよう次数を増やす
        inputs = {input_name:  x}
        outputs = sess.run(None, inputs)
        y_pred.append(np.argmax(outputs[0], axis=1)[0])
    ```   
    - Accuracyの計算
        - （補足）例として使用したモデルは、正解率が0.2と低い精度でした。動作確認用のチュートリアルで作成したモデルのため、トレーニングのitteration数が小さく設定されており、学習が十分に行われていなかったことが原因と考えられます。
    ```python
    from sklearn.metrics import accuracy_score
    
    # 精度計算用の入力データ
    print(f"y_true: {y_true}") #正解ラベル
    print(f"y_pred: {np.array(y_pred)}") #予測結果のラベル
    
    # 精度計算
    accuracy=accuracy_score(y_true, y_pred)
    print(f"accuracy: {accuracy}")
    ```
    ```text: 精度計算の結果例
    y_true: [7 2 1 0 4 1 4 9 5 9]
    y_pred: [1 1 1 9 1 1 1 1 1 1]
    accuracy: 0.2
    ```

### 4. 参考資料

プロジェクトに保存したモデルのダウンロード方法は以下を参照しました。

IBM Watson Machine Learning documentation > Core > Repositry　
https://ibm.github.io/watson-machine-learning-sdk/core_api.html#repository

ONNXの利用方法について以下の記事を参考にさせていただきました。ありがとうございました。

ONNXの使い方メモ
https://qiita.com/studio_haneya/items/be9bc7c56af44b7c1e0a