scikit-learn
Watson
WML

Watson Machine Learning - Watson Studio Jupytet Notebook上のscikit-learn 機械学習モデルをデプロイする -

はじめに

Watson Machine Learning(WML)のサンプルアプリを参照しつつ、Watson Studio(旧IBM DSX)のJupyter Notebook上で作ったscikit-learn機械学習モデルをWMLにデプロイする手順を解説してみます。

(2018-02-08 後半のモデル登録、Webサービス化のコマンドをきれいに直しました)
(2018-02-08 IBM Cloud上で稼働するサンプルアプリ紹介を追加しました)
(2018-02-09 Github上のJupyter Notebookのリンクを追加しました)
(2018-04-26 新バージョンに対応して全面的に見直し)

モデルの説明

サンプルで作られる機械学習モデルは、手書き文字データを元に数字の分類を行うものです。
モデルは、SVM(Support Vector Machine)で作られていますが、前処理として標準化を行っています。
scikit-learnパイプライン機能を用いて、この2つの処理を結合したモデルをWMLにデプロイする形になります。
サンプルはたまたまパイプラインを使っていますが、これを使うことは必須ではなく、通常の単体のモデルクラスでも以下と同じ形のデプロイは可能です。

前提

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

  • Watson Studio(旧 IBM DSX)
  • Spark
  • Cloud Object Storage
  • Watson Machine Leaning

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

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

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

最初のステップはWatson Studio上で通常のやりかたで機械学習モデルを作ります。
前提として、sckit-learnを使ったモデルを使う必要があります。

Jupyter Notebookの作成

Watson Studio上で新しいJupyter Notebookを作成します。
名前はなんでもいいですがここでは"Watson ML Sample"としています。
その他のオプションは全部デフォルトとして下さい。

jupyter.png

データのロードと確認

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

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)

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

スクリーンショット 2018-02-07 19.40.41.png

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 Machine Learning独自の話になります。

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

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

ライブラリの導入

次の2つのコマンドで、Watson MLのリポジトリにアクセスするためのライブラリ最新版を導入します。

!rm -rf $PIP_BUILD/watson-machine-learning-client
!pip install watson-machine-learning-client --upgrade

資格情報の設定

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

ml-credenttials.png

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

wml_credentials={
  "url": "https://ibm-watson-ml.mybluemix.net",
  "username": "***",
  "password": "***",
  "instance_id": "***"
}

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

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

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)

次のコマンドでアップロードしたモデルの属性を確認することができます。

published_model_uid = client.repository.get_model_uid(published_model)
model_details = client.repository.get_details(published_model_uid)
print(json.dumps(model_details, indent=2))

大量に結果がかえってくるので結果出力は省略します。
次のコマンドでリポジトリに登録済みの機械学習モデルの一覧が取得可能です。

# リポジトリに登録済みモデルの一覧
client.repository.list_models()

以下のような結果がかえってくるはずです。

------------------------------------  -------------------------------  ------------------------  -----------------
GUID                                  NAME                             CREATED                   FRAMEWORK
8d105f5e-59e9-4af9-b502-e9fa6c0917c4  mnist1                           2018-04-16T23:59:05.732Z  tensorflow-1.5
a630875d-d183-4178-9cf7-4a6c79a8000b  CIFAR10                          2018-04-17T01:00:55.550Z  tensorflow-1.5
33aeec66-407e-4a31-b8c8-b599498f24e4  MNIST best model                 2018-04-24T13:50:51.040Z  tensorflow-1.5
f3834096-9aeb-4e84-8844-650150514d71  Tent-Sell-Predict                2018-04-26T10:57:55.153Z  wml-1.1
8fbeb37d-9c55-4cc4-aa98-4b336e8d2e30  Digits prediction model          2018-04-26T12:10:29.729Z  scikit-learn-0.19
------------------------------------  -------------------------------  ------------------------  -----------------

ここまでがうまくいくと、Watson Machine Learningのダッシュボード側にも、下の画面コピーのように登録したモデルが表示されます。

(一覧画面)
model-list-ui.png

(詳細画面)
model-list-ui2.png

リポジトリ登録済みモデルのロードと予測実行

次の手順は、リポジトリに登録したモデルをローカルに再ロードし、そのモデルを使って予測を行うためのものです。リポジトリからモデルをロードするためのキーとしてpublished_model_uidを使っています。

loaded_model = client.repository.load(published_model_uid)
test_predictions = loaded_model.predict(test_data[:10])
print(test_predictions)

print文の結果、以下のような結果が返ってくるはずです。

[4 0 5 3 6 9 6 4 7 5]

機械学習モデルのWebサービス化

クラウド上にアップロードしたモデルからWebサービスを作成します。

# WEBサービス作成
created_deployment = client.deployments.create(published_model_uid, "Deployment of scikit model")

# エンドポイントURLの取得
scoring_endpoint = client.deployments.get_scoring_url(created_deployment)
print(scoring_endpoint)

WEBサービス作成に成功すると、下の図にようにプロジェクトの「Deployment」タブに表示されるようになります。

(一覧表示)
deployments1.png

(詳細表示)
deployments2.png

Webサービスの呼出し

最後に作成したWEBサービスの呼出しテストを行います。

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

# ML Tokenの取得
headers = urllib3.util.make_headers(basic_auth='{}:{}'.\
        format(wml_credentials['username'], wml_credentials['password']))
url = '{}/v3/identity/token'.format(wml_credentials['url'])
response = requests.get(url, headers=headers)
mltoken = 'Bearer ' + json.loads(response.text).get('token')

# Tokenを使ったヘッダの生成
header_token = {'Content-Type': 'application/json', 'Authorization': mltoken}

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

# Webサービスの呼出し
scoring = json.loads( requests.post(scoring_endpoint,json=payload_scoring, headers=header_token).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

お疲れ様でした。以上で、一連のサンプルの実行は完了です。
IBM DSX上で動いているscikit-learnの機械学習モデルをWatson Machine Learning上のWebサービスとして稼働可能なことがおわかりいただけたかと思います。

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

Python flaskの勉強を兼ねて、IBM Cloud上で稼働する簡単なサンプルアプリを作ってみました。
ソースと導入手順はWatson Machine Learning サンプルアプリを参照して下さい。

wml-sample-1.png

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

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"),
        "access_key": os.environ.get("WML_ACCESS_KEY"),
        "username": os.environ.get("WML_USERNAME"),
        "password":  os.environ.get("WML_PASSWORD"),
        "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', ''))
    data = score_data[image_id]

    # Basic認証用ヘッダの生成
    auth = '{username}:{password}'.format(username=wml_credentials['username'], password=wml_credentials['password'])
    header_basic_auth = urllib3.util.make_headers(basic_auth=auth)
    url = '{}/v3/identity/token'.format(wml_credentials['url'])

    # Tokenの取得
    mltoken =  json.loads( requests.get(url, headers=header_basic_auth).text )['token']
    print(mltoken)
    header_token = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + mltoken}

    # Webサービスの呼出し
    payload_scoring = {"values": [list(data)]}
    print(payload_scoring)
    scoring_response = requests.post(scoring_url, json=payload_scoring, headers=header_token)
    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 チュートリアルを試してみる