娘からの唯一のバレンタインチョコに対し、BigQuery+ベクトル検索でホワイトデーの最適解を弾き出してみた
- 4 時間前
- 読了時間: 6分
プロローグ:2月14日、私のデスクは更地だった
2月14日。私の会社のデスクには、ボールペンとホッチキスだけが静かに置かれていた。
数年前まで存在した「会社での義理チョコ配布」という悪しき風習は、コンプライアンスとリモートワークの波に飲まれ、完全に消滅した。
財布には優しい。だが、データおじさんとしての私の自尊心(スコア)はゼロを叩き出していた。 そんな私を救ったのは、帰宅後にリビングの机に置かれていた小さな紙袋だった。
「これ、友チョコ作りすぎたから余り。お父さんにあげる」
中学生の娘からの、恩着せがましいが圧倒的な慈悲。 このたった1つのデータ(手作りブラウニー)が、私の心に火をつけた。
3月14日のホワイトデー。
ここで何を返すかで、今後の家庭内での私の立ち位置(カースト)が決まる。
安物のマシュマロで済ませれば「センスのないおじさん」に降格。
かといって、ハイブランドのコスメなどを贈れば「キモい」「重い」とSNSの裏垢で晒される危険性すらある。
JC(女子中学生)という未知の生命体に対する最適解を測るため、私は再びデータサイエンスの刀を抜いた。
第1章:JCの心理は多次元空間にある
娘が何を欲しがっているか? 直接聞くのは三流だ。「何でもいいよ」と言われて本当に何でもいいものを買っていくと激怒されるのが、この世界の仕様である。
かといって、私のおじさん脳内ネットワークで推論しても「図書カード」か「スタバのギフト券」という思考停止の出力しか出てこない。
いまどきの中学生、侮るなかれ。彼女たちの欲望は、価格、トレンド感、写真映え、実用性といった、複雑な多次元空間に存在しているのだ。
「私にはもう娘の心がわからない。」
データおじさんこと私は奥の手「ベクトル検索(Vector Search)」を召喚することにした。
第2章:BigQuery Vector Searchによる「お返し」の最適化
「重くない」「センスがいい」というJKの概念をSQLで表現するのは不可能だ。 そこで私は、Google Cloudの最新機能「BigQuery Vector Search」を使うことにした。
これは、言葉を「数値の配列(ベクトル)」に変換し、空間上の距離で「意味の近さ」を計算する魔法のような技術だ。
やることはたったの4ステップである。
Step 1:ギフト情報をBigQueryに入れる
まずは、世の中のスイーツや小物の情報をテーブルに流し込む。 (今回はわかりやすく手動で数件入れた想定だ)
descriptionに各商品の特徴がセットされている。

Step 2:LLM(テキスト埋め込みモデル)の接続設定
ここがBigQueryのすごいところ。BigQueryの中から直接、GoogleのLLM(今回は text-embedding-004 などのモデル)を呼び出して、テキストをベクトルに変換する。
まずは、BigQueryからVertex AIのモデルを呼び出すための「リモート接続」と「モデルの作成」を行う。
※利用にあたり、事前にGCPコンソールでBigQuery Connection APIを有効化し、コネクション設定を行っておくこと
-- BigQuery上にリモートモデル(Embedding API)を作成
CREATE OR REPLACE MODEL `your_project.whiteday_project.text_embedder`
REMOTE WITH CONNECTION `us.my-connection` -- ※実際の接続名に変更
OPTIONS (
endpoint = 'text-embedding-004' -- テキストを数値配列にするAIモデル
);Step 3:テキストをベクトル化(Embedding)する
ここが一番のキモだ。BigQueryの中から直接GoogleのAI(LLM)を呼び出し、さきほどの「商品の説明文」を 768次元の数値データ に変換して保存する。これがベクトルDBの正体だ。
-- ML.GENERATE_EMBEDDING関数を使って、説明文をベクトル化
CREATE OR REPLACE TABLE `your_project.whiteday_project.gift_embeddings` AS
SELECT
item_id,
item_name,
description,
ml_generate_embedding_result AS gift_vector -- これがベクトルデータ(ARRAY<FLOAT64>)
FROM
ML.GENERATE_EMBEDDING(
MODEL `your_project.whiteday_project.text_embedding_model`,
(
SELECT item_id, item_name, description, description AS content
FROM `your_project.whiteday_project.gift_items`
)
);実際の変換後イメージはこちら。

