AutoAIでお手軽機械学習(その5) API編

# はじめに
「AutoAIでお手軽機械学習」シリーズの第5弾です。
AutoAIは、元々UIで機械学習モデルを作るツールなのですが、最近APIも用意されて、Pythonからモデル生成機能を呼び出すことが可能になりました。
当記事は、その事例の紹介となります。
前4つの記事は以下のとおりです。

* [AutoAIでお手軽機械学習(その1) 準備編](https://qiita.com/makaishi2/items/d63f0bbac32a975c391e)
* [AutoAIでお手軽機械学習(その2) モデル構築編](https://qiita.com/makaishi2/items/d6cd449f7a9f7186a833)
* [AutoAIでお手軽機械学習(その3) Webサービス編](https://qiita.com/makaishi2/items/e5ad4d068bd364fdc056)
* [AutoAIでお手軽機械学習(その4) Jupyter Notebook編](https://qiita.com/makaishi2/items/0c59ae675362c9f85640)


# 元ネタ
Githubに公開されている[AutoAI and COVID-19 with TEST DATA](https://dataplatform.cloud.ibm.com/analytics/notebooks/v2/0ce2d6b2-3b4a-454b-a040-c1a6b8a101c5/view?access_token=1eb809c7cd7fc237ecdf734d7fe67375e79360652e38b85073f5636c29a05536)が元ネタです。
このコードのコメントを日本語化したものを、次のリンクに公開しました。

https://github.com/makaishi2/sample-notebooks/blob/master/autoai/AutoAI-COVID19.ipynb

また、このNotebookは(英語ですが)以下のブログでも解説されているので、こちらも参照して下さい。

https://medium.com/ibm-watson/watson-studio-autoai-python-api-and-covid-19-data-78169beacf36

# モデルのユースケース
モデルのユースケースは、「コロナの感染者数予測」です。データはジョンズ・ホプキンス大学でリアルに公開されているデータを利用します。
オリジナルのコードではポーランドの予測をしていましたが、折角なので絞り込み対象を「日本」に変更しています。
モデルの種類としては、感染者数の予測なので、「回帰」モデルとなります。

以下、オリジナルのnotebookに沿った章立てで説明をしていきます。

# 1. セットアップ

## WMLの資格情報

```py3
wml_credentials = {
                   "url": "xxx",
                   "apikey":"xxx",
                   "instance_id": "xxx"
                  }
```

まず、WMLの資格情報をNotebookに設定します。資格情報の入手手順は下記リンク先を参考にして下さい。

https://github.com/makaishi2/sample-data/blob/master/notebooks/bank-marketing-autoai-test.ipynb

## ライブラリの導入

次に必要ライブラリを導入します。Notebook内の関連するコードは以下のとおりです。

```py3
!pip uninstall -y watson-machine-learning-client-V4 | tail -n 1
```

```py3
!pip install -U watson-machine-learning-client-V4 | tail -n 1
!pip install -U autoai-libs | tail -n 1
```

```py3
!pip show watson-machine-learning-client-V4e
```

## データ入手
ジョンズ・ホプキンス大学システム科学工学センターで公開されているデータを入手します。

```py3
import pandas as pd
import numpy as np

confirmed_df = pd.read_csv('https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv')
confirmed_df.head()
```

こんな感じの出力が得られます。

![スクリーンショット 2020-09-01 18.15.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/53147ca8-b06b-13b9-7693-0b4facf4436b.png)

## 日本の感染者数を時系列形式で抽出
次のコードで日本の感染者数を時系列形式で入手します。

```py3
country = 'Japan'

confirmed = confirmed_df.loc[:, confirmed_df.keys()[4]:confirmed_df.keys()[-1]]
days_since_1_22 = np.array([i for i in range(len(confirmed.keys()))]).reshape(-1, 1)
country_cases = [confirmed_df[confirmed_df['Country/Region']==country][i].sum() for i in confirmed.keys()]
training_df = pd.DataFrame(days_since_1_22, columns=['Day'])
training_df['Elapsed_days'] = training_df.Day
training_df['Cases'] = country_cases
training_df = training_df.fillna(0)
test_df = training_df.tail(5)
training_df = training_df.iloc[:-5]
training_df
```

最終結果はこんな表データになります。

![スクリーンショット 2020-09-01 18.17.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/7fc0f047-4d8a-76fc-f205-6ea4a86b68f5.png)

## Cloud Object Storageに新しいデータセットをcsvとしてアップロードする
APIでモデルを作るため、こうやって作った学習データをCSVファイルにする必要があります。さらに、このCSVファイルをIBM Cloudの別サービスであるCloud Object Storageにアップロードします。
具体的なコードは以下のとおりです。
``project_id``と``project_access_token``の入手方法についてはNotebook内に説明がありますので、そちらを参照して下さい。

```py3
project_id = 'xxx`
project_access_token = 'xxx`

from project_lib import Project

project = Project(project_id=project_id, project_access_token=project_access_token)
pc = project.project_context

csv_details = project.save_data(data=training_df.to_csv(index=False),file_name='covid19_training.csv',overwrite=True)

storage_meta = project.get_storage_metadata()
```

# 2. オプティマイザー定義
これで事前準備は完了です。いよいよ、モデル生成のための「オプティマイザー」の定義に入ります。

## トレーニングデータ定義
前のステップでObject StorageにアップロードしたCSVファイルのリファレンスを定義します。
具体的なコードは以下のとおりです。

```py3
filename = csv_details['file_name']
bucket_name = csv_details['bucket_name']

from watson_machine_learning_client.helpers import DataConnection, S3Connection, S3Location

csv_data_conn = DataConnection(
    connection=S3Connection(endpoint_url=storage_meta['properties']['endpoint_url'],
                            access_key_id=storage_meta['properties']['credentials']['editor']['access_key_id'],
                            secret_access_key=storage_meta['properties']['credentials']['editor']['secret_access_key']),
    location=S3Location(bucket=bucket_name,
                        path=filename))

training_data_reference=[csv_data_conn]
```

## オプティマイザの設定
UIの場合、画面で設定する情報を、パラメータの形で指定していきます。
具体的なコードは以下の形になります。

```py3
from watson_machine_learning_client.experiment import AutoAI

experiment = AutoAI(wml_credentials)

pipeline_optimizer = experiment.optimizer(
    name='COVID-19 Japan - AutoAI',
    prediction_type=AutoAI.PredictionType.REGRESSION,
    daub_include_only_estimators=['RidgeEstimator', 'LinearRegressionEstimator'],
    prediction_column='Cases',
    scoring=AutoAI.Metrics.R2_SCORE,
)
```

``get_params()``でオプティマイザのすべてのパラメータを確認できます。

```
pipeline_optimizer.get_params()
```

結果は、以下の形になります。

```
{'name': 'COVID-19 Japan - AutoAI',
 'desc': '',
 'prediction_type': 'regression',
 'prediction_column': 'Cases',
 'scoring': 'r2',
 'test_size': 0.1,
 'max_num_daub_ensembles': 1,
 't_shirt_size': 'l',
 'train_sample_rows_test_size': None,
 'daub_include_only_estimators': ['RidgeEstimator',
  'LinearRegressionEstimator'],
 'data_join_graph': False,
 'csv_separator': ',',
 'excel_sheet': 0,
 'positive_label': None,
 'run_id': None}
```

ちなみにまだ工事中のようですが、APIリファレンスは下記です。サンプル以外のパラメータを指定したい場合は、こちらを参照して下さい。

https://wml-api-pyclient-dev-v4.mybluemix.net/#autoai-modules-beta-for-ibm-cloud


# 3. モデル構築
これで学習のためのすべての準備は完了です。あとは、optimizerに対してfit関数を呼び出すと、モデル構築がはじまります。
この時の引数として1で準備したCSVファイルのリファレンスを利用します。
また、``background_mode``というパラメータもあり、学習を同期・非同期どちらで行うかが指定できます。

```py3
run_details = pipeline_optimizer.fit(
            training_data_reference=training_data_reference,
            background_mode=False)
```

# 4. パイプラインの比較とテスト
学習が完了したら、できたモデル(パイプライン)間の比較が可能です。
実装とその結果を以下に示します。

```py3
summary = pipeline_optimizer.summary()
summary
```

![スクリーンショット 2020-09-01 18.41.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/1432f461-9c54-cb3b-b234-1539f5d4743b.png)

## 可視化
また、次のコードで、パイプライン間の比較をグラフで行うこともできます。

```py3
%matplotlib inline

summary.holdout_r2.plot();
```

結果は以下のとおりです。

![スクリーンショット 2020-09-01 18.44.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/158928c4-3220-b663-14fe-16e349f98abb.png)

## 最適なパイプラインモデルを取得する
次のコードにより、最適なパイプラインをNotebook上に取得することができます。
また、'Pipeline_1'のような参照方法でそれ以外のパイプラインも取得できます。

```py3
best_pipeline = pipeline_optimizer.get_pipeline()
pipeline1 = pipeline_optimizer.get_pipeline(pipeline_name='Pipeline_1')
```

## パイプラインの可視化
Notebook上に取得したパイプラインはvisualizeという関数を持っていて、処理プロセスの可視化が可能です。
コードと、結果を以下に示します。

最適なパイプラインの可視化

```py3
best_pipeline.visualize()
```
![スクリーンショット 2020-09-01 18.49.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/5379b11d-7fce-9ff8-e72f-318ac7d0078d.png)


最初のパイプラインの可視化

```py3
pipeline1.visualize()
```

![スクリーンショット 2020-09-01 18.49.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/a10c6853-8e71-aec7-dd12-b3cb16d13648.png)

##  パイプラインのコード化

パイプラインはpretty_print関数も持っていて、パイプライン構築のためのコードを生成してくれます。
実装と結果を以下に示します。

```py3
best_pipeline.pretty_print(ipython_display=True)
```


```py3
from autoai_libs.transformers.exportable import ColumnSelector
from lale.lib.autoai_libs import NumpyColumnSelector
from lale.lib.autoai_libs import FloatStr2Float
from lale.lib.autoai_libs import NumpyReplaceMissingValues
from lale.lib.autoai_libs import NumImputer
from lale.lib.autoai_libs import OptStandardScaler
from lale.lib.autoai_libs import float32_transform
from lale.lib.autoai_libs import TA1
import numpy as np
import autoai_libs.utils.fc_methods
from lale.lib.autoai_libs import FS1
import autoai_libs.cognito.transforms.textras_methods
from lale.lib.sklearn import Ridge
import lale
lale.wrap_imported_operators()

column_selector = ColumnSelector(columns_indices_list=[1])
numpy_column_selector = NumpyColumnSelector(columns=[0])
float_str2_float = FloatStr2Float(dtypes_list=['int_num'], missing_values_reference_list=[])
numpy_replace_missing_values = NumpyReplaceMissingValues(filling_values=float('nan'), missing_values=[])
num_imputer = NumImputer(missing_values=float('nan'), strategy='median')
opt_standard_scaler = OptStandardScaler(num_scaler_copy=None, num_scaler_with_mean=None, num_scaler_with_std=None, use_scaler_flag=False)
ta1_0 = TA1(fun=np.sqrt, name='sqrt', datatypes=['numeric'], feat_constraints=[autoai_libs.utils.fc_methods.is_non_negative, autoai_libs.utils.fc_methods.is_not_categorical], col_names=['Elapsed_days'], col_dtypes=[np.dtype('float32')])
fs1_0 = FS1(cols_ids_must_keep=range(0, 1), additional_col_count_to_keep=4, ptype='regression')
ta1_1 = TA1(fun=np.square, name='square', datatypes=['numeric'], feat_constraints=[autoai_libs.utils.fc_methods.is_not_categorical], col_names=['Elapsed_days', 'sqrt(Elapsed_days)'], col_dtypes=[np.dtype('float32'), np.dtype('float32')])
fs1_1 = FS1(cols_ids_must_keep=range(0, 1), additional_col_count_to_keep=4, ptype='regression')
ta1_2 = TA1(fun=autoai_libs.cognito.transforms.textras_methods.cube, name='cube', datatypes=['numeric'], feat_constraints=[autoai_libs.utils.fc_methods.is_not_categorical], col_names=['Elapsed_days', 'sqrt(Elapsed_days)', 'square(Elapsed_days)', 'square(sqrt(Elapsed_days))'], col_dtypes=[np.dtype('float32'), np.dtype('float32'), np.dtype('float32'), np.dtype('float32')])
fs1_2 = FS1(cols_ids_must_keep=range(0, 1), additional_col_count_to_keep=4, ptype='regression')
ridge = Ridge(alpha=3.739450909578426e-05, max_iter=53, random_state=33, solver='cholesky', tol=3.913158955537585e-05)
pipeline = column_selector >> numpy_column_selector >> float_str2_float >> numpy_replace_missing_values >> num_imputer >> opt_standard_scaler >> float32_transform() >> ta1_0 >> fs1_0 >> ta1_1 >> fs1_1 >> ta1_2 >> fs1_2 >> ridge
```

## トレーニングデータとホールドアウトデータをCOSから読み取る
オプティマイザーからは、トレーニングデータとホールドアウトデータを取得することも可能です。
実装は以下の通りです。

```py3
train_df, holdout_df = pipeline_optimizer.get_data_connections()[0].read(with_holdout_split=True)

holdout_X = holdout_df.drop(['Cases'], axis=1).values
holdout_y = holdout_df.Cases.values
```

## パイプラインモデルをローカルでテストする
sckit-learnのパイプランを取得することもできるので、ローカル環境でモデルの予測・評価ができます。
実装は、以下のとおりです。

```py3
model = pipeline_optimizer.get_pipeline(astype='sklearn')

from sklearn.metrics import r2_score

predicted_y = model.predict(holdout_X)
score = r2_score(predicted_y, holdout_y)
print(f'r2_score {score:.1%}')
```

```
r2_score 94.9%
```

# 5. デプロイとスコアリング
構築したモデルをWatson Machine Learningにデプロイし、Webサービスとして呼び出してスコアリングをしてみます。
最初のコードは、モデルをWebサービスとしてデプロイするためのコードです。

## デプロイメントの作成

```py3
from watson_machine_learning_client.deployment import WebService

service = WebService(wml_credentials)

service.create(
    experiment_run_id=pipeline_optimizer._engine._current_run_id,
    model=best_pipeline, 
    deployment_name="COVID-19 AutoAI WebService")
```

正常に処理されると以下のようなメッセージが表示されます。

```
Preparing an AutoAI Deployment...
Published model uid: ad36b2be-0394-4ca1-9697-336b3ea49d63
Deploying model ad36b2be-0394-4ca1-9697-336b3ea49d63 using V4 client.


#######################################################################################

Synchronous deployment creation for uid: 'ad36b2be-0394-4ca1-9697-336b3ea49d63' started

#######################################################################################


initializing.......
ready


------------------------------------------------------------------------------------------------
Successfully finished deployment creation, deployment_uid='a8993acd-0519-48ae-9245-cc17bd41f35a'
------------------------------------------------------------------------------------------------

```

配置済みのオブジェクトはprint関数で、その内容が表示できます。

```py3
print(service)
```

```
name: COVID-19 AutoAI WebService, id: a8993acd-0519-48ae-9245-cc17bd41f35a, scoring_url: https://us-south.ml.cloud.ibm.com/v4/deployments/a8993acd-0519-48ae-9245-cc17bd41f35a/predictions, asset_id: ad36b2be-0394-4ca1-9697-336b3ea49d63
```

## ウェブサービスのスコアリング
デプロイされたパイプライン上で score() を呼び出すことでスコアリングリクエストを行うことができます。

```py3
predictions = service.score(payload=holdout_df.drop(['Cases'], axis=1).iloc[:10])
predictions
```

```
{'predictions': [{'fields': ['prediction'],
   'values': [[17298.03515625],
    [39230.00390625],
    [57713.87890625],
    [-248.7548828125],
    [-2589.3232421875],
    [36067.33203125],
    [15313.1142578125],
    [21764.03125],
    [52552.86328125],
    [49318.40234375]]}]}
```

## 予測値と実測値の比較
予測値と実測値の比較をしてみます。

```py3
full_df = training_df.append(test_df)
predicted_cases = model.predict(full_df.drop(['Cases'], axis=1).values)
df = pd.DataFrame(predicted_cases, columns=['Predicted'])
df['Cases'] = full_df.Cases.values
df['Day'] = full_df.Day.values
df = df.sort_values(by=['Day'])
df
```

![スクリーンショット 2020-09-01 19.06.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/6502be3d-6e5b-7fb9-9f19-4e5a248552e8.png)

## plotlyを使用して可視化する
最後に予測値と実測値の可視化をしてみます。
このサンプルでは``plotly``というライブラリを利用しています。

```py3
!pip install -U plotly
```

```py3
import plotly.graph_objects as go
import plotly.offline as pyo
pyo.init_notebook_mode()

fig = go.Figure()
fig.add_trace(go.Scatter(x=df.Day, y=df.Cases, mode='markers', name='Actual train'))
fig.add_trace(go.Scatter(x=holdout_df.Day, y=holdout_df.Cases, mode='markers', marker_size=10, name='Actual holdout'))
fig.add_trace(go.Scatter(x=test_df.Day, y=test_df.Cases, mode='markers', marker_size=10, name='Actual test'))
fig.add_trace(go.Scatter(x=df.Day, y=df.Predicted, mode='lines', name='Predicted'))
        
fig.update_layout(title="COVID19 AutoAI model for Japan",
                  xaxis_title="day",
                  yaxis_title="confirmed cases count",
                  font=dict(family="Courier New, monospace", size=18, color="#7f7f7f"))
fig.show()
```

結果は以下のとおりです。

![スクリーンショット 2020-09-01 19.15.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206130/4ccdbd88-2b60-4499-974a-401868a81dc9.png)

お疲れ様でした。以上で、チュートリアルは完了となります。