【Google Cloud】BigQueryでベクトル検索を実施してみた

# 0. はじめに
ベクトル検索とは、単語、文といった自然言語、画像および動画などのデータから生成したベクトル表現 (多次元配列に挿入された数値データ：エンベディング) を利用してデータを検索する仕組みとなります。
フルテキスト検索とは異なり、類似性検索を実施可能な点が大きな特徴だと思います。
BigQueryでは標準機能としてベクトルデータベース機能を保持しており、ベクトル検索を実施することが可能となっています。
今回は、BigQueryでベクトル検索を実施する方法についてまとめてみようと思います。

# 目次
[0. はじめに](#0-はじめに)
[1. ベクトル検索のユースケース](#1-ベクトル検索のユースケース)
[2. BigQueryベクトル検索設定](#2-bigqueryベクトル検索設定)
[3. BigQueryベクトル検索例](#3-bigqueryベクトル検索例)

# 1. ベクトル検索のユースケース
ベクトル検索では類似性検索を実施可能な点が大きな特徴だと思いますが、以下のようなユースケースで利用することができそうです。
</br>
**【ユースケース】**
- **レコメンデーション**
特定ユーザーの趣味、好み、商品購入の傾向などが類似している他のユーザーが購入している商品などをレコメンデーションとして通知するなど。
- **類似商品の検索**
購入したかった医薬品が売り切れていた場合、類似の効能、類似の成分を利用している薬の検索を実施するなど。
- **名寄せ**
同じユーザーの情報が複数のデータベースに保持され、保持されているユーザー情報 (氏名、住所など) の統合を実施するなど。
- **欠陥製品の発見**
センサーなど通常のデータ範囲外のデータが流れてきた場合に検知するなど。
- **障害発生時の解決策検索**
障害発生時の状況により、類似の障害発生時に対応した解決策を検索するなど。

# 2. BigQueryベクトル検索設定
**【設定手順の流れ】**
1\) BigQuery API, Vertex AI API の有効化
2\) BigQuery データセットの作成
3\) 外部データソース（接続タイプ : Vertex AI リモートモデル) の作成
4\) 外部データソース (サービス アカウント ID) に対して「Vertex AI ユーザー」権限を付与
5\) BigQuery 言語モデルの作成
※ 日本語を対応する場合、最新の「text-multilingual-embedding-00x」モデルを選択
※ 最新モデル「text-multilingual-embedding-002」(2024年9月時点)
　
**【ベクトル検索設定事前準備】**
1-1) 「APIとサービス」-「+ APIとサービスを有効にする」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/c393b6e7-2995-965f-b516-01bcd71ba450.png)
</br>
1-2) 「BigQuery API」で検索し、選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/3e086e9a-c9ac-7fbb-584c-f9cc24f8d253.png)
</br>
1-3) 「有効にする」を選択し、「BigQuery API」を有効化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/52ed4654-d822-4c33-a945-3fbaf4b19121.png)
同様に「Vertex AI API」を有効化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/3e24edda-8e03-aac9-5d57-f94e0e466637.png)
</br>
2-1) Google Cloud コンソールから「BigQuery」を選択し、「データセットを作成」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/d2ac131f-9375-7542-9294-cb1b2f1e617b.png)
</br>
2-2) 「データセット ID」、「ロケーション タイプ」などを入力後、「データセットを作成」を選択し、データセットを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/764026a4-2cc0-be88-5470-d6f60f39dfac.png)
</br>
2-3) BigQuery エクスプローラから「+ 追加」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/112f305c-a78c-372f-c783-86ef2385a8a6.png)
</br>
3-1)「外部データソースへの接続」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/ddc63999-b8d5-a99d-126b-66104d78719e.png)
</br>
3-2) 接続タイプ「Vertex AI リモートモデル...」を選択し、「接続ID」などを入力後、「接続を作成」を選択し、外部接続を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/6cbe1761-d723-22af-058b-7595e1eb3df2.png)
</br>
3-3) 作成した外部接続を選択し、「サービス アカウント ID」をメモ帳などに控えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/e5b90edf-069b-44aa-9b30-01a84b998708.png)
</br>
4-1) 「IAMと管理」-「アクセス権を付与」を選択します。
4-2) 「新しいプリンシパル」に 2-9) で控えたサービス アカウント ID を入力し、ロール「Vertex AI ユーザー」を指定後、「保存」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/77724d10-63cf-d354-bcbe-07756e336f18.png)
</br>
5-1) 再度「BigQuery」に戻り、以下のようなクエリを実行して言語モデルを作成します。
- モデル名 : <データセット名>.<言語モデル名> (例: vector_search.embedding_model)
- リモートコネクション名情報 : 外部接続名 (例: vector_search.embedding_model)
- エンドポイント情報 : 日本語対応する場合には「text-multilingual-embedding-00x」を選択 

