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

<h1>はじめに</h1>

前回[VertexAI Automl](https://qiita.com/SyoInoue/items/2688b99a4e7549bcfc59)のチュートリアルをやってみて、機械学習やAiに軽く触れて色々学びになったので今回はRetail APIのRecommendations AIをNode.jsからテストAPIを叩いてみたいと思います！

<h1>Why Retail API? Recommendations AI?</h1>

<h3>Retail APIとは</h3>

GCP（GoogleCloud）が提供しているレコメンド&検索サービスで、小売向けの主にECサイトに導入できるサービスを提供しています。
提供しているサービスは二つで商品検索サービスの[Retail Search](https://cloud.google.com/solutions/retail-product-discovery?hl=ja)と商品レコメンドサービスの[Recommendations AI](https://cloud.google.com/recommendations?hl=ja)があります。
その中で今回はRecommendations AIにフォーカスを当てて解説していきます。

<h3>Recommendations AIとは</h3>
簡単に言うと超高機能なレコメンドサービスで、ページを訪れたユーザーの情報を解析してユーザー一人一人にあった商品やサービスを提供してくれるサービスになります。

<h1>全体の流れ</h1>

Recommendations AIをローカルのNode.jsでテスト的にAPIを叩くに当たって、最低限必要な要素は下記3つになります。
<ul>
<li>１）商品カタログ情報</li>
<li>２）ユーザーイベント情報</li>
<li>３）モデルの作成</li>
</ul>

一つずつ解説していくと、まず「商品カタログ情報」と言うのはその名の通り商品の情報になりまして、この後APIを叩いて取ってくるデータになります。
「ユーザーイベント情報」と言うのはユーザーがどう言うアクションを起こしたかまとめたデータで、例えば「商品を買い物かごに入れる」だったり「商品詳細ページを閲覧する」等のデータがjson形式でまとめられたデータになります。
「モデル」と言うのはそれらデータに対してどう言ったレコメンド（推薦）行うかを決める要素になりまして、例えば「関連商品のおすすめ」だったり「あなたへのおすすめ」と言ったものが該当します。
これらの要素を使用し、今回の手順をまとめると以下になります。
※モデルはデフォルトで作成されている物があるのでそちらを使用します。

<ul>
<li>１）Retail APIのAPIを有効にする</li>
<li>２）商品カタログ情報をCloudStorageにインポートする</li>
<li>３）CloudStorageの商品カタログ情報をRetail APIのカタログにインポートする</li>
<li>４）ユーザーイベント情報をCloudStorageにインポートする</li>
<li>５）CloudStorageのユーザーイベント情報をRetail APIのイベントにインポートする</li>
<li>６）モデルを作成する</li>
<li>７）Node.jsでAPIを叩いてデータを取得する</li>
</ul>

ではまずは１）からやっていきましょう！

<h1>１）Retail APIのAPIを有効にする</h1>

GCPのプロジェクトを開いてプロジェクトを選んでください。（無料枠だとこちらのAPIは使用できません。）
※[プロジェクト作成がまだな方はこちらから作成してください。](https://cloud.google.com/resource-manager/docs/creating-managing-projects?hl=ja)![スクリーンショット 2022-12-19 15.24.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/d2083f5d-c264-3351-c9b3-e1db0603968b.png)
選べましたら左上のハンバーガーメニューを開き、一番下の「その他のプロダクト」のプルダウンを開き下にスクロールすると人工知能の欄に「Recommendations AI」があるのでそちらを選びます。
※右のピンアイコンをクリックすると上部に固定できますので便利です。
最初は「APIを有効にする」と言うボタンが出てくるかと思いますので有効にしてください。
するとこのRecommendations AIのメイン画面に遷移すると思われます。![スクリーンショット 2022-12-19 15.28.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/a16394cd-9727-2a42-49eb-3cbfcd93a230.png)
ここまで出来たら次のステップに移ります。

<h1>２）商品カタログ情報をCloudStorageにインポートする</h1>

ここからのステップには[gcloud CLI](https://cloud.google.com/sdk/docs/install?hl=ja)が必要になるため、
まだインストールされていない場合は[こちら](https://cloud.google.com/sdk/docs/install?hl=ja)から行ってください。
<h3>コードをcloneする</h3>

gcloud cliがインストールできたらまず自分のローカル環境にこちらのGitHubからコードをcloneしてきます。
```bash
git clone https://github.com/googleapis/nodejs-retail.git
```
cloneが出来たらGCPのサイドバーから、「IAMと管理」→「IAM」で自分の権限をオーナーにしておいてください。
<h3>設定スクリプトを実行する</h3>

こちらのcloneしてきたプロジェクトを開き、下記のコマンドでサービスアカウントの作成とjson形式のキーファイルをダウンロードします。

```bash
bash \
    samples/interactive-tutorials/user_environment_setup.sh \
    <PROJECT-ID>
export \
    GOOGLE_APPLICATION_CREDENTIALS=~/key.json
```

```<PROJECT-ID>```の部分にはGCPコンソールのヘッダーのプルダウンから確認したプロジェクトIDを入れてください。
下記の様なエラーが出た場合は```gcloud auth login```コマンドでgcloud cli上からGCPにログインしてください。

```bash
ERROR: (gcloud.config.set) There was a problem refreshing your current auth tokens: ('invalid_grant: Invalid grant: account not found', {'error': 'invalid_grant', 'error_description': 'Invalid grant: account not found'})
Please run:

  $ gcloud auth login

to obtain new credentials.

If you have already logged in with a different account:

    $ gcloud config set account ACCOUNT

to select an already authenticated account to use.
=========================================
The Google Cloud setup was not completed.
Please fix the errors above!
=========================================
```
ログインしコマンドを実行してこの様な表示がコンソールに出れば成功です。
```bash
To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
=========================================
The Google Cloud setup is completed.
Please proceed with the Tutorial steps
=========================================
```
一応確認しておきましょう。
GCPコンソールを開きサイドバーから「IAMと管理」→「サービスアカウント」を開きます。
![スクリーンショット 2022-12-19 17.44.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/d9fa1de1-3bfb-e3f9-e567-88729c983778.png)
そこにこのような表示がされていたら正常にサービスアカウントが作成されています。
「サービスアカウント」とはアプリケーションや仮想マシン（VM）に属している特別な Google アカウントです。
今回であればRecommendations AIがサービスアカウントに当たります。
こちらのサービスアカウントを使用する上で必要なキーが```key.json```ファイルとしてご自分のローカル環境に落ちてきている
はずなのでそちらも確認してください。
大体はホーム画面にあります。
<h3>Google Cloud Retail ライブラリをインストールする</h3>

次は必要なライブラリのインポートを行います。
samplesディレクトリに移動して```npm install```を実行します。

これで作業環境の構築は完了です。

<h3>カタログデータをCloudStorageにアップロードする</H3>

下記のコマンドでCloudStorageにバケットを作成して商品カタログのJSONファイルをアップロードします。
ディレクトリは```samples```ディレクトリのまま実行してください。
```node interactive-tutorials/setup/create-gcs-bucket.js```
このようにコンソールに出力されれば成功です
```bash
Bucket バケット名 created.
File products.json uploaded to バケット名
File products_some_invalid.json uploaded to バケット名
```
確認しておきます。GCPコンソール画面からサイドバー「CloudStorage」→「バケット」を選択してください。
![スクリーンショット 2022-12-19 18.01.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/2e0f6b33-775c-3b45-43a0-c6055c77a7c7.png)
このように出ていれば正常にCloudStorageにバケットが作成され商品カタログのJSONファイルがアップロードされています。
確認出来ましたらバケット名をコピーして、exportコマンドで一時的に環境変数を変更しておきます。
```bash
export BUCKET_NAME=<PROJECT-ID>_<TIMESTAMP>
```
<h1>３）CloudStorageの商品カタログ情報をRetail APIのカタログにインポートする</h1>

最後にCloud Storageソースから商品をインポートします。
```bash
node samples/interactive-tutorials/product/import-products-gcs.js
```
下記の様にターミナルに出力されていたら成功です。
```bash
Number of successfully imported products: 316
Number of failures during the importing: 0
Operation result: [{...}]
Import products finished
```
GCPコンソールでも確認してみます。
サイドバーから「Recommendations AI」→「データ」→「カタログ」でこのような画面が表示されていたらOKです。
![スクリーンショット 2022-12-19 19.40.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/89866b6a-f43a-dd96-b178-1be2af869a5d.png)

これで商品カタログデータの登録は完了です。

<h1>４）ユーザーイベント情報をCloudStorageにインポートする</h1>

次はユーザーイベントをやっていきますが、ほぼ同じ手順になります。
作業環境の構築は上記で完了していると思いますので、CloudStorageにユーザーイベントをアップロードします。
<h3>CloudStorageにユーザーイベントをアップロードする</h3>

下記のコマンドを```cd samples/interactive-tutorials```でinteractive-tutorialsディレクトリに移動してから実行します。
```bash
node samples/interactive-tutorials/setup/events-create-gcs-bucket.js
```
省略しますが確認したい方はカタログインポート時と同じ方法でGCPのコンソール画面から確認してみてください。
環境変数を通します。
```bash
export EVENTS_BUCKET_NAME=<PROJECT-ID>_events_<TIMESTAMP>
```

<h1>５）CloudStorageのユーザーイベント情報をRetail APIのイベントにインポートする</h1>

ディレクトリはinteractive-tutorialsディレクトリのままで下記を実行します。
```bash
node events/import-user-events-gcs.js
```
下記の様にターミナルに出力されていたら成功です。
```bash
Number of successfully imported events: 4
Number of failures during the importing: 0
Operation result: [{...}]
Events import finished
```
GCPコンソールでも確認してみます。
サイドバーから「Recommendations AI」→「データ」→「イベント」でこのような画面が表示されていたらOKです。
![スクリーンショット 2022-12-19 20.32.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/7e8074b2-845a-decd-2cb1-92717b66c74d.png)
これでユーザーイベントデータの登録は完了です。

<h1>６）モデルを作成する</h1>

モデルの作成ですが、デフォルトで作成されているモデルがあるので今回はそれを使います。
GCPコンソールからサイドバー「Recommendations AI」→「モデル」を表示してください。
![スクリーンショット 2022-12-19 21.22.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/20810dcc-ec31-f57e-29a1-f278c0cc342a.png)
このような画面になってるかと思います。
こちらがデフォルトで作成されているモデルで「モデルタイプ」は「最近表示したもの」になっています。
これは指定したuserIdまたはvisitorIdのユーザーが閲覧したアイテムの履歴を返してくれるモデルで、
最大で75個まで返してくれます。
他にもいろんなモデルがあるのですが、膨大な量のデータが必要だったりトレーニングに十分なユーザーイベントデータを提供する必要があったりと、テストで叩くにはかなり大掛かりになってしまうのと料金も結構かかるので今回はこちらを使用します。
ちなみにモデルのトレーニングとは、データやイベントから予測をリクエスト（レコメンド）してくれる機能になります。
<h1>７）Node.jsでAPIを叩いてデータを取得する</h1>

下準備が完了したのでAPIを叩いていきます。
まずsamplesディレクトリへ移動し、dotenvとrequestモジュールをimportします。
・dotenvのimport
```bash
npm i dotenv
```
・requestのimport
```bash
npm install request
```
samplesディレクトリ配下に```test.js```（ファイル名はなんでも良い）を作成し、下記コードを書きます。
urlのプロジェクト名のところにはプロジェクトIDを入れてください。

```javascript
const request = require('request');
require("dotenv").config();

try {
  request.post(
    {
      url: 'https://retail.googleapis.com/v2/projects/プロジェクト名/locations/global/catalogs/default_catalog/servingConfigs/recently_viewed_default:predict',
  headers: {
    'Authorization': `Bearer ${process.env.TOKEN}`,
    'Content-Type': 'application/json; charset=utf-8',
  },
  json: {
    validateOnly: true,
    userEvent: {
      eventType: 'detail-page-view',
      visitorId: 'bjbs_group1_visitor1',
      productDetails: [
        {
          product: {
            id: 'GGCOGAEC100616',
          },
        },
      ],
    },
  },
    },
    (err, res, data) => {
      console.log(data);
    }
  );
} catch (error) {
  console.log(error);
}
```
ターミナルで```gcloud auth application-default print-access-token```コマンドを実行し、アクセストークンを取得します。
![スクリーンショット 2022-12-19 22.40.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1076062/6012f603-03b1-2e2e-3328-de19c05ee308.png)
samplesディレクトリ配下で```.env```ファイルを作成します。
そこに取得したアクセストークンを入れます。
```.env
TOKEN=ya29.c.b0AT7lpjDOioKqtJpozPwpX3WsP1zhw6aUE81aHhjAuyRjF....
```
samplesディレクトリで```node test.js```を実行します（testの所は任意のファイル名になります。）
下記のようなデータが取れていたら成功です。

```bash
{
  results: [
    { id: 'GGPRGALB100815' },
    { id: 'GGPRGAEL101415' },
    { id: 'GGPRGADC107915' },
    { id: 'GGPRGADC107914' },
    { id: 'GGPRGAAB100718' },
    { id: 'GGPRGAAB100714' },
    { id: 'GGPRGAAB100712' },
    { id: 'GGPRAHPL107110' },
    ...
  ],
  attributionToken: 'ChQxMTUzNzQ3NTkyMjU2NDY5MjY3MxACGgJSViICUlYoAA',
  validateOnly: true
```
レスポンスで返ってきている```attributionToken```は予測を行うと発行され、ユーザーの操作と結果を相互に関連付けより精度の高い予測を行うのに使用されます。
```validateOnly: true```はテストでAPiを呼ぶときに使用します。これがfalseだとエラーになるのでtrueにしておいてください。
上記のレスポンスのデータはCloudStorageのカタログデータの中から来ています。
CloudStorageのユーザーイベントデータの中身は下記のようになっており、
test.jsのbodyで指定したeventTypeとvisitorIdがこのユーザーイベントのデータを指定して、
モデルでこのユーザーが閲覧したアイテムを取得する指示を出しているのでAPIを叩くとそのデータが表示されるようになっています。
```
{"eventType":"detail-page-view","visitorId":"bjbs_group1_visitor1", ...}
```
以上になります。お疲れ様でした。
<h1>おわりに</h1>

Retail APIはECサイトはもちろん色んな用途に使えそうな高機能なAIサービスです。
実務で使う場合はもっと膨大なカタログデータとユーザーデータを使ってやる必要がありますが、
テストでAPIを呼び出してみることで全体像は掴めたかなと思います。
また機会があればGCP色々いじってみたいですね。

<h1>参考</h1>

https://cloud.google.com/retail/docs/setting-up?hl=ja#cloud-storage

https://github.com/googleapis/nodejs-retail.git

https://zenn.dev/google_cloud_jp/articles/ad5de014ffe061