【GCP】Node.jsでVertex AI AutomlのAPIを叩く方法【機械学習】

<h1>はじめに</h1>
今回業務でVertexAI Automlを使用する機会がありましたので、備忘録がてらここに記しておきます。

テストで動かしただけなので自分のローカル環境からAPIを叩けるようになるまでが今回のゴールとしています。
GCPをほとんど触ったことのない状態からのスタートだったので適宜色々調べながら実装を行いました。
なので自分みたいな「なにもわからん！」みたいな人にも分かるように書けたら良いなと思っています。
経験者の方は知ってる所は飛ばしながら読んでいただけると幸いです。
それではいってみましょ〜！

<h1>Why GCP? Vertex AI?</h1>
<h3>GCP（GoogleCloud）とは</h3>
Googleが提供しているクラウドコンピューティングサービス（クラウド）になります。

要はサーバーとかネットワークとかその他諸々便利機能を誰でも登録してお金さえ払えば（従量課金）使えるようにしてくれている訳です。
最近呼び方がGCPからGoogleCloudに変わったみたいで、Google Cloudの方がより広義で広い意味で使われてるっぽいです。
まあでもエンジニア界隈はみんなGCPって言ってますね。
<h3>VertexAI Automlとは</h3>
Vertex AIはGoogle Cloudの機械学習関連のサービスを統合したプラットフォームです。

サクッとまとめると、
1.テキストとか表とか画像とかなんでもデータを取り込んで、
2.そのデータに欠損とかノイズとかエラー値がある場合はそれを良い感じにして、
3.機械学習で成長予測とか出して、
4.それがちゃんと機能してるか自分で復習して、
5.デプロイしてそこにデータを与えると予測結果を返してくれる。
と言う優れものです。
Auto mlって何やねんと言う話ですが、Automlとは（Automated Machine Learning）の略称で、
上記に列挙したような内容をすべて自動でやってくれると言う訳です。
※手動でやる方法も選べますが上級者向きなので今回は割愛
<h1>全体の流れ</h1>
最低限の座学を学んだあとは全体の流れを頭に入れてから実装に移りたいと思います。
流れとしては、

１）Vertex AIを使えるようにする。
２）対象となるデータをデータセットに登録する。
３）データセットを指定してAutoMLの学習処理を実行する。
４）学習済みモデルを使ってエンドポイントの作成を行う。
５）Node.jsでエンドポイントを叩いてデータを取得する。
６）放っておくとお金が掛かり続けるので後片付け。
この流れで進めて行きたいと思います。
では具体的な流れを見ていきましょう

<h1>１）Vertex AIを使えるようにする。</h1>
さあまずはGoogle CloudのHome画面を開いてプロジェクトを選んでください。（お金が数千円かかるので無料枠だと使えません）

![HOME画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/0b012603-bc61-bfa3-5308-437324ea7b63.png)
選べましたら左上のハンバーガーメニューを開き、一番下の「その他のプロダクト」のプルダウンを開き下にスクロールすると人工知能の欄に「Vertex AI」があるのでそちらを選びます。
※右のピンアイコンをクリックすると上部に固定できますので便利です。
最初は「APIを有効にする」と言うボタンが出てくるかと思いますので有効にしてください。
するとこのVertex AIのメイン画面に遷移すると思われます。
![スクリーンショット 2022-12-13 10.37.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/51e9d7bb-7fbd-e51a-91b5-4e136d90b85f.png)
ここまで出来たら次のステップに移ります。
<h1>２）対象となるデータをデータセットに登録する。</h1>
次はAutoMLで利用するデータをVertex AIのデータセットに登録します。
Vertex AIはデータそのものを内部で保持するわけではなく、CloudStorageやBigQueryといったGoogleCloud上にデータを保存しておき、 そのデータの中からVertex AIで利用するデータを紐づけると言う形になっています。
ここのサイドバーメニュー画面から「データセット」を選んでください。

