Edited at

Watson Studioでscikit-learn機械学習モデルをWebサービス化する


はじめに

Watson Studioの特徴はいろいろとあり、一言ではいえません。

しかし、従来からPython - Jupyternotebookを使ってscikit-learningによる機械学習モデルを利用していたユーザーにとってのメリットは何かといえば、sckit-learningで学習した機械学習モデルを、クラウド上にアップしてWebサービスとして簡単に使えるようになる点です。

サンプルアプリを参照しつつ、Watson StudioのJupyter Notebook上で作ったscikit-learn機械学習モデルをデプロイし、Webサービス化する手順を簡単に解説します。

(2018-02-08 後半のモデル登録、Webサービス化のコマンドをきれいに直しました)

(2018-02-08 IBM Cloud上で稼働するサンプルアプリ紹介を追加しました)

(2018-02-09 Github上のJupyter Notebookのリンクを追加しました)

(2018-04-26 新バージョンに対応して全面的に見直し)

(2018-07-01 手順の見直し、タイトル改定)

(2019-08-09 Watson MLの認証方式変更に伴い改訂)


モデルの説明

サンプルで作られる機械学習モデルは、手書き文字データを元に数字の分類を行うものです。

モデルは、SVM(Support Vector Machine)で作られていますが、前処理として標準化を行っています。

scikit-learnパイプライン機能を用いて、この2つの処理を結合したモデルをWMLにデプロイする形になります。

サンプルはたまたまパイプラインを使っていますが、これを使うことは必須ではなく、通常の単体のモデルクラスでも以下と同じ形のデプロイは可能です。


前提

IBM Cloud上で、次のインスタンスは作成済みであることを前提とします。


  • Watson Studio

  • Cloud Object Storage

  • Watson Machine Leaning

また、次の記事のやりかたで、上記インスタンス間の関連づけが終わっていることとします。

10分でできるPython機械学習環境! Watson Studioセットアップガイド


機械学習モデルを作るまで

最初のステップはWatson Studio上で通常のやりかたで機械学習モデルを作ります。

前提として、sckit-learnを使ったモデルを使う必要があります。


Jupyter Notebookの作成

Watson Studio上で新しいJupyter Notebookを作成します。

名前はなんでもいいですがここでは"Watson ML Sample"としています。

その他のオプションは全部デフォルトとして下さい。


データのロードと確認

次のコマンドをコピペして、機械学習に使うデータをロードし、内容を表示します。

from sklearn import datasets

digits = datasets.load_digits()
%matplotlib inline
import matplotlib.pyplot as plt

images_number = 5
images_and_labels = list(zip(digits.images, digits.target))

for i, (image, label) in enumerate(images_and_labels[:images_number]):
plt.subplot(2, images_number, i + 1)
plt.axis('off')
plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
plt.title('%i' % label)

次のような結果が表示されるはずです。

digits.dataには、入力データが、digits.targetには教師データ(ラベル)が配列の形で入っています。その状態を以下のコマンドで確認します。

print(digits.data[0])

print(digits.target[0])
samples_count = len(digits.images)
print("Number of samples: " + str(samples_count))

こんな結果が返ってくるはずです。

[  0.   0.   5.  13.   9.   1.   0.   0.   0.   0.  13.  15.  10.  15.   5.

0. 0. 3. 15. 2. 0. 11. 8. 0. 0. 4. 12. 0. 0. 8.
8. 0. 0. 5. 8. 0. 0. 9. 8. 0. 0. 4. 11. 0. 1.
12. 7. 0. 0. 2. 14. 5. 10. 12. 0. 0. 0. 0. 6. 13.
10. 0. 0. 0.]
0
Number of samples: 1797


学習データの準備

1797件のデータを、学習用70%、検証用20%、スコアリング用10%に分割するため、以下のコマンドを実行します。「スコアリング」はモデルをクラウド上にデプロイした後の評価で使う想定です。

train_data = digits.data[: int(0.7*samples_count)]

train_labels = digits.target[: int(0.7*samples_count)]

test_data = digits.data[int(0.7*samples_count): int(0.9*samples_count)]
test_labels = digits.target[int(0.7*samples_count): int(0.9*samples_count)]

score_data = digits.data[int(0.9*samples_count): ]
score_labels = digits.target[int(0.9*samples_count): ]

