はじめに:似ている商品をデータから探したい

中小ECを運営していると、「この商品と似た商品を一覧に並べたい」という場面がよくあります。商品ページの下に出る「関連商品」や「あわせて買いたい」、検索ヒットが少なかったときの「こちらもどうですか」といった提案です。

人気商品をただ並べるだけなら注文数順にソートするだけで対応できますが、「いま見ているこの商品に近いもの」を出そうとすると、とたんに難しくなります。カテゴリやタグで絞る方法もありますが、タグの付け方が雑だったり、そもそも付いていなかったりすると、うまく似た商品を拾えません。

そこで役に立つのが、商品の説明文をAIに「ベクトル(数値の並び)」へ変換してもらい、その数値の近さで似ている商品を探すやり方です。これを「埋め込み(エンベディング)」と呼びます。しかも、BigQueryならこの処理をSQLの延長として書けます。

この記事では、埋め込みとは何かという基本から、商品テキストをベクトル化する流れ、似た商品を探す方法、そして実運用での注意点までを、できるだけかみくだいて解説します。

⚠️ 注意 BigQueryのAI関数は、関数の名称や引数の構文が版によって変わります(埋め込み生成が AI.EMBED なのか ML.GENERATE_EMBEDDING なのか、引数の名前や形など)。この記事のSQLは「考え方を示すイメージ」として読んでください。実際に書くときは、必ず最新の公式ドキュメントで正確な関数名と構文を確認してください。

なお、レコメンドの仕組みそのものは BigQueryとVertex AIでECのレコメンドエンジンを作る で、SQLからGeminiを呼んでテキストを分析する話は BigQueryから直接Geminiを呼び出してテキストを分析する(AI.GENERATE) で、それぞれ詳しく扱っています。あわせて読むと全体像がつかみやすいはずです。


埋め込み(ベクトル)とは何か

「埋め込み」という言葉はとっつきにくいですが、考え方はそれほど難しくありません。

ざっくり言うと、文章や単語を「たくさんの数字の並び」に変換したものが埋め込み(ベクトル)です。たとえば「ふんわり厚手のオーガニックコットンタオル」という説明文を、AIに渡すと [0.021, -0.118, 0.347, ...] のような数百個の数字の列が返ってきます。

ポイントは、意味が近い文章どうしは、数字の並びも近くなるように作られている点です。

  • 「厚手のコットンタオル」と「ふっくらした綿のタオル」 → ベクトルが近い
  • 「厚手のコットンタオル」と「ステンレス製の水筒」 → ベクトルが遠い

つまり、文章の見た目(使っている単語)が違っても、意味が似ていれば数字として近い場所に配置されます。タグや単語の完全一致に頼らず、「意味の近さ」で商品を比べられるのが、埋め込みの大きな利点です。

この「近さ」を測る代表的なものさしがコサイン類似度です。2つのベクトルの向きがどれくらい揃っているかを示す値で、1に近いほど似ている、と覚えておけば十分です。


全体像:3つのステップ

商品の類似度検索は、大きく次の3ステップで作ります。

  1. モデルへの接続を用意する — BigQueryから埋め込み用のAIモデルを呼び出すための「通り道」を作ります。
  2. 商品テキストをベクトル化する — 商品名や説明文をモデルに渡し、ベクトルの列を生成してテーブルに保存します。
  3. 似た商品を探す — ある商品のベクトルと近いベクトルを持つ商品を、コサイン類似度などで探します。

順番に見ていきます。


ステップ1:モデルへの接続を用意する

SQLを書けばいきなりAIが動くわけではなく、まずBigQueryからAIモデルを呼び出すための準備が必要です。

大まかには、Google CloudのAIサービスへの「接続(コネクション)」を作り、その通り道を使って「この埋め込みモデルを使います」という定義(リモートモデル)をBigQueryのデータセット内に登録します。

リモートモデルの作成は、概念としては次のような形になります。

-- ※あくまでイメージです。正確な構文は最新の公式ドキュメントで確認してください
CREATE OR REPLACE MODEL `your_dataset.embedding_model`
REMOTE WITH CONNECTION `your_project.region.your_connection`
OPTIONS (
  -- 使用する埋め込みモデルを指定する(モデル名は版により異なる)
  endpoint = 'text-embedding-model-name'
);

ここで登録した embedding_model を、あとのSQLから呼び出してテキストをベクトルに変換していきます。接続の作り方や権限まわりは少し手間がかかるので、ここは公式ドキュメントの手順に沿って丁寧に進めるのがおすすめです。


ステップ2:商品テキストをベクトル化する

接続ができたら、いよいよ商品のテキストをベクトルに変換します。

ここでは、商品テーブル(products)に商品名と説明文があると仮定します。これらをひとつのテキストにまとめてモデルに渡し、ベクトルを生成します。

-- ※関数名・引数は版により変わります。最新の公式ドキュメントで確認してください
-- 例として埋め込み生成関数を仮に AI.EMBED と表記しています
CREATE OR REPLACE TABLE `your_dataset.product_embeddings` AS
SELECT
  product_id,
  product_name,
  -- 商品名と説明文をまとめてベクトル化する(イメージ)
  AI.EMBED(
    MODEL `your_dataset.embedding_model`,
    CONCAT(product_name, ' ', IFNULL(description, ''))
  ) AS embedding
FROM `your_dataset.products`;

実際の構文では、入力テキストの列名を content のように決められていたり、出力が ml_generate_embedding_result のような名前の列で返ってきたりします。ここも版によって差が出やすい部分なので、必ず最新の仕様を確認してください。

この処理を一度実行すると、商品ごとにベクトルが入った product_embeddings テーブルができあがります。商品が数千件あっても、SQLひとつでまとめて変換できるのがBigQueryの気持ちよさです。