![スクリーンショット 2022-12-13 10.49.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/75d3de41-df53-fc51-7e36-a7ab28b28f4e.png)
選択できたら上部にある「作成」を押します。
![スクリーンショット 2022-12-13 10.51.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/96a148ca-8ea7-c784-1b9c-4ac3b97136f1.png)
作成画面では、「データセット名」と「データタイプと目標の選択」を設定します。 データセット名は任意の名前で問題ありません。
データタイプは項目にある通り「画像」や「テキスト」などのデータの種類、目標は「回帰/分類」などの機械学習でどのような予測を行うかの種類を選択します。
今回は、「表形式」のデータタイプに対して「回帰/分類」を行うためのデータセットを作成します。
リージョン（データセンターが設置されているエリア）はこのままで問題ありません。
<h3>データセットに対してどのデータを紐づけるかを設定</h3>
表形式のデータの場合、Cloud Storageに保存されたCSVファイルかBigQueryをデータソースとして選択できます。

![スクリーンショット 2022-12-13 10.56.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/dd8b1839-fca2-cc58-7d6f-2e9945d84b56.png)

今回使用するデータセットですが、デフォルトでCloudStorageに入っていたと言う人はそれを使ってもらえたら大丈夫なんですが、自分の場合はデータが無かったのでこちらから取ってきました。
・Kaggle
https://www.kaggle.com/datasets/camnugent/california-housing-prices
登録が必要ですが、無料でサンプルCSVをダウンロードすることができます。![スクリーンショット 2022-12-13 12.15.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/238eb4f0-5ce0-bd9c-3a65-1557a74ef680.png)

使用するデータは「カリフォルニア州の住宅価格の中央値」になります。![スクリーンショット 2022-12-13 11.07.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/f673322a-7912-732c-7d8a-111506188324.png)
これでAutoMLから利用可能なデータセットの準備は完了です。
<H1>３）データセットを指定してAutoMLの学習処理を実行する。</h1>
作成したデータセットを使ってAutoMLの処理を実行します。

サイドバーメニューのトレーニングから作成をクリックします。![スクリーンショット 2022-12-13 11.09.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/52ae6849-f6fc-39a4-4dec-fd98d616360c.png)
この画面の場合は下のテーブルにもうトレーニングが完了したデータが入っていますが、初めての場合はこちらは入ってないかと思います。
こちらの「トレーニング方法」でいくつかの項目を設定します。
<h3>トレーニング方法</h3>
<ul>
<li>Dataset : 先ほど作成したデータセット</li>
<li>Objective : 実行するタスク(今回は住宅価格の数値予測なのでRegression(回帰)を使用)</li>
<li>Model training metohd: 今回はAutoMLを使用</li>
</ui>

![スクリーンショット 2022-12-13 11.15.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/c1ae9002-6eaa-2c75-6d2b-8aa63f2361eb.png)
次の「モデルの詳細」でトレーニングの作成と目的変数（成長予測で欲しい値）の設定を行います。
<h3>モデルの詳細</h3>
今回は新たに作成するもののため、「新しいモデルのトレーニング」を選択します。 Target columnでは、目的変数となる列名を選択します。
今回は「median_house_value」としています。
「Export test dataset to BigQuery」にチェックを入れると、テストデータをBigQueryに保存しておくことも可能です。今回は使わないのでチェック無しで行きます。

![スクリーンショット 2022-12-13 11.20.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/ccf5c532-1425-d62c-cd9d-ce9c9530a7b0.png)
<h3>トレーニングオプション</h3>
「トレーニングオプション」では説明変数（列名）に関する設定を行います。 各列の項目にチェックを入れることで、その列を利用します

![スクリーンショット 2022-12-13 11.26.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/5d23defd-04ea-9aff-c101-66134569ebb1.png)
<h3>コンピューティングと料金</h3>

最後に「コンピューティングと料金」で最大でどのくらい学習を行うかの設定をします。
ここで設定した値が最大で発生する料金となります。
まだ学習できる余地があってもここで決められたノード時間で処理を中断させることで結果とコストのトレードオフをユーザが設定できます。
この設定は"最大"ノード時間となっています。
「早期停止を有効にする」オプションを有効にすることで、これ以上の向上が見込めないと判断された場合は途中で処理を終了することが可能なため、余分なコストは発生しません。

![スクリーンショット 2022-12-13 11.27.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/48ad1fa6-3bcf-2b86-6e6c-57306073d4a8.png)
このノード時間と言うものは内部の単位なので、実際の処理にかかる時間を明記するわけではありません。
この場合だと一時間以内でトレーニングを終了してください。と言う指示ではないのでご注意ください。
自分がやった時はこのカリフォルニアのCSVデータ（約2万行）を1ノードで大体二時間くらいかかりました。
ここで「トレーニング開始」を押すと学習が実行されますが、結構時間かかるのでしばらくは他の作業をするかひたすら待ちます。
学習が完了したらメールでお知らせが届きますので、ご安心を。
こちらの学習が終了するとサイドバーの「Model Registry」に学習モデルが登録されるのでそちらから確認できます。
<h1>４）学習済みモデルを使ってエンドポイントの作成を行う。</h1>