print("Number of training records: " + str(len(train_data)))
print("Number of testing records : " + str(len(test_data)))
print("Number of scoring records : " + str(len(score_data)))


パイプラインの作成とモデルの学習

以下のコマンドを実行して、次のことを行います。


  • 前処理としての標準化クラスの生成

  • 機械学習モデルとしてSVMクラスの生成

  • 上記2つを組み合わせたパイプラインの作成

  • パイプラインに対して、事前に準備した学習データを用いて学習の実施

  • 学習されたパイプラインを、model変数に代入

  • 学習したモデルを使い、評価データに対して予測を実施

  • 予測結果の評価を実施

from sklearn.pipeline import Pipeline

from sklearn import preprocessing
from sklearn import svm, metrics

scaler = preprocessing.StandardScaler()
clf = svm.SVC(kernel='rbf')
pipeline = Pipeline([('scaler', scaler), ('svc', clf)])

model = pipeline.fit(train_data, train_labels)

predicted = model.predict(test_data)
print("Evaluation report: \n\n%s" % metrics.classification_report(test_labels, predicted))

評価として、こんな結果が返ってくるはずです。

Evaluation report: 

precision recall f1-score support

0 1.00 0.97 0.99 37
1 0.97 0.97 0.97 34
2 1.00 0.97 0.99 36
3 1.00 0.94 0.97 35
4 0.78 0.97 0.87 37
5 0.97 0.97 0.97 38
6 0.97 0.86 0.91 36
7 0.92 0.97 0.94 35
8 0.91 0.89 0.90 35
9 0.97 0.92 0.94 37

avg / total 0.95 0.94 0.95 360

ここまでは、通常のsckit-learnを用いた機械学習です。

ここから先が、Watson Studio独自の話になります。


機械学習モデルのリポジトリへの登録

学習・テスト済みの機械学習モデル(scikit-learnを利用したもの)を、Watson MLのリポジトリに登録します。


(ライブラリの導入 -> 現在は不要)

(2018-07-01 追記)

以前は手動のライブラリ導入が必要でしたが、2018-07-01時点でマスターのイメージに含まれていてこの手順が不要になったことを確認しました。


資格情報の設定

IBM CloudのダッシュボードからWatson Machine Learningの資格情報を表示させ、クリップボードに貼り付けます。

下記のコードを張り付けた結果で置き換えて、実行して下さい。

(必要なのはinstance_id,apikey,urlのみですが、他の項目が残っていても構いません。)

wml_credentials={

"apikey": "***",
"instance_id": "***",
"url": "https://ibm-watson-ml.mybluemix.net"
}


管理クライアントインスタンスの生成

次のコマンドを実行してクライアントインスタンスを生成し、モデルアップロードの準備を行います。

from watson_machine_learning_client import WatsonMachineLearningAPIClient

client = WatsonMachineLearningAPIClient(wml_credentials)


モデルアップロード

次のコマンドでモデルのアップロードが実行されます。

published_model = client.repository.store_model(model=model, meta_props={'name':'Digits Classification Model'}, \

training_data=train_data, training_target=train_labels)

モデルのアップロードが完了すると、残りの操作はすべてUIから可能になりますので、以下ではその手順を示します。


UIツールでアップロードしたモデルの確認

Watson Studioのアセットタブ、Modelsの欄に今アップロードしたモデルが含まれているはずですので、この項目をクリックして詳細表示を行います。

Summaryタブでは、作ったモデルのサマリー表示がされています。


Webサービスのデプロイ

次に一番右のDeploymentsタブをクリックします。

次のような画面になるので、右上の「Add deployment」のアイコンをクリックします。

次のような画面になるので、名称を適当にいれて、画面右下の「Save」ボタンをクリックして下さい。

下のような画面に遷移し、ステータスがいろいろと変わっていきますが、10秒程度で図のように「DEPLOY_SUCESS」になると思います。これでWebサービスは完成です。


Webサービス管理画面

Webサービスが完成すると、上の画面のサービス名のところがリンクになり、クリックするとWebサービス管理画面に遷移します。

以下のように、「Overview」「Implementation」「Test」の3つのタブが表示可能です。

この中でImplementationタブのScoring End-pointに関しては後ほど必要になるので、コピーしてテキストエディタなどで保存しておいて下さい。


Overview


Implementation


Test


Webサービスの呼出し

WebサービスをPythonから呼び出す場合は、以下のようなコーディングになります。

