Watson Machine Learning v2のREST APIをPythonで呼び出す

# はじめに
別記事 [2020年9月 Watson Machine Learning仕様変更まとめ](https://qiita.com/makaishi2/private/233d8be9cfb7889df4e0)の続編です。
コード解説が多くなるので、記事を別だしにしました。

# 参考リンク
下記のリンクの情報を参考にしています。

(API呼び出しサンプル)
https://github.com/IBM/watson-machine-learning-samples/blob/master/notebooks/python_sdk/deployments/custom_library/Use%20scikit-learn%20and%20custom%20library%20to%20predict%20temperature.ipynb

# サンプルアプリ
Githubにアップしたサンプルデータ [bank-train-jp-autoai.csv](https://raw.githubusercontent.com/makaishi2/sample-data/master/data/bank-train-jp-autoai.csv)をAutoAIを使ってモデル生成、デプロイまでやった結果のモデルが、呼び出し対象です。

# 予測用データの準備
最初に予測用データを準備しておきます。ここで説明することは、すべて通常のPython、Pandasでできることで、Watson Machine Learning固有の話はありません。

## データ読み込み
最初に検証用データを読み込んで、データフレームに保存します。

```py3
# 必要ライブラリの導入
!pip install japanize-matplotlib | tail -n 1

# 必要ライブラリのimport
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# 日本語化対応
import japanize_matplotlib #日本語化matplotlib

csv_url = 'https://raw.githubusercontent.com/makaishi2/sample-data/master/data/bank-test-jp-autoai.csv'

# データフレームに読み込み
df_bank_test = pd.read_csv(csv_url)

display(df_bank_test.head())
```

下記のような結果が返ってくるかと思います。

![スクリーンショット 2020-09-11 9.04.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/9dfada6b-921d-732e-18e4-122e5d129f21.png)

## 入力用データへの加工
np.nan値をNoneに置換したり、目的変数を落とすなどして、読み込んだデータを入力用に加工します。ここも、特にWatson API固有の話はないです。

```py3
df_sub = df_bank_test.copy()

# 目的変数列の削除
df_sub = df_sub.drop('今回販促結果', axis=1)

# np.nan値をNoneに置き換え
df_sub = df_sub.where((pd.notnull(df_sub)), None)

display(df_sub.head())
```

下のような結果になります。

![スクリーンショット 2020-09-11 9.08.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/eb2963aa-8192-1555-a9a9-62fa3429b26d.png)

## 項目名リスト(fields)の作成
データフレームから項目名の一覧を取得し、listに変換します。

```py3
# DataFrameから項目名Listの生成
fields = df_sub.columns.tolist()
print(fields)
```

こんな結果になります。

```
['年齢', '職業', '婚姻', '学歴', '債務不履行', '平均残高', '住宅ローン', '個人ローン', '連絡手段', '最終通話日', '最終通話月', '最終通話秒数', '通話回数_販促中', '前回販促後_経過日数', '通話回数_販促前', '前回販促結果']
```

## 入力データリスト(values)の作成
入力データは、複数のデータを同時に予測させることができます。そのため、入力データの書式はlistのlistとなっています。
データフレームからこのデータを作るためのコードは以下の通りです。
(いきなり大量データで呼び出しがいかないよう、最初は2行分だけ予測するようにしています。)


```py3
# DataFrameから入力用Listの生成
#values = df_sub.values.tolist()
# テスト用
values = df_sub.head(2).values.tolist()
print(values[:2])
```

次のようなprint文の出力になります。

```
[[39, 'services', 'single', 'secondary', 'いいえ', 1163, 'はい', 'いいえ', 'cellular', 7, 'may', 432, 3, 363, 2, 'other'], [37, 'self-employed', 'married', 'secondary', 'いいえ', 160, 'いいえ', 'いいえ', 'unknown', 6, 'jun', 107, 1, -1, 0, 'unknown']]
```

## scoring_payload変数の組立て
準備した項目名一覧(fields)とデータ(values)から、スコアリング用の入力変数であるscring_payloadを組立てます。具体的なコードは下記になります。

```py3
# payload変数の組立て
scoring_payload = {"input_data": [{
   "fields": fields, 
    "values": values
}]}

print(payload_scoring)
```

print文の結果は下記のとおりです。

```
{'input_data': [{'fields': ['年齢', '職業', '婚姻', '学歴', '債務不履行', '平均残高', '住宅ローン', '個人ローン', '連絡手段', '最終通話日', '最終通話月', '最終通話秒数', '通話回数_販促中', '前回販促後_経過日数', '通話回数_販促前', '前回販促結果'], 'values': [[39, 'services', 'single', 'secondary', 'いいえ', 1163, 'はい', 'いいえ', 'cellular', 7, 'may', 432, 3, 363, 2, 'other'], [37, 'self-employed', 'married', 'secondary', 'いいえ', 160, 'いいえ', 'いいえ', 'unknown', 6, 'jun', 107, 1, -1, 0, 'unknown']]}]}
```

これで、データ側の準備は完了です。次は、Watson MLへの呼び出し手順となります。

# Watson ML呼び出し

## 必要ライブラリの導入
次のライブラリが必要なので、pipコマンドで事前に導入します。今後仕様変更が頻繁に起きることが予想されるので、-Uオプションをつけて常に最新のものを使うようにして下さい。

```py3
# 必要ライブラリの導入
!pip install -U ibm-watson-machine-learning | tail -n 1
```

## ロケーション、APIKEYの設定
最新のWatson ML APIを使うために必要な資格情報はロケーションとAPIKEYです。

ロケーションに関しては

```
$ ibmcloud resource service-instance <instance_name>
```

の結果で、指定して下さい。
このコマンドの結果サンプルが以下になります。下記の出力中**「場所」**の行が「ロケーション」に該当しています。

```
$ ibmcloud resource service-instance pm-20-lf
AKAISHI@jp.ibm.com としてアカウント MASANORI AKAISHI's Account の リソース・グループ default のサービス・インスタンス pm-20-lf を取得しています...
OK
                           
名前:                   pm-20-lf   
ID:                     crn:v1:bluemix:public:pm-20:us-south:a/0960d86217f3b669ae4408f3b4deaa08:fef17f33-d57a-4823-9ffb-bb74525b4cc2::   
GUID:                   fef17f33-d57a-4823-9ffb-bb74525b4cc2   
場所:                   us-south   
サービス名:             pm-20   
サービス・プラン名:     v2-standard   
リソース・グループ名:   default   
状態:                   active   
タイプ:                 service_instance   
サブタイプ:                
作成日時:               2020-09-03T23:26:06Z   
作成元:                 akaishi@jp.ibm.com   
更新日時:               2020-09-03T23:26:06Z   
最後の操作:                                
                        状況         create succeeded      
                        メッセージ   Completed create instance operation      
```

APIKEYに関しては、従来はインスタンス管理画面から取得したのですが、v4で方式が変わりました。
(別記事　[「2020年9月 Watson Machine Learning仕様変更まとめ」](https://qiita.com/makaishi2/private/233d8be9cfb7889df4e0)　参照)
下記のリンク先から取得するようにして下さい。(他のサービスと共通のものになった)

https://cloud.ibm.com/iam/apikeys


```py3
# ロケーションの指定
# ロケーションは下記のコマンドの結果得られたものを使う
#
# $ ibmcloud resource service-instance

location = 'us-south'

# APIKeyの指定
# API key は次のリンク先から生成する
#
# https://cloud.ibm.com/iam/apikeys

apikey = "xxxx"

wml_credentials = {
    "apikey": apikey,
    "url": 'https://' + location + '.ml.cloud.ibm.com'
}
```

## APIClientオブジェクト生成
今、調べた2つの情報からAPIClientオブジェクトを生成します。
下記のコードが正常終了すれば、認証に成功していることになります。

```py3
from ibm_watson_machine_learning import APIClient

client = APIClient(wml_credentials)
```

## space_id の取得、設定

これから紹介するAPIを利用する場合、デプロイメントスペースのIDであるspace_idを設定する必要があります。
space_idは、デプロイメントスペースの管理画面から取得可能です。
もう一つの方法は、下記にあるようなlist関数を呼び出す方法です。

```py3
# 次のAPIでも取得可能です。
# deployment space idの確認
client.spaces.list()
```

いずれかの方法でIDを調べて、その値を以下のAPIで設定します。

```py3
# space_idは、deplyment space管理画面の「設定」タブから取得します。
space_id = 'xxxx'

# デフォルトのdeployment spaceの指定をします。
client.set.default_space(space_id)
```

設定が正常終了すると ``'SUCCESS'``と結果が返ってきます。

## デプロイメントIDの確認、設定
デプロイメントIDとは、Webサービスに対して振られるIDです。deplymnet space の「デプロイメント」タブで取得できるので、その値を以下のコードに設定します。

```py3
# deplyment_uid はdeplymnet space の「デプロイメント」タブで取得します。
deployment_uid = "xxxx"
```

## 予測関数の呼び出し
これで、予測のためのすべての準備は整いました。
あとは、以下の関数呼び出しで、予測を行います。
うまく呼び出せた場合は、最後のprint文で予測結果が表示されるはずです。

```py3
# REST APIはscore関数で呼び出します。
predictions = client.deployments.score(deployment_uid, payload_scoring)

print(json.dumps(predictions, indent=2, ensure_ascii=False))
```

呼び出しがうまくいくと、こんな結果になります。


```
{
  "predictions": [
    {
      "fields": [
        "prediction",
        "probability"
      ],
      "values": [
        [
          "失敗",
          [
            0.5913717715449178,
            0.4086282284550822
          ]
        ],
        [
          "失敗",
          [
            0.9487114955185982,
            0.051288504481401744
          ]
        ]
      ]
    }
  ]
}
```

# Watson ML呼び出し (ライブラリを使わない方法)

Watson MLは、今説明したライブラリ``APIClient``を使わず、Python標準の関数だけで呼び出すことも可能です。次に、この方法での呼び出しを試してみます。

## Tokenの取得

この場合、最初のステップはTokenの取得です。

```py3
import urllib3, requests, json

# トークン取得
apikey = "xxxx"
url = "https://iam.cloud.ibm.com/identity/token"

headers = { "Content-Type" : "application/x-www-form-urlencoded",
          "Accept" : "application/json" }

data    = "apikey=" + apikey + "&grant_type=urn:ibm:params:oauth:grant-type:apikey"

response  = requests.post( url, headers=headers, data=data)
iam_token = response.json()["access_token"]
print('iam_token = ', iam_token)
```

APIKeyは先ほどと同じものを利用します。
うまくTokenが取得できると、最後のprint文で取得したTokenが表示されるはずです。

### APIの呼び出し

最初にヘッダを組み立てます。ヘッダには、今取得したTokenを組み込みます。

```py3
header = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + iam_token}
```

次にURLを組み立てます。scoring_urlは、デプロイメントの管理画面から取得できます。
注意すべき点として、ここで取得したURLにバージョンをパラメータとして追加すべき点があります。
(このあたりの詳細な話は下記リンク先のドキュメントを参照して下さい)
https://cloud.ibm.com/apidocs/machine-learning?code=python#versioning

具体的なコードは以下のとおりです。


```py3
scoring_url = "xxxx"
scoring_url2 = scoring_url + '?version=2020-09-01'
```

データ部分に関しては、先ほど準備した``paylod_scoring``がそのまま使えます。
最終的なAPI呼び出しのコードは以下のとおりです。

```py3
response_scoring = requests.post(scoring_url2, json=payload_scoring, headers=header)

# 結果確認
print(response_scoring)
```

最後のprint文の結果が``<Response [200]>``となっていれば呼び出しに成功しています。

以下のように結果も取得できるはずです。

```
print(response_scoring.text)
```

```
{
  "predictions": [{
    "fields": ["prediction", "probability"],
    "values": [["失敗", [0.5913717715449178, 0.4086282284550822]], ["失敗", [0.9487114955185982, 0.051288504481401744]]]
  }]
}
```