エンドポイントの作成では、REST APIでリクエストが可能なインスタンスを立ち上げてリアルタイムに推論する仕組みを提供します。
要はここを設定することで自分のローカル環境からNode.jsでAPIを叩けるようになるわけです。
<h3>エンドポイントの作成</h3>

メニューのエンドポイントから「エンドポイントの作成」を選択します。

![スクリーンショット 2022-12-13 11.37.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/abaafd6d-85fb-77ca-7f8d-1bd7f26faccd.png)
<h3>エンドポイントの定義</h3>

「エンドポイントの定義」ではエンドポイント名を指定します。
ここは任意のエンドポイント名を指定してください。
今回は「automl_endpoint」としております。

![スクリーンショット 2022-12-13 12.27.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/0a6daec6-a792-e51b-402a-23f37d427d58.png)
<h3>モデル設定</h3>
「モデル設定」ではこのエンドポイントで利用するモデルや動作環境となるコンピューティングリソースに関する設定を行います。
「モデルを追加」で利用するモデルとそのバージョンをModel Registryにある項目の中から選択します。

![スクリーンショット 2022-12-13 12.29.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/a5a4a5b0-6b49-8c10-0d42-ccde33f52e5f.png)
<h3>コンピューティングリソース</h3>
「コンピューティングリソース」ではこのモデルで利用するマシンのスペックを指定できます。
トラフィックに応じてオートスケールさせる機能もあるため、最大ノード数を指定することも可能です。

![スクリーンショット 2022-12-13 12.17.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/cbfb6db1-ffb8-fa1a-357c-b263cbbabd3d.png)

<h3>ロギング</h3>
「ロギング」はログ出力の設定となります。

・アクセスロギングとは...
タイムスタンプやレイテンシなどエンドポイントへのリクエストに関する情報を出力する
・コンテナロギングとは...
コンテナ内の処理で標準出力や標準エラー出力に出力したものを対象にして出力する。カスタムコンテナを利用する際などに
利用できます。
![スクリーンショット 2022-12-13 12.22.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/f702e2be-8b41-7c60-c73d-eab0448b472f.png)
<h3>モデルのモニタリング</h3>

「モデルのモニタリング」は期待通りに動作しているか確認し、閾値以上の変化が発生している場合にアラートを出す設定ができます。
今回は使用しませんのでこちらをクリックしてオフ（灰色）にしておきます。

![スクリーンショット 2022-12-13 12.33.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/3ad78e43-4636-b95f-736a-d92aa91ef73c.png)
<h3>デプロイ</h3>

全ての設定を終えて「作成」を選択すると、エンドポイントのデプロイが開始されます。 
デプロイ完了までは数分程度の時間がかかるので、少し待ちましょう。
デプロイが完了すればリクエストを受け付ける状態にあるのでAPIを叩いてみましょう！!
ここにエンドポイントが表示されステータスが完了になれば準備完了です。

![スクリーンショット 2022-12-13 12.36.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/cff7533c-9770-2c71-038e-f704945be1e9.png)
<h1>５）Node.jsでエンドポイントを叩いてデータを取得する。</h1>

さあ次はNode.js側の処理に移りたいと思います。
まずは自分のPCの作業フォルダの中にフォルダを作成し、

```npm init```コマンドで```package.json```を作成してください。
そして作業用フォルダに移動しターミナルから今回使用するパッケージをインストールします。
・Node.jsのrequestモジュール
APIを叩く際に使用します。axiosとか別に何でもいいです。
```npm install request```
・dotenv
一応環境変数（プライベートな値）は隠しておきたいので入れておきます。
```npm i dotenv```
準備が出来ましたら、さっそくエンドポイントを叩きたい所ですが、この状態だとまだエンドポイントへはアクセスできません。
Google Cloudで作成したアクセスポイントを叩くには、アクセストークンを取得する必要があります。
アクセストークンとは、自分だけがAPIにアクセス出来る鍵のようなものです。まずこちらを取得する必要があります。
<h2>アクセストークンの取得</h2>
アクセストークンを取得する方法ですが、その前に自分のパソコンに「gcloud CLI」をインストールする必要があります。
「gcloud CLI」とはローカル（自分のPC上）のターミナルからGoogle Cloudに色んな指示出しができるようになるツールです。
公式にダウンロードの方法が載っていますが、macの方はhomebrewで入れても良いかもしれません。（自分はhomebrewで入れました）
まあどっちでやっても結果は同じなのでお好みでどうぞ！