# scoring endpintの設定

scoring_endpoint = 'xxxx'

# 必要ライブラリのロード
import numpy as np
import urllib3, requests, json

# トークン取得
apikey = wml_credentials["apikey"]

# Get an IAM token from IBM Cloud
url = "https://iam.bluemix.net/oidc/token"

# Tokenを使ったヘッダの生成
headers = { "Content-Type" : "application/x-www-form-urlencoded" }
data = "apikey=" + apikey + "&grant_type=urn:ibm:params:oauth:grant-type:apikey"
IBM_cloud_IAM_uid = "bx"
IBM_cloud_IAM_pwd = "bx"
response = requests.post( url, headers=headers, data=data,
auth=( IBM_cloud_IAM_uid, IBM_cloud_IAM_pwd ) )
iam_token = response.json()["access_token"]
print('iam_token = ', iam_token)

# API呼出し用ヘッダ
# API呼出し用のヘッダには新しく`ML-Instance-ID`を追加する必要があります。
ml_instance_id = wml_credentials["instance_id"]
header = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + iam_token,
'ML-Instance-ID': ml_instance_id}

# Webサービス用パラメータの設定
payload_scoring = {"values": [list(data) for data in score_data]}

# Webサービスの呼出し
scoring = json.loads( requests.post(scoring_endpoint,json=payload_scoring, headers=header).text )

# 呼出し結果の取得
predict_list = np.array([item[0] for item in scoring['values']])

# 予測結果の表示
print(predict_list)

# 正解データの表示
print(score_labels)

# 正解率の表示
print("Evaluation report: \n\n%s" % metrics.classification_report(score_labels, predict_list))

次のような結果になれば成功です。

[5 2 8 0 1 7 6 3 2 1 7 8 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3 6 9 6 1 7 5 4 4 7

2 2 5 4 3 5 8 4 5 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 5
4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 8 8
7 5 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4
5 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 9 0 8 9 8]
[5 2 8 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4 3 1 4 0 5 3 6 9 6 1 7 5 4 4 7
2 2 5 7 9 5 4 4 9 0 8 9 8 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3
4 5 6 7 8 9 0 9 5 5 6 5 0 9 8 9 8 4 1 7 7 3 5 1 0 0 2 2 7 8 2 0 1 2 6 3 3
7 3 3 4 6 6 6 4 9 1 5 0 9 5 2 8 2 0 0 1 7 6 3 2 1 7 4 6 3 1 3 9 1 7 6 8 4
3 1 4 0 5 3 6 9 6 1 7 5 4 4 7 2 8 2 2 5 7 9 5 4 8 8 4 9 0 8 9 8]
Evaluation report:

precision recall f1-score support

0 1.00 1.00 1.00 16
1 1.00 1.00 1.00 19
2 1.00 1.00 1.00 17
3 0.93 0.72 0.81 18
4 0.95 0.90 0.92 20
5 0.82 1.00 0.90 18
6 1.00 1.00 1.00 18
7 1.00 0.95 0.97 19
8 0.81 1.00 0.89 17
9 1.00 0.89 0.94 18

avg / total 0.95 0.94 0.94 180

お疲れ様でした。以上で、一連のサンプルの実行は完了です。

Watson Studioの機能を使って簡単にsckit-learnのモデルがWebサービス化できることがわかったかと思います。


おまけ サンプルアプリ紹介

Python flaskの勉強を兼ねて、IBM Cloud上で稼働する簡単なサンプルアプリを作ってみました。

ソースと導入手順はWatson Machine Learning サンプルアプリを参照して下さい。

サーバーサイドのコードは以下の通りです。


server.py

#!/usr/bin/python

# -*- coding: utf-8 -*-
import requests
import os
from flask import Flask, request, render_template
from sklearn import datasets
import numpy
from PIL import Image
import urllib3
import json
from cfenv import AppEnv
from os.path import join, dirname
from dotenv import load_dotenv

# 認証情報の読み取り (.env または IBM Cloud上のバインド)
env = AppEnv()
pm20 = env.get_service(label='pm-20')
if pm20 is None:
dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)
wml_credentials = {
"url": os.environ.get("WML_URL"),
"apikey": os.environ.get("WML_APIKEY"),
"instance_id": os.environ.get("WML_INSTANCE_ID")
}
else:
wml_credentials = pm20.credentials