```ruby:言語モデルの作成 (例)
create or replace model vector_search.embedding_model
remote with connection `asia-northeast1.vertex-ai-for-bq`
options(
  endpoint = 'text-multilingual-embedding-002'
);
```
【補足】
CREATE MODEL ステートメントの詳細については、[CREATE MODEL ステートメント](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-create) を参照。
最新のテキスト エンベディング API の詳細については、[Text embeddings API](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api) を参照。


# 3. BigQueryベクトル検索手順例
**【検索手順の流れ】**
1\) BigQuery で空テーブルを作成し、任意のデータを挿入
2\) ML.GENERATE_EMBEDDING 関数を利用し、エンベディングを生成
3\) 2) で生成された結果セットから 新規 BigQuery テーブルを作成 
4\) 3) で作成されたテーブルに対して、VECTOR_SEARCH 関数を利用したベクトル検索を実行
</br>

**【ベクトル検索手順例】**
1-1) BigQueryで空のテーブルを作成し、データを挿入します。
※ 任意のデータを作成したテーブルに挿入します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/3a2a9e18-eaa9-4ff0-4d53-dbe650d1fca0.png)
</br>
```ruby: テーブル スキーマ (例)
id:INTEGER,
name:STRING,
firstname:STRING,
lastname:STRING,
age:INTEGER,
sex:STRING,
zip:STRING,
address:STRING
```

2-1) 以下のクエリを実行し、1-1) で作成したテーブルにエンベディング データ列を追加した結果セットを作成します。
※ 本クエリでは、「firstname」,「lastname」,「age」,「sex」,「zip」,「address」列を結合したデータを ML.GENERATE_EMBEDDING 関数を利用してエンベディングしています。

[VECTOR_SEARCH 関数](https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#vector_search) 

```ruby:Embedding データ生成 (例)
WITH user_info_embedding AS (
 SELECT * from `<プロジェクト名>.vector_search.user_info`
),
embedding_table AS (
 SELECT *
 FROM ML.GENERATE_EMBEDDING(
   MODEL `vector_search.embedding_model`, 
   (SELECT firstname || ' ' || lastname || ' ' || age || ' ' || sex || ' ' || zip || ' ' || address AS content, id FROM user_info_embedding), 
   STRUCT(TRUE AS flatten_json_output)
 )  
)
select uie.*, et.ml_generate_embedding_result as embedding,et.content from embedding_table as et
inner join user_info_embedding as uie
on uie.id = et.id
```

【補足】
ML.GENERATE_EMBEDDING 関数の詳細については、[ML.GENERATE_EMBEDDING 関数](https://cloud.google.com/bigquery/docs/reference/standard-sql/bigqueryml-syntax-generate-embedding) を参照。

</br>
3-1) 2-1) の結果セットを「結果を保存」-「BigQuery テーブル」を選択し、BigQuery テーブルとして保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/082b0efa-37d5-8de5-8e50-bb0346fa0289.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/65a08cac-a689-b01d-5124-4c29f5d10979.png)
</br>
4-1) 3-1)で作成した BigQuery テーブルに対して、VECTOR_SEARCH 関数を利用したベクトル検索クエリを実行します。
※ 本クエリでは、WITH句でベクトル検索の条件 (content) を ML.GENERATE_EMBEDDING 関数でエンベディングし、データをembedding_Nayose に保存。その後、VECTOR_SEARCH 関数の引数に指定してベクトル検索を実行しています。


```ruby: ベクトル検索クエリ(例)
WITH embedding_Nayose AS (
 SELECT * from ML.GENERATE_EMBEDDING(
  MODEL `vector_search.embedding_model`,
  (SELECT 'Taro test 25 male xxx-xxxx 東京都xx区xx1-1-1' AS content), 
   STRUCT(TRUE AS flatten_json_output)
 )
)
SELECT
  base.name,
  base.age,
  base.sex,
  base.zip,
  base.address,
  distance
FROM VECTOR_SEARCH(
 TABLE `<プロジェクト名>.vector_search.user_info_embedding`,
 'embedding',
 (SELECT ml_generate_embedding_result FROM embedding_Nayose), 
 top_k => 3, 
 distance_type => 'COSINE',
 OPTIONS => '{"use_brute_force":true}' 
) 
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540203/a19c2535-03f1-33ab-97aa-430968b3cd09.png)

※ distance 列の値が 0 に近いほど、類似性の高いデータとなります。

【補足】
VECTOR_SEARCH 関数の詳細については、[VECTOR_SEARCH 関数](https://cloud.google.com/bigquery/docs/reference/standard-sql/search_functions#vector_search) を参照。
</br>
**※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。**
**※ 2024年9月 現在**