・公式
https://cloud.google.com/sdk/docs/install?hl=JA
・homebrew版

```brew install --cask google-cloud-sdk```

無事gcloud CLI(google-cloud-sdk)を入れることができたら今度は```g cloud```コマンドをパソコンで使えるように設定していきます。
お使いのパソコンのシェル設定ファイル（.bashrcとか.zshrc）に下記を追記します。
隠しファイルなのでmacの方はcommand+shift+.で表示させることができます。
```
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.bash.inc'
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.bash.inc'
```
末尾の```bash.inc```はzshの方はzshに変更してください。
これで gcloud コマンドが使えるようになりました。
最後に```gcloud --version```コマンドでgcloudのバージョンを確認します。
以下のように表示されれば無事設定完了です。
![スクリーンショット 2022-12-13 12.58.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/118d3227-32ae-3402-8df9-3d03fc2ed36d.png)
<h2>サービスアカウントのキーを発行</h2>

「サービスアカウント」とは個々のエンドユーザーではなく、アプリケーションや仮想マシン（VM）に属している特別な Google アカウントです。
今回であればVertex AIがサービスアカウントに当たります。
サービスアカウントの確認方法は、サイドメニューから「IAMと管理」→「サービスアカウント」になります。

![スクリーンショット 2022-12-13 13.40.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/8207ee04-b10a-a67d-0d45-11b51997c25e.png)
こちらの「メール」の値は後で必要になりますのでメモっておきましょう。
<h2>Google Cloud SDKでコマンドを実行</h2>
権限がオーナーでないと出来ないので下記コマンドで権限をオーナーにします。（元からオーナーの方は不要です。そこまでの権限を与えられてない人は後で変更しておきましょう。）