差分だけ更新する

商品は日々増えたり、説明文が書き換わったりします。毎回すべての商品をベクトル化すると時間もコストもかかるので、新しく追加された商品や、説明文が変わった商品だけを対象にするのが現実的です。

-- まだベクトル化していない商品だけを変換するイメージ
INSERT INTO `your_dataset.product_embeddings`
SELECT
  p.product_id,
  p.product_name,
  AI.EMBED(
    MODEL `your_dataset.embedding_model`,
    CONCAT(p.product_name, ' ', IFNULL(p.description, ''))
  ) AS embedding
FROM `your_dataset.products` AS p
LEFT JOIN `your_dataset.product_embeddings` AS e
  ON p.product_id = e.product_id
WHERE e.product_id IS NULL;

このような差分更新を、スケジュールされたクエリで毎日まわしておけば、ベクトルのテーブルを常に最新に保てます。


ステップ3:似た商品を探す

ベクトルがそろえば、あとは「ある商品のベクトルと近いベクトルを持つ商品」を探すだけです。

コサイン類似度で素直に探す

商品数が数千件くらいまでなら、すべての商品との距離をその場で計算する素直なやり方でも十分動きます。

-- 基準となる商品(product_id = 'A001')に似た商品を上位10件探すイメージ
WITH target AS (
  SELECT embedding
  FROM `your_dataset.product_embeddings`
  WHERE product_id = 'A001'
)
SELECT
  e.product_id,
  e.product_name,
  -- コサイン距離。小さいほど「近い=似ている」
  ML.DISTANCE(e.embedding, t.embedding, 'COSINE') AS distance
FROM `your_dataset.product_embeddings` AS e, target AS t
WHERE e.product_id != 'A001'
ORDER BY distance ASC
LIMIT 10;

ここでは距離が小さいほど似ている、という関係になります。「類似度」で考えたい場合は、コサイン距離を 1 - distance のように読み替えれば、1に近いほど似ている、という直感的な見え方になります。

件数が増えたらベクトル検索を使う

商品が数万件・数十万件と増えてくると、毎回すべての商品と距離を計算するのは重くなります。そこで使うのが**ベクトル検索(VECTOR_SEARCH)**です。

-- VECTOR_SEARCH で近い商品を効率よく探すイメージ
-- ※引数・出力構造は版により異なります。最新の公式ドキュメントで確認してください
SELECT
  -- query.* がクエリ側(基準の商品)、base.* が検索でヒットした側
  query.product_id AS base_product_id,
  base.product_id  AS similar_product_id,
  base.product_name,
  -- 距離。小さいほど「近い=似ている」
  distance
FROM VECTOR_SEARCH(
  TABLE `your_dataset.product_embeddings`, 'embedding',
  (SELECT product_id, embedding
   FROM `your_dataset.product_embeddings`
   WHERE product_id = 'A001'),
  top_k => 10,
  distance_type => 'COSINE'
)
ORDER BY distance ASC;

VECTOR_SEARCH の結果は、検索でヒットした行を表す base、クエリ側の行を表す query、両者の distance(距離)といった列を含む形で返ってきます。そのため、上の例のように base.product_iddistance のかたちで取り出します。列の名前や入れ子の構造は版によって差が出やすい部分なので、ここも必ず最新の公式ドキュメントで確認してください。

ベクトル検索は、あらかじめ用意した索引(ベクトルインデックス)を使って「近そうな候補」だけを効率的に絞り込む仕組みです。全件と総当たりするより速く、規模が大きいECほど効果が出ます。インデックスの作り方や対応条件も公式の最新情報で確認しておきましょう。


用途と注意点

埋め込みによる類似度検索は便利ですが、万能ではありません。使いどころと気をつける点を整理しておきます。

向いている用途

  • 商品ページの「関連商品」「似ている商品」の自動生成
  • 検索結果が少ないときの「こちらもどうですか」の補完
  • タグ付けが追いついていない商品どうしのグルーピング
  • 重複登録ぎみの商品(似すぎている商品)の洗い出し

気をつけたいこと

  • 説明文の質に結果が左右されます。 説明がスカスカの商品は、ベクトルも特徴をつかみきれず、似た商品をうまく拾えません。まずは商品名と説明文を整えることが土台になります。
  • 「意味が近い」と「一緒に買われる」は別物です。 似ているタオルどうしを並べても、実は「タオルと入浴剤」のほうが売上につながることもあります。類似度はあくまで出発点で、購買データと組み合わせる発想も持っておきましょう。
  • コストは件数と更新頻度に比例します。 ベクトル化はAIモデルの呼び出しなので、全件を毎日作り直すと費用がかさみます。差分更新を基本にしてください。
  • モデルを変えるとベクトルの互換性が崩れます。 別の埋め込みモデルで作ったベクトルどうしは比べられません。モデルを切り替えるときは、全商品を作り直す前提で考えます。

まとめ

BigQueryの埋め込み機能を使うと、商品の説明文を数値のベクトルに変換し、「意味の近さ」で似た商品を探せます。タグの完全一致に頼らずに関連商品を出せるのが、いちばんのメリットです。

ふり返ると、流れはシンプルです。

  1. AIモデルへの接続を用意する
  2. 商品テキストをベクトル化してテーブルに保存する(差分更新を基本に)
  3. コサイン類似度や VECTOR_SEARCH で近い商品を探す

繰り返しになりますが、埋め込み生成やベクトル検索の関数名・構文は版によって変わります。この記事のSQLは考え方のイメージとして読み、実装時は必ず最新の公式ドキュメントで正確な仕様を確認してください。

まずは手元の商品データで、説明文の整備とベクトル化を一度試してみるのがおすすめです。小さく作って、結果を見ながら少しずつ精度を上げていきましょう。