はじめに
以下の3つの記事の応用問題として「Google Colabで学習したPyTorchのモデルをWatson ML上で動かす(予測する)」ということを試したところ、比較的簡単にできたので、その手順メモを残します。
- Watson Studioでscikit-learn機械学習モデルをWebサービス化する
- PyTorch 学習メモ (Karasと同じモデルを作ってみた)
- (手順メモ) Watson Studio Jupyterでデータアセットに登録したデータをローカルにコピーする
PyTorchのモデルをWatson MLで動かす場合の前提
PyTorchのモデルをWatson MLで動かす場合は、モデルをONNX形式でexportし、更にgzipで圧縮をかけます。
今回は、このgzファイルをいったんローカルPCにダウンロードし、更にWatson Studioのデータアセットとして登録する方法をとりました。
具体的な手順としては、2. PyTorch 学習メモ (Karasと同じモデルを作ってみた)
のNotebookをGoogle Colab上でそのまま動かし、更にその下に下記のセルを追加して、実行しました。
# ダミーデータの作成
dummy_input = torch.randn((1, 3, 32, 32)).to(device)
# onyx形式でexport
# keep_initializers_as_inputsのオプションが重要でこれがないとエラーになる
torch.onnx.export(net, dummy_input, "cifar10-pytorch-sample.onnx",
keep_initializers_as_inputs=True, verbose=True)
# gz形式に圧縮
!tar czvf cifar10-pytorch-sample.gz cifar10-pytorch-sample.onnx
# 結果をPCにダウンロード
fn = 'cifar10-pytorch-sample.gz'
from google.colab import files
files.download(fn)
ポイントとして、torch.onnx.export関数呼び出しのときに、keep_initializers_as_inputsオプションを付ける必要がありました。この指定をしないと、モデル呼び出し時にランタイムエラーになってしまいます。
Watson Studio側用Notebook
ここから先の作業はWatson Studio側になります。
デプロイ作業と、予測作業の両方を、以下のNotebookを使って行いました。
URL指定で新規Watson Studio Notebookを作る場合は、下記リンクをコピペして下さい。
https://raw.githubusercontent.com/makaishi2/sample-data/master/notebooks/pytorch%20deploy%20sample.ipynb
読み込んだ直後の状態は下記のようになります。
Notbeookを動かす前の準備
以下のことが必要になります。
- Watson Studioのインスタンス作成
- Watson Studioのプロジェクト作成
- アクセストークンが作成済み
- デプロイメントスペースが作成済み
- PCにダウンロードしたファイル
cifar10-pytorch-sample.gzをデータアセットとしてアップロード済み
それぞれの手順については、以下の記事を参考にして下さい。
1.と2.: 無料でなんでも試せる! Watson Studioセットアップガイド
3. : (手順メモ) Watson Studio Jupyterでデータアセットに登録したデータをローカルにコピーする
4. : デプロイメントスペースの作成
5に関しては 「プロジェクトに追加」->「データ」から画面の指示に従い、ファイルをdrag and dropすればいいです。
Notebook解説
あとは、ほとんど上で紹介した3つの記事の復習です。概要のみの説明にとどめ、PyTorch固有の話だけ、詳しめに解説します。
一番上のセル
下記の一番上のセルは、ツールが自動生成したものです。データアセットをローカルにコピーするときに必要になります。
詳しい手順は、(手順メモ) Watson Studio Jupyterでデータアセットに登録したデータをローカルにコピーする を参照して下さい。
# @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='xxxx', project_access_token='xxxx')
pc = project.project_context
ローカルのモデルファイルのコピー
以下のコードがデータアセットにあるモデルファイルをローカルにコピーするものです。
解説は、(手順メモ) Watson Studio Jupyterでデータアセットに登録したデータをローカルにコピーする
にありますので、こちらを参照して下さい。
# project-libを使ってデータアセットのファイルをローカルにコピーする
fn = 'cifar10-pytorch-sample.gz'
infile = project.get_file(fn)
with open(fn, 'wb') as local_file:
local_file.write(infile.read())
1. モデルの保存
この手順は、上で紹介した記事Watson Studioでscikit-learn機械学習モデルをWebサービス化するとほとんど同じ手順です。
差分として考慮が必要な箇所として「1.3 Software Specification ID の取得」がありますが、結論として上の記事と同じ"default_py3.7"を選べばよかったです。
「1.4 モデルの保存」のコードは次のようになります。(見やすいようにnotebookから一部修正しています)
model_path = 'cifar10-pytorch-sample.gz'
metadata = {
client.repository.ModelMetaNames.NAME: 'External pytorch model',
client.repository.ModelMetaNames.TYPE: 'pytorch-onnx_1.3',
client.repository.ModelMetaNames.SOFTWARE_SPEC_UID: sofware_spec_uid
}
published_model = client.repository.store_model(
model=model_path,
meta_props=metadata)
scikit-learnの時は、store_model関数の引数として、学習済みモデルのインスタンスそのものを引数にした箇所が、今回は、外部ファイル名(実体はONNX形式のモデルを圧縮したもの)が引数になっています。
正常にモデルの登録ができると、下記のようなコマンドに対して、モデルが表示されます。
models_details = client.repository.list_models()
------------------------------------ ------------------------- ------------------------ -----------------
ID NAME CREATED TYPE
f25e3664-b2d3-476b-94ad-0f17868f82b9 External pytorch model 2021-01-12T06:08:18.002Z pytorch-onnx_1.3
dfc072c0-3dad-42b0-9951-4827271fd868 Scikit IRIS random forest 2021-01-11T06:32:59.002Z scikit-learn_0.23
------------------------------------ ------------------------- ------------------------ -----------------
2. モデルのデプロイ
モデルのデプロイもscikit-learnの時とまったく同じです。解説は省略し、コードと実行結果のみ記載します。
コード
metadata = {
client.deployments.ConfigurationMetaNames.NAME: "Deployment of external pytorch model",
client.deployments.ConfigurationMetaNames.ONLINE: {}
}
created_deployment = client.deployments.create(published_model_uid, meta_props=metadata)
実行結果
#######################################################################################
Synchronous deployment creation for uid: 'f25e3664-b2d3-476b-94ad-0f17868f82b9' started
#######################################################################################
initializing
ready
------------------------------------------------------------------------------------------------
Successfully finished deployment creation, deployment_uid='d2748d63-7b21-49a1-8533-956b421498ca'
------------------------------------------------------------------------------------------------
コード
# デプロイメントの一覧表示
client.deployments.list()
実行結果
----------------------------------- ------------------------------------ ----- ------------------------
GUID NAME STATE CREATED
d2748d63-7b21-49a1-8533-956b421498ca Deployment of external pytorch model ready 2021-01-12T06:08:41.907Z
864aec72-15c1-4008-b96a-6ce1c1776af4 Scikit-Learn Iris Model online ready 2021-01-11T06:46:10.762Z
------------------------------------ ------------------------------------ ----- ------------------------
次のコードは、deployment_uid変数を設定するためのものです。この変数は予測時に必要です。
今は、デプロイをした直後なので、created_deployment変数から取得可能ですが、予測のみを行うコードの場合、この変数が使えません。その場合、上の一覧表示の結果をコピーして、手動で設定することになります。
コード
# depolyment_uid 取得
deployment_uid = client.deployments.get_uid(created_deployment)
# すでにデプロイ済みの場合、下記コメントをはずして手動でdeployment_uidを設定する
# deployment_uid = "d2748d63-7b21-49a1-8533-956b421498ca"
# deployment_uidの確認
print(deployment_uid)
結果
d2748d63-7b21-49a1-8533-956b421498ca
3. 予測
最後のステップは予測です。今は、たまたま同じnotebookにありますが、普通は別の場所から呼ばれることになります。
その場合は、上で説明したdeployment_uidの設定に注意するようにして下さい。
3.1 検証用データの取得
検証用データは、学習時同様、pytorchのライブラリを使って取得することにします。Watson StudioのJupyterでは、pytorchvisionが含まれていないので、追加導入する必要があります。ただ、この時にバージョンに注意しないと、import時にエラーが起きるようです。
下記のコードが動作確認されたものとなります。
!pip install torchvision==0.8.1 | tail -n 1
import torch
import torchvision
import torchvision.transforms as transforms
念のため、導入済みのバージョンを確認してみます。
print(torch.__version__)
print(torchvision.__version__)
1.7.0
0.8.1
それでは、実際にデータを読み込んでみましょう。以下のコードはPyTorch 学習メモ (Karasと同じモデルを作ってみた)とまったく同じものなので、細かい解説は省略します。
# 分類先クラス名
classes = ['plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
# 分類クラス数
num_classes = len(classes)
# 1回の学習で何枚の画像を使うか
batch_size = 128
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(testset, batch_size=batch_size, shuffle=False, num_workers=2)
3.2 検証用データの組み立てと、イメージ表示
次の読み込んだデータから、検証用の入力データ100件分を組み立てます。
ポイントは、元のデータ形式である、LataLoaderクラスのインスタンスから、ループを回しながら、「入力データ」と「正解ラベル」のセットを取り出し、その結果をリストでつないで、2つの変数valuesとlabelsを作っている点です。
入力データに関しては、元々はTensor形式だったものを、numpy -> list形式に変換します。
また、この処理をしている最中にせっかくなので、読み込んだ入力データをイメージ表示もしています。
具体的なコードは以下のようになります。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import display
N = 100
values = []
labels = []
plt.figure(figsize=(15, 15))
for i in range(N):
# テスト用データの組み立て
image, label = testset[i]
labels.append(label)
xdata = image.numpy().tolist()
values .append(xdata)
# データの画面表示 (予測とは無関係。ついでにやっただけ)
ax = plt.subplot(10, 10, i + 1)
img = np.transpose(image.numpy(), (1, 2, 0))
img2 = (img + 1)/2
plt.imshow(img2)
ax.set_title(classes[label], fontsize=10)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
イメージはこんな形で出力されるはずです。(下記は一部)
最後に、今、組み立てた変数valuesを使って、最終的にAPI呼び出しの引数となる変数scoring_payloadを組み立てます。この書式も、scikit-learnの時とまったく同じです。
# 入力用パラメータの組み立て
scoring_payload = {"input_data": [{"values": values}]}
3.3 予測用APIの呼び出し
これで、予測のための準備はすべて終わりました。あとは、ライブラリを使ってAPI呼び出しをします。
具体的なコードは下記になります。
predictions = client.deployments.score(deployment_uid, scoring_payload)
予測結果はPyTorchの場合、下のように、各モデルの確信度の配列になります。
print(predictions)
{'predictions': [{'values': [[-4.940670013427734, -4.821472644805908, -3.138746500015259, 4.834586143493652, -8.361305236816406, 3.5139641761779785, 0.46214526891708374, -5.3571882247924805, -2.8474788665771484, -5.455841541290283], [2.7644705772399902, 2.6016273498535156, -15.107336044311523, -17.771255493164062, -18.063720703125, -23.072168350219727, -12.652151107788086, -24.18853759765625, 16.923925399780273, -3.93799090385437], [3.2324273586273193, 0.6664918661117554, -9.696178436279297, -7.741365432739258, -7.4524688720703125, -11.031821250915527, -8.739614486694336, -9.531129837036133, 7.229529857635498, -0.4289969205856323], (以下略)
np.argmax関数を使って、どの要素が最大化を調べることで、予測クラスがわかります。具体的なコードは次のとおりです。(比較のため、正解ラベルも表示してみました)
w1 = predictions['predictions'][0]['values']
w2 = np.array([np.argmax(x) for x in w1])
print('予測: ',w2)
print('正解: ', np.array(labels))
予測: [3 8 8 0 6 6 1 4 3 9 0 9 5 7 9 6 5 7 8 6 7 0 4 9 4 4 4 0 9 6 6 5 4 5 9 8 4
9 9 5 4 6 5 6 0 9 3 9 7 4 9 8 7 3 8 8 5 3 3 5 7 5 6 3 6 2 1 2 3 7 0 6 8 8
0 2 0 3 3 8 8 1 1 7 2 5 2 4 8 9 0 6 8 6 4 6 6 0 0 7]
正解: [3 8 8 0 6 6 1 6 3 1 0 9 5 7 9 8 5 7 8 6 7 0 4 9 5 2 4 0 9 6 6 5 4 5 9 2 4
1 9 5 4 6 5 6 0 9 3 9 7 6 9 8 0 3 8 8 7 7 4 6 7 3 6 3 6 2 1 2 3 7 2 6 8 8
0 2 9 3 3 8 8 1 1 7 2 5 2 7 8 9 0 3 8 6 4 6 6 0 0 7]
ぱっと見、8割程度で正解が得られていそうなことがわかります。
最後に混同行列表示もしてみます。
ここは、コードでなく結果のみ載せることにします。
対角線のマス目(=正解)に多くの件数が集まっていて、そこそこの精度が出ていることがわかりました。