# スコアリングURLの読み取り (.env または環境変数)
scoring_url = os.environ.get("SCORING_URL")

# 手書き文字データの読み込み
digits = datasets.load_digits()
samples_count = len(digits.images)
score_data = digits.data[int(0.9*samples_count): ]
score_labels = digits.target[int(0.9*samples_count): ]
score_images = digits.images[int(0.9*samples_count): ]

app = Flask(__name__)

@app.route('/')
def top():
name = "Top"
return render_template('wml-sample.html', title='WML Test', name=name)

# 「テスト対象選択」ボタンが押された時の処理
@app.route('/select_image', methods=['GET'])
def select_image():
print('/select_image')
image_id = int(request.args.get('image_id', ''))
image = score_images[image_id]
label = score_labels[image_id]
pilImg = Image.fromarray(numpy.uint8((16 - image) * 15))
pilImg.save('static/image.png')
return str(label)

# 「予測」ボタンが押された時の処理
@app.route('/predict', methods=['GET'])
def predict():
print('/predict')
image_id = int(request.args.get('image_id', ''))
image_data = score_data[image_id]

# トークン取得
apikey = wml_credentials["apikey"]
# Get an IAM token from IBM Cloud
url = "https://iam.bluemix.net/oidc/token"
headers = { "Content-Type" : "application/x-www-form-urlencoded" }
data = "apikey=" + apikey + "&grant_type=urn:ibm:params:oauth:grant-type:apikey"
IBM_cloud_IAM_uid = "bx"
IBM_cloud_IAM_pwd = "bx"
response = requests.post( url, headers=headers, data=data,
auth=( IBM_cloud_IAM_uid, IBM_cloud_IAM_pwd ) )
iam_token = response.json()["access_token"]
print('iam_token = ', iam_token)

# API呼出し用ヘッダ
ml_instance_id = wml_credentials["instance_id"]
header = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + iam_token,
'ML-Instance-ID': ml_instance_id}

# Webサービスの呼出し
payload_scoring = {"values": [list(image_data)]}
print(payload_scoring)
scoring_response = requests.post(scoring_url, json=payload_scoring, headers=header)
scoring = json.loads( scoring_response.text )

# 結果の取得
predict_list = [item[0] for item in scoring['values']]
predict_str = str(predict_list[0])
print(predict_str)
return predict_str

@app.route('/favicon.ico')
def favicon():
return ""

port = os.getenv('VCAP_APP_PORT', '8000')

if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(port), debug=True)


また、クライアントサイドのコードの一部(wml-sample.html)も添付しておきます。


wml-sample.html

{% extends "layout.html" %}

{% block body %}
<h3>Watson Machine Learning Sample</h3>
画像分類機能
<hr>
画像ID(0-99)
<input text id="image_id" value='10'>
<br>
画像 <img id="image_pane" width="50" height="50">
<br>
正解 <label id="label1"></label>
<br>
予測 <label id="label2"></label>
<br>
<input type="submit" id="select" value="テスト対象選択">
<input type="submit" id="predict" value="予測">

<script>
$(function(){
$('#select').click(select_image)
});

$(function(){
$('#predict').click(predict)
});

function call_flask( type, url, data, success, error ) {
// API呼出し
$.ajax({
type: type,
url: url,
data: data,
contentType: "application/json; charset=utf-8",
success: success,
error: error
});
}

function select_callback(msg) {
src = "{{ url_for('static', filename='image.png') }}"
$('#image_pane').attr('src', src + '?' + new Date().getTime());
$('#label1').text(msg);
$('#label2').text('');
console.log(msg);
}

function predict_callback(msg) {
$('#label2').text(msg);
console.log(msg);
}

function select_image() {
image_id = Number($('#image_id').val());
console.log('select_image');
console.log('image_id: ' + image_id);
var data = {'image_id': image_id}
call_flask( 'GET', '/select_image', data, select_callback,
function(XMLHttpRequest,textStatus,errorThrown){alert('error');} );
}

function predict() {
image_id = Number($('#image_id').val());
console.log('call_predict');
console.log('image_id: ' + image_id);
var data = {'image_id': image_id}
call_flask( 'GET', '/predict', data, predict_callback,
function(XMLHttpRequest,textStatus,errorThrown){alert('error');} );
}

</script>

{% endblock %}


Github上のJupyter Notebookのリンク


関連記事

Watson Machine Learing チュートリアルを試してみる