Step 4:「理想のギフト」をクエリ検索する
いよいよ検索だ。
「JCが喜ぶ、センスが良くて重くないもの」という私の「フワッとした概念(クエリ)」をその場でベクトル化し、先ほど作った gift_embeddings テーブルのベクトル群と「距離(コサイン類似度)」を計算して、一番近いものを探し出す。
-- 娘の潜在的ニーズと、ギフトの類似度(コサイン距離)を計算する狂気のクエリ
SELECT
base.item_name,
base.description,
distance -- 距離が近い(数値が小さい)ほど「意味が似ている」
FROM
VECTOR_SEARCH(
TABLE `your_project.whiteday_project.gift_embeddings`,
'gift_vector',
(
-- 私の「概念」をその場でベクトル化する
SELECT ml_generate_embedding_result AS text_vector
FROM ML.GENERATE_EMBEDDING(
MODEL `your_project.whiteday_project.text_embedding_model`,
(SELECT 'SNSで流行中。JC、女子中学生にも人気で、重たくない。' AS content)
)
),
distance_type => 'COSINE',
top_k => 3 -- 上位3件を取得
)
ORDER BY distance ASC;このクエリが、宇宙のような高次元空間を飛び回り、私のフワッとした概念にピタリと一致するアイテムを引き当ててくれるのだ。完璧だ。
第3章:導き出された「グローバル最適解」

AIが弾き出したトップの解。それは、 「有名パティスリーの、高級春限定ピスタチオ・バターサンド」だった。価格は¥2,500、悪くない。
価格帯:娘のブラウニー(原価推定¥500)に対し、5倍返し。重すぎない上限値。
トレンド感:SNSでの「#ご褒美スイーツ」ベクトルとの類似度 0.89。
汎用性:消え物(食べ物)であるため、部屋の景観を損ねるリスク(ノイズ)ゼロ。
我ながら完璧なディフェンスにして、鋭いオフェンスだ。 私は会社帰りにデパ地下へ赴き、おじさんの大群に紛れながら、そのバターサンドを調達した。
第4章:ホワイトデー決戦、そして無慈悲なアイドルパワー
3月14日、夜。 リビングでスマホをいじる娘に、私は計算し尽くされた紙袋を差し出した。
「お、サンキュ」 中を見た娘の目が、少しだけ見開かれる。 「え、これ『〇〇(店名)』の限定のやつじゃん! TikTokでめっちゃバズってた! パパにしてはセンス良すぎない?」
勝った。 データサイエンスが、JCの複雑な感性をハックした瞬間だった。 私は心の中でガッツポーズをし、ベクトル検索のアルゴリズムに感謝した。
その時だった。 背後から、キッチンで洗い物をしていた妻(我が家の絶対君主)が声をかけた。
妻 「あ、そういえば〇〇(娘)、今日発売の『推し』アイドルのアクリルスタンドとランダム缶バッジ、届いてたよ。ホワイトデーにピッタリだね。」
娘 「えっっ!!! 嘘でしょ!? 神!! ママ一生ついていく!!!」
娘はバターサンドを机の端に置き、妻に抱きついた。その熱量は、先ほどの比ではない。
机の端に追いやられたピスタチオ・バターサンドを見つめながら、私は悟った。
ベクトル検索は確かに「世間一般のJCの最適解(グローバル最適解)」を導き出すことには成功した。
しかし、妻は娘との日々の何気ない会話から、「今、この瞬間の娘の強烈なローカル変数(推し活)」を完全に把握していたのだ。
インターネット上の数万件のデータよりも、毎晩リビングで交わされる「推しが尊い」という生きたデータのほうが、圧倒的に重み(Weight)が強かった。
データサイエンスの敗北。いや、現場でのデータ収集を怠った私の敗北である。
「パパ、これ(バターサンド)も明日学校で友達と食べるね!」
娘のその言葉に涙し、私は静かにGCPのコンソールを閉じた。 来年はリビングでのヒアリング(要件定義)から始めようと思う。
(完)

![[後編] 新入社員の歓迎ランチが不安すぎて、Google Maps APIで最強の店リストを作って武装した話](https://static.wixstatic.com/media/nsplsh_1524c14852314ae3a2f647900c5a2b38~mv2.jpg/v1/fill/w_980,h_653,al_c,q_85,usm_0.66_1.00_0.01,enc_avif,quality_auto/nsplsh_1524c14852314ae3a2f647900c5a2b38~mv2.jpg)
![[前編] 新入社員の歓迎ランチが不安すぎて、Google Maps APIで最強の店リストを作って武装した話](https://static.wixstatic.com/media/nsplsh_764a736a2d68674f454730~mv2_d_4954_3303_s_4_2.jpg/v1/fill/w_980,h_653,al_c,q_85,usm_0.66_1.00_0.01,enc_avif,quality_auto/nsplsh_764a736a2d68674f454730~mv2_d_4954_3303_s_4_2.jpg)


コメント