```gcloud projects add-iam-policy-binding [プロジェクトID] --member="serviceAccount:[サービスアカウント]" --role="roles/owner"```
こちらの[プロジェクトID]はGoogle Cloudヘッダーから現在のプロジェクトをクリックすると確認できます。![スクリーンショット 2022-12-13 13.56.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/684cff82-eafe-7c11-eaac-2416ef1bc40e.png)
[サービスアカウント]の所は先程上記で確認した「メール」の部分の値を入れてください。
権限がオーナーになりましたら、キーファイルをダウンロードしてきます。
```gcloud iam service-accounts keys create [キーのファイル名].json --iam-account=[サービスアカウント]```
[キーのファイル名]は任意の名前を付けることができます。
自分のパソコンにダウンロードされるファイル名がこちらに当たります。
ダウンロード出来ましたら、作成したjsonファイルを環境変数に登録します。(bashrcかzshrc）
```export GOOGLE_APPLICATION_CREDENTIALS="ファイルまでのパス/取得してきたキーのファイル名.json"```
<h2>アクセストークン発行</h2>
ここまで出来ましたら後は、下記コマンドでアクセストークンを取得できます！

```gcloud auth application-default print-access-token```![スクリーンショット 2022-12-13 14.08.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/51326242-3707-8529-caa1-708e67524b8b.png)
<h2>Node.jsでAPIを叩く</h2>
さあ準備が整いましたのでNode.jsでエンドポイントからデータを取得していきます。
作業フォルダでjsファイルと.envファイルを作成し、jsファイルに下記を記述します。

```javascript
const request = require("request"); //APIFetchLibrary
require("dotenv").config(); //envLibrary

// エンドポイントをデプロイしたリージョン
const REGION = process.env.REGION;

// 現在選択しているプロジェクトID
const PROJECT_ID = process.env.PROJECT_ID;

// 作成されたエンドポイントのID
const ENDPOINT_ID = process.env.ENDPOINT_ID;
```
一つずつ見ていきます。
上二行は今回使うライブラリのインポートになります。
下3つですが、envファイルに置いたプライベートな値を定数に代入しています。
・エンドポイントをデプロイしたリージョンですが、サイドメニューからエンドポイントを選択し、テーブルから確認することができます。
・作成されたエンドポイントのIDも同じ場所で確認可能です。
・現在選択しているプロジェクトIDはキーファイルをダウンロードした時に確認した値と同じものになります。
上記が出来たら次はAPIを叩く処理を記述していきます。
```javascript
try {
  request.post(
    {
      uri: `https://${REGION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${REGION}/endpoints/${ENDPOINT_ID}:predict`,
      headers: {
        "Content-Type": "application/json",
        authorization: `Bearer ${process.env.TOKEN}`,
      },
      json: {
        instances: [
          {
            longitude: "-122.24",
            latitude: "37.85",
            housing_median_age: "52",
            total_rooms: "1467",
            total_bedrooms: "190",
            population: "496",
            households: "177",
            median_income: "7.2574",
            median_house_value: "352100",
            ocean_proximity: "NEAR BAY",
          },
        ],
      },
    },
    (err, res, data) => {
      console.log(data);
    }
  );
} catch (error) {
  console.log(error);
}
```
Node.jsのrequestModuleを使ってエンドポイントを叩いています。
TryCatch文でAPIが正常に走らなかった時の例外処理を記述し、tryの中にrequestの記述を書いています。
「uri」にエンドポイントのURLを指定していますが、ここで上記で定数に代入した値を入れています。
「headers」には上記で取得したアクセストークンを指定しています。
```node ファイル名.js```で以下のような結果が返ってくれば成功です。
```
{
  "predictions": [
    {
      "upper_bound": 463628.5625,
      "value": 386459.125,
      "lower_bound": 15578.5302734375
    }
  ],
  ... (利用したモデルに関する情報が続く)
}
```
もしここで403エラーになる場合は作成したサービスアカウントに正しい権限が付与されていない可能性が挙げられます（自分の環境ではなりました）
なので、サイドバーから「IAMと管理」→「IAM」でロールを以下のように変更してください。
「 Automl Tutorial Vertex AI サービス エージェント」の権限であれば正常にリクエストを受け付けられると思います。
※もしダメな場合は一旦オーナーにしてしまうのもありかもです。
ただ最適な権限ではないため正しくエンドポイントが叩けた後で修正する必要があります。

![スクリーンショット 2022-12-13 14.31.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/f9cf4a7d-4e8d-6691-7482-dda38209522b.png)
ここまででエンドポイントをNode.jsで叩く処理は終了です。最後に後片付けをして料金がこれ以上かからないように設定しましょう！
<h1>６）後片付け。</h1>

このままモデルをデプロイした状態にしておくと永続的に金額が発生してしまうので、後片付けをします。
まずサイドバーから「ModelRegistry」を選択し、ページ上部の「デプロイとテスト」タブを選択して画面右側の縦に3点のアイコンを選択し、「モデルのデプロイ解除」を選択します。

![スクリーンショット 2022-12-13 9.30.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/fc24df00-05d8-0750-1792-ff8c0a5989fd.png)
確認をクリックしてモデルがデプロイ解除されたことを確認します。
次にエンドポイントを削除します。サイドバーの「エンドポイント」をクリックしこちらも3点アイコンから削除します。
![スクリーンショット 2022-12-13 9.30.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/49016d8a-9357-8ede-b70f-f41267cfa2e8.png)
画像では見切れてしまっていますが、右に行くと3点アイコンが出てきますのでそちらで削除します。

<h1>おわりに</h1>

以上がVertexAIを使用しNode.jsでエンドポイントを叩いて情報を取得するまでの全行程になります。
色々ググリながらやったので結構時間掛かりましたが、GCPに慣れてる人ならサクッと短時間で出来るんじゃないかと思います。
機械学習に触れてみてとても面白い経験だったので、もっと造詣を深めてマスターしていきたいです！
それでは！
<h1>参考</h1>

https://dev.classmethod.jp/articles/vertex-ai-automl-intro/

https://qiita.com/egcmrsk/items/9dbd265dd957992143f1

https://dev.classmethod.jp/articles/vertex-ai-custom-container/

https://blog.g-gen.co.jp/entry/troubleshoot-iam-permission-error

https://blog.g-gen.co.jp/entry/vertexai-explained#AutoML-%E3%81%AB%E3%82%88%E3%82%8B%E3%83%88%E3%83%AC%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%E3%81%AE%E6%96%99%E9%87%91

https://qiita.com/tfactory/items/fe7b486bffa50297a5c5