3.4 LLM を活用した検索技術
はじめに: LLM を活用した検索で学ぶこと
この章では、動画サービスにおける候補検索を対象に、LLM・embedding・ANN をどのように役割分担させるかを説明する。検索技術は、作品の意味を理解する処理と、大きなカタログから候補を低遅延で見つける処理を合わせて初めて機能する。LLM を導入することと、高速な候補検索基盤を置き換えることは同じではない。
特に Hulu では、検索窓に入力された言葉から作品を探す明示検索と、視聴履歴などから「今見そうな作品」を探す推薦が共存する。両者は embedding や ANN を共有できる一方で、正解ラベル、評価方法、必要なフィルタは異なる。本章では、この違いを保ったまま、dense 検索、ANN、LLM によるクエリ理解、ハイブリッド検索を扱う。
まず、各技術が担う役割と、設計時に混同してはならない点を確認する。
最初に押さえる要点
LLM は「何を探すか」を改善し、ANN は「どこから速く探すか」を担う
LLM を活用した検索には、二つの異なる仕事が含まれる。
- LLM や embedding モデルは、ユーザーの自然言語クエリ、視聴履歴、作品説明文を意味ベクトルへ変換し、検索意図を補う
- ANN(Approximate Nearest Neighbor)index は、そのベクトルに近い作品を巨大なカタログから低遅延で取り出す
前者は「何を探しているか」を表現する問題、後者は「その表現に近い候補をどの計算量で見つけるか」という探索問題である。LLM を導入しても、数十万から数百万の作品ベクトルをリクエストごとに全比較する必要がなくなるわけではない。検索品質とレイテンシを両立するには、埋め込み、index、候補マージ、フィルタ、ランキングを分けて設計する必要がある。
Hulu の文脈では、検索の入力は二種類に分けられる。
- 明示クエリ: 例えば「泣けるアニメ」「宇宙を舞台にした映画」「子どもと見られる作品」のように、検索窓で入力された文字列
- 暗黙クエリ: プロフィールの過去視聴、直近視聴、端末、時間帯、表示面から推定した「今見そうな作品」という要求
どちらも最終的には候補 series_id
を作るが、正解の意味と失敗の原因が異なる。明示検索ではユーザーの言葉と作品の適合が重要であり、推薦では視聴履歴と将来の視聴行動の適合が重要である。両者を同じ
embedding index
で補助できても、評価、フィルタ、ランカーは分けて考えるべきである。
1. 検索パイプラインにおける位置づけ
検索・推薦の候補生成を、Hulu のカタログとログに沿って単純化すると次の流れになる。
ユーザー入力またはプロフィール状態
↓
クエリ理解
├─ クエリ正規化・LLM 書き換え
├─ クエリ embedding / ユーザー embedding の生成
└─ 意図、年齢制約、サービス面などの抽出
↓
複数の候補検索
├─ sparse 検索(タイトル・人物・タグ)
├─ dense ANN 検索(説明文・映像 embedding)
├─ 視聴共起・Two-Tower 検索
└─ 人気、新着、継続視聴、編集候補
↓
候補マージ・明示フィルタ
├─ SVOD / TVOD
├─ 配信期間
├─ ブラックリスト、年齢・キッズ条件
└─ 既視聴・同一シリーズ重複
↓
ランキング・再ランキング
↓
表示
item_information_table の
description、sentence、sockets_tag、genre、人物情報、avg_fingerprint
はコンテンツ検索の材料になる。
hj-data-01.report.unique_viewed_series
は、プロフィールがシリーズを 25% 以上視聴したという比較的強い正の行動と
last_viewing_date を提供する。後者はユーザー embedding
の集約や時系列評価に使えるが、表示されたが選ばれなかった作品、再生秒数、検索語そのものは含まない。従って、このテーブルだけで
LLM のクエリ書き換えを学習・評価することはできない。検索ログ、impression
log、再生ログがあれば別途結合する必要がある。
候補生成の目的は、最終表示を決めることではなく、ランカーが評価できる候補集合に将来の正解を残すことである。最終推薦集合を 、候補集合を とすれば、必ず次が成り立つ。
候補に入らなかった作品を、後段のどれほど良いランカーも表示できない。したがって ANN の検索品質は、候補生成 Recall とレイテンシの両方で測る必要がある。
2. dense 検索の基礎
2.1 クエリベクトルとアイテムベクトル
作品 のベクトルを 、クエリまたはユーザー状態のベクトルを とする。正規化済みのベクトルなら、内容類似の候補スコアは内積で表せる。
このとき、 なら内積は cosine 類似度と一致する。
数値を入れた例を考える。あるプロフィールが最近視聴したアニメ・推理作品から作った正規化済みベクトルを、説明のため とする。二つの未視聴候補 A、B のベクトルがそれぞれ 、 なら、スコアは次の通りである。
dense 検索は、このような内積を全作品に対して計算し、スコアが高い作品を上位から取り出す操作である。ただし、2 次元の各軸に「アニメ性」「推理性」のような人が読める意味を割り当ててはいけない。これは計算を示すための小さな例であり、実際の 768 次元などの embedding は多くの意味が分散した潜在表現である。
2.2 cosine 類似度と dot product を選ぶ基準
cosine 類似度はベクトルの向きだけを比較し、長さを無視する。dot product は向きと長さの両方を使う。モデルがどちらを目的関数として学習したかに合わせることが原則である。
- テキスト embedding を L2 正規化して意味の近さを比較するなら、cosine 類似度、または正規化後の inner product を使う
- Two-Tower モデルを dot product が大きくなるように学習したなら、通常は学習時と同じ dot product を使う
- モデル出力を勝手に正規化すると、ベクトル長に埋め込まれた人気・確信度などの情報を消す可能性がある
例えば、 、 、 を考える。 なので、cosine 類似度は A と B のどちらも 1 に近い同じ方向である。一方、dot product は 、 となる。B を上位にしたいかは、その長さをモデルが何として学習したかに依存する。index の類似度設定は、モデル選択と切り離せない。
2.3 exact NN が重くなる理由
アイテム数を 、embedding 次元を とすると、全探索による inner product 検索はクエリ一件あたり概ね の計算を要する。例えば、 、 なら、およそ 7.68 億個の積和演算を行う。CPU・GPU の並列化により実時間はこの単純な数から決まらないが、すべてのホーム表示、検索、複数候補経路で常に全探索するのは高コストである。
完全探索は不要ではない。カタログが小さい、オフライン評価、ANN の品質基準の作成、GPU で十分なレイテンシが出る、といった場合には最も単純で正確なベースラインとなる。ANN は「常に必要な高度な手法」ではなく、完全探索のコストが要求レイテンシや費用を超えた時に導入する近似手法である。
3. ANN の評価で最初に区別すべき二つの Recall
ANN を導入するとき、二種類の Recall を混同しやすい。
| 指標 | 正解 | 何を測るか |
|---|---|---|
| ANN Recall@K | exact 全探索が返す上位 の近傍 | index が正しい近傍をどれだけ取り逃がしたか |
| 候補生成 Recall@K | 未来に 25% 以上視聴された作品 | 検索器が推薦候補として有望な作品を残せたか |
ANN Recall@K は、同じクエリ・同じ item embedding に対する index 実装の品質である。exact index の上位 10 件が 、ANN index の上位 10 件が なら、次のようになる。
これは「近似 index が exact な近傍 10 件中 8 件を回収した」ことを示す。しかし、exact な embedding 近傍そのものがユーザーの将来視聴に弱ければ、ANN Recall を 1.0 にしても推薦は改善しない。逆に、ANN Recall が 0.98 でも、失った 2% が最終ランキングに影響しないならプロダクト指標はほぼ変わらないことがある。index の近似誤差と、推薦タスクの候補品質を別々に測る理由である。
4. 局所性敏感ハッシュ法(LSH)
4.1 何をしているのか
LSH(Locality-Sensitive Hashing)は、似たベクトルほど同じ bucket に入る確率を高くするハッシュ関数群である。通常のハッシュは似た入力でも別 bucket に散らすことを目指すが、LSH は逆である。
cosine 類似度向けのランダム超平面 LSH では、ランダムベクトル に対して、次の 1 bit を作る。
個の超平面で得た bit 列を、ベクトル の hash code とする。
検索時には、クエリと同じ hash code を持つ bucket、または近い hash code の bucket だけを調べる。全カタログを調べずに済む代わりに、本当の近傍が別 bucket に入れば取り逃がす。
4.2 数値を入れた例
2 次元の小さな例で、三つの超平面を次の法線ベクトルで表す。
クエリを 、作品 A を 、作品 B を とする。各内積と hash code は次の通りである。
| ベクトル | hash code | |||
|---|---|---|---|---|
111 |
||||
111 |
||||
010 |
作品 A はクエリと同じ 111 bucket に入り、B は異なる
010 bucket に入る。検索器が 111 だけを見るなら
A は候補になり、B
は比較せずに捨てられる。これは高速である一方、境界近くのベクトルなら、意味的に近くても一
bit だけ異なる bucket に入る。そこで実際の LSH では複数の hash table
を作り、少なくとも一つで同じ bucket になる確率を上げる、近い code
も探索する、といった工夫を行う。
4.3 LSH の調整と限界
一 table あたりの hash bit 数を増やすと bucket は細かくなり、候補数は減るので速くなる。しかし同じ bucket に入る確率も下がり、Recall が落ちやすい。table 数を増やすと別のランダム射影で近傍を拾いやすくなるが、メモリと構築・検索コストが増える。
| パラメータ | 増やしたときの主な効果 | 主な代償 |
|---|---|---|
| hash bit 数 | bucket が小さくなり、探索候補が減る | 近傍を別 bucket へ取り逃がしやすい |
| hash table 数 | 少なくとも一 table で近傍と衝突しやすい | メモリ、構築時間、検索回数が増える |
| probe する近傍 bucket 数 | Recall が上がる | レイテンシが増える |
LSH は理論的性質が明快で、hash だけで候補を絞れる利点がある。一方、現代の dense embedding に対する汎用近傍検索では、メモリ効率と実測 Recall/latency の面から HNSW や IVF 系が選ばれる場面が多い。LSH を「必ず高 Recall だが大量メモリ」と一般化するのではなく、データ分布、類似度、パラメータ、比較対象に依存する手法として評価するべきである。
5. 空間分割: KD-Tree と Annoy
5.1 KD-Tree が高次元で苦しくなる理由
KD-Tree は、例えば第一軸の中央値、次に第二軸の中央値というように、軸と直交する平面で空間を再帰的に分割する。低次元なら、クエリに遠い領域を大きく除外できる。
しかし embedding が 768 次元のように高次元になると、各軸に沿った分割だけでは近傍をうまく隔離しにくい。クエリに近い点が多数の領域にまたがり、正確性を保つために多くの枝を戻って探索する必要がある。この現象が次元の呪いであり、KD-Tree が低次元で主に有効とされる理由である。次元数 20 は厳密な境界ではなく、データ分布と要求 Recall により性能は変わる。
5.2 Annoy の考え方
Annoy はランダムな超平面で空間を再帰的に分割する木を複数作る。各木では、leaf に入るアイテム数が小さくなるまで分割を繰り返す。クエリ時には一つの木だけを信じず、複数木でたどり着く leaf から候補を集め、候補に対して正確な距離を再計算する。
これは LSH の「複数のランダムな見方を使う」という発想と似ているが、固定長の hash code で bucket を作るのではなく、木の分割を再帰的に行う点が異なる。複数の木を持つことで、ある木で分離された近傍が別の木では同じ leaf 近くに現れる可能性を上げる。
概念的には、 n_trees を増やすと index
は大きくなり、Recall は上がりやすい。クエリ時の search_k
を増やすとより多くの node を探索し、Recall
は上がりやすいが遅くなる。最終的には収集した候補へ exact な inner
product または距離を計算するため、候補数と rerank
コストもレイテンシへ影響する。
Annoy は読み取り中心で比較的扱いやすいが、作品の頻繁な追加・削除が必要な運用では index の再構築・切替方法を事前に確認する必要がある。カタログ更新頻度、必要な metadata filter、言語やインフラの都合を含めて選定するべきであり、「高次元だから Annoy が常に最適」とは言えない。
6. グラフ探索: NSW と HNSW
6.1 NSW の直感
NSW(Navigable Small World)では、各作品 embedding をノードとし、近い作品どうしに edge を張る。検索は、適当な入口ノードから始め、現在のノードよりクエリに近い隣接ノードがあれば移動する貪欲探索である。
例えば、クエリを「宇宙を舞台にした思索的な作品」のベクトルとする。入口ノード P の距離が 1.10 で、P の隣接ノード Q の距離が 0.45、Q の隣接ノード R の距離が 0.18 なら、探索は P Q R と進む。R の隣接ノードがすべて距離 0.18 より遠ければ、R を局所的な近傍として採用する。
この方法が速いのは、全ノードを比較せず、グラフの edge に沿って有望な方向へ移動するからである。しかし単層のグラフでは、入口が悪い、edge が局所的すぎる、といった理由で局所解に閉じ込められる場合がある。
6.2 HNSW の階層
HNSW(Hierarchical Navigable Small World)は、疎な上位層と密な下位層を重ねる。上位層は少数ノードと長距離 edge により大まかな地域へ移動し、下位層では多数ノードと局所 edge により精密な近傍へ移動する。すべてのノードは最下層に存在するが、一部だけがランダムに上位層にも昇格する。
上位層: P ───────── Q ───────── R
大きく領域を移動する
中間層: P ─ A ─ B ─ Q ─ C ─ D ─ R
粗い候補を絞る
最下層: P─a─b─A─c─d─B─e─Q─f─g─C─h─i─D─j─R
局所の近傍を精密に探索する
クエリ検索では最上層の入口から、距離が改善する限り貪欲に移動する。次の層へ降りるときには、上位層で見つけた近いノードを入口にする。最下層では、単一の
node
だけを追うのではなく、一定数の候補を優先度付きキューに保持して探索する。この探索幅が
efSearch である。
6.3 HNSW の主要パラメータ
| パラメータ | 意味 | 大きくしたときの傾向 |
|---|---|---|
| ノードあたりの接続数の上限に関わる値 | Recall 向上、グラフのメモリと構築コスト増加 | |
efConstruction |
index 構築時に候補をどれだけ広く探索するか | グラフ品質・Recall 向上、構築時間増加 |
efSearch |
query 時に保持・探索する候補幅 | Recall 向上、query レイテンシ増加 |
数値例として、同じ HNSW index に対して efSearch=20 では
exact 上位 100 件のうち 91 件を回収し、efSearch=100 では 98
件を回収したとする。前者の ANN Recall@100 は
、後者は である。一方で p95
レイテンシが 6 ms から 19 ms
に増えたなら、後者が常に良いとは言えない。候補生成では、この 7
件の差が最終的な将来視聴 Recall、表示面の予算、全体 SLO
にどれだけ効くかを測って決める。
HNSW は高い Recall と低い query 遅延を得やすい一方、グラフ edge を保持するメモリコストを持つ。ベクトル本体、ID、metadata、graph edge、複製 shard を合わせた容量で見積もる必要がある。更新・削除、filter 付き検索、shard 間の探索、再構築時の index 切替も、本番採用ではアルゴリズムと同じくらい重要である。
7. ANN 手法の比較
| 手法 | 主な index 構造 | 得意な条件 | 主な調整対象 | 主な注意点 |
|---|---|---|---|---|
| LSH | hash table / bucket | 類似度に対応した hash を使える場合 | table 数、bit 数、probe 数 | bucket 境界の取り逃がし、メモリ |
| KD-Tree | 軸に沿った分割木 | 低次元の数値空間 | leaf サイズ、探索枝数 | 高次元 embedding では効率が落ちやすい |
| Annoy | 複数のランダム分割木 | 読み取り中心の近傍検索 | tree 数、探索量 | 更新運用、filter、Recall の実測が必要 |
| HNSW | 階層的近傍グラフ | 高 Recall・低遅延を狙う dense 検索 | 、efConstruction、efSearch |
edge によるメモリ、構築・更新コスト |
| IVF | centroid による逆ファイル | 大規模で検索領域を明示的に絞る | nlist、nprobe |
cluster を外すと近傍を失う |
| IVF-PQ | IVF と圧縮コード | メモリ制約が強い大規模検索 | cluster 数、code 長、rerank 数 | 量子化誤差、学習・チューニング |
この表は選定の出発点であり、優劣の固定表ではない。同じアイテム数でも、768 次元か 1,688 次元か、cosine か dot product か、メモリ予算、更新頻度、metadata filter、GPU の有無、必要な p99 レイテンシにより最適解は変わる。候補 index は、Hulu 固有の embedding と実トラフィック分布で exact baseline と比較しなければならない。
8. FAISS: index を組み合わせるためのライブラリ
FAISS は ANN アルゴリズム一つの名称ではなく、ベクトル類似検索のためのライブラリである。完全探索、HNSW、IVF、PQ とそれらの組合せを選べる。従って「FAISS を採用する」だけでは性能特性は決まらず、どの index をどの metric・パラメータで使うかまで決める必要がある。
8.1 Flat index は品質基準である
正規化済み embedding の cosine 検索なら、inner product の完全探索を使える。概念的には次の構成である。
import faiss
import numpy as np
item_vectors = item_vectors.astype(np.float32)
faiss.normalize_L2(item_vectors)
index = faiss.IndexFlatIP(item_vectors.shape[1])
index.add(item_vectors)
query_vector = query_vector.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(query_vector)
scores, row_ids = index.search(query_vector, k=500)row_ids は index 内の行番号であり、
series_id ではない。行番号から
series_id、embedding version、service_type
への対応表を versioned
に保存する必要がある。検索結果をそのまま表示せず、配信期間、
is_blacklist、レーティング、既視聴などを別途フィルタする点も重要である。
Flat index は近似誤差がないため、HNSW や IVF を評価する際の ground truth を作るのに使える。性能が許すなら、最初のリリースで Flat を使うことも合理的である。
8.2 IVF: 全 cluster を探さない
IVF(Inverted File Index)は、事前にベクトルを 個の centroid へクラスタリングし、各ベクトルを最も近い centroid の posting list に入れる。検索時にはクエリに近い一部の cluster だけを調べる。
小さな 2 次元例では、centroid が 、 、 にあり、クエリが だとする。L2 距離の二乗は次の通りである。
nprobe=1 なら最も近い の posting
list だけを見る。全 100 万作品が 1,000 cluster
に均等に分かれていれば、平均では約 1,000
件だけを詳細比較するので高速になる。一方、真の近傍が cluster
側に誤って割り当てられていれば、
nprobe=1 では絶対に見つからない。 nprobe=2 や
nprobe=10 と増やせば候補が増え、Recall
は上がりやすいが遅くなる。
8.3 PQ: ベクトルを圧縮する
PQ(Product Quantization)は、ベクトルを複数の部分ベクトルに分割し、各部分を小さな codebook の代表値で近似する。例えば 768 次元の float32 ベクトルは、ベクトル本体だけで byte である。これを 96 個の部分に分け、各部分を 256 通りの code の 1 byte で表せば、code 部分は 96 byte になる。概念上、ベクトル値だけを見ると約 倍の圧縮である。
ただし実際の index 容量には、ID、posting list、codebook、alignment、graph や shard の複製なども加わる。また圧縮したコードを使う距離は近似であり、正確な top-K を取り逃がし得る。実務では IVF で候補 cluster を絞り、PQ で多くのベクトルを圧縮して概算スコアを出し、上位の少数候補だけ元の float ベクトルで rerank する構成がよく使われる。
この rerank を数値で考える。PQ の近似スコアで作品 A、B、C、D が と出たとする。上位 3 件だけを元のベクトルで正確に計算すると になり、最終順は B、A、C へ変わる。近似検索は候補を安く絞る役割、正確な rerank は上位の順序を直す役割である。rerank 対象を何件残すかも、Recall とレイテンシの調整点になる。
9. ScaNN をどう位置づけるか
ScaNN は大規模なベクトル検索で Recall とレイテンシを両立することを目的にしたライブラリ・手法群である。大まかには、検索空間の分割、量子化による近似スコアリング、上位候補の精密な再順位付けを組み合わせる。特に、dot product 検索で重要なベクトル方向の誤差を意識した異方性量子化が特徴である。
「異方性」とは、すべての誤差方向を同じ重さで扱わないという意味である。dot product 検索では、クエリ方向への誤差が順位へ大きく効く。そこで、単純にベクトルのユークリッド距離を小さくするだけではなく、検索スコアを壊しやすい誤差をより抑えるように圧縮を設計する。
FAISS と ScaNN は排他的な概念ではない。どちらもベクトル検索を実装する選択肢であり、利用環境、CPU/GPU、metric、アイテム数、更新方法、依存関係、運用能力を含めてベンチマークで選ぶべきである。公開ベンチマークの順位をそのまま Hulu の本番選定に持ち込んではいけない。Hulu の embedding 分布、filter 後の候補数、検索リクエストの偏り、p95/p99 要件で比較する必要がある。
10. LLM 強化型検索 1: クエリ書き換え
10.1 クエリ書き換えは「検索語を増やす」だけではない
検索クエリは短く曖昧であることが多い。例えば「泣けるアニメ」は、家族愛、成長、別れ、感動的な音楽、日常系など、複数の意図を含み得る。「コナン」はテレビシリーズ、劇場版、人物、関連作品のどれを探すのか不明である。
LLM によるクエリ書き換えの役割は、入力を勝手に一つの意味へ断定することではない。検索 index と照合しやすい形へ正規化し、曖昧性を保った複数の検索表現を作ることである。
例えば、原クエリが 子どもと見られる 1 時間くらいの作品
なら、LLM
の出力は自由な一文ではなく、次のような構造化された検索計画にする方が監査しやすい。
{
"normalized_query": "子どもと一緒に見られる短時間の映像作品",
"lexical_terms": ["キッズ", "ファミリー", "短編"],
"semantic_intent": ["family-friendly", "short-duration"],
"hard_filters": {
"kids_compatible": true,
"max_duration_minutes": 70
},
"ambiguity": ["子どもの年齢は不明"]
}この出力をそのまま事実として扱ってはいけない。
max_duration_minutes=70 はユーザー文の「1
時間くらい」を便宜上表した仮説である。実際の video_duration
の形式を分単位へ安全に変換し、年齢適合は kids_mature_flg や
rating_name などの信頼できるカタログ列で判定する。LLM
の文面だけで年齢制限を判断してはいけない。
10.2 Hulu 向けの具体例
ユーザーが 頭を使うアニメ と入力したとする。LLM
は次のように候補意図を出せる。
- 推理・謎解き
- 心理戦・戦略
- SF 的な設定を理解する面白さ
ただし、この三つのどれをユーザーが意図したかは確定していない。そこで、原クエリを捨てず、複数の検索経路を並列に走らせる。
原クエリ sparse 検索: "頭を使う アニメ"
書き換え 1 sparse 検索: "推理 謎解き アニメ"
書き換え 2 dense 検索: "心理戦や戦略的な展開を楽しめるアニメ"
書き換え 3 dense 検索: "複雑な設定を考察しながら見るアニメ"
人物・ジャンル・配信条件 filter
候補をマージしてランク付け
この方法なら、「頭を使う」を LLM が誤って推理だけに狭めても、原クエリと他の解釈が候補を補う。候補の説明には、実在の metadata に基づく「推理」「心理戦」タグや概要文を使い、LLM が生成した根拠をそのままユーザーへ表示しない方が安全である。
10.3 クエリ書き換えの失敗と制御
クエリ書き換えには query drift
がある。これは、書き換え後のクエリが元の意図から離れる失敗である。例えば
静かな映画 を LLM が
睡眠前に見られる癒やしの映画
と拡張すると、「静かなサスペンス」や「静かなドキュメンタリー」を落とすかもしれない。
制御策は次の通りである。
- 原クエリによる検索結果を必ず候補へ残す
- 書き換えは一つに決めず、少数の代替解釈を生成する
- 人物名、作品名、固有名詞は entity resolution を経て ID に正規化する
- 年齢、料金、配信可否などの hard constraint はカタログ列・ルールで検証する
- 生成された語と検索結果をログに残し、ゼロ件率、改変率、検索後視聴率を監査する
- 入力にない属性を「ユーザーが望む」と確定しない。曖昧なら query clarification を出す
例えば コナン 映画 は、作品名 entity と
media_type、シリーズ関係で検索するのが基本である。LLM
が「推理アニメ映画」という一般語を足してもよいが、同名・類似名の作品を取り違えないよう、正規化済みの
series_id や作品関係データを優先する。
11. LLM 強化型検索 2: 文脈的拡張
11.1 「履歴全部をプロンプトへ入れる」ことではない
文脈的拡張は、クエリにユーザーの長期嗜好・直近行動・セッション意図を足して検索を個人化する方法である。ただし、履歴の全タイトルを LLM プロンプトに連結することと同義ではない。長い履歴には古い興味、家族共有による複数嗜好、偶然視聴、ネタバレにつながる情報が混ざる。
Hulu の unique_viewed_series
で確実に分かるのは、シリーズを 25%
以上視聴したことと最終視聴日である。したがって、まずは直近性を持つ user
embedding や、ジャンル・人物・ムードの集計を使う方が堅実である。LLM
を使う場合も、プロフィールを次のような限られた構造へ要約し、元の作品 ID
と根拠を保持する。
{
"recent_interests": ["推理", "スパイコメディ"],
"long_term_interests": ["アニメ", "犯罪サスペンス"],
"evidence_series_ids": [500001, 500002, 500003],
"uncertainty": ["キッズ作品の視聴は共有プロフィール由来の可能性"]
}この要約は「プロフィールの真の人格」を断定するものではない。検索器が使う一時的な仮説であり、古さ、根拠作品、生成モデル・プロンプト version を保存して監査するべきである。
11.2 数値を入れた文脈統合の例
LLM を必ず使わなくても、クエリ embedding と履歴 embedding
は重み付きで統合できる。クエリ 宇宙を舞台にした映画
の正規化済みベクトルを
、直近視聴から作ったプロフィールベクトルを
とする。検索語を優先して
とすると、統合前ベクトルは次である。
このベクトルの長さは なので、cosine 検索の前にはおよそ へ正規化する。 なら明示クエリだけを使い、 なら履歴だけを使う。検索では通常、ユーザーが今打ち込んだ言葉を上書きしないよう を高めにし、履歴は同点の候補を補う程度に扱う。
LLM
は、この線形結合で表せない複雑な文脈を補助できる。例えば、ユーザーが
家族で見られるもの
と検索し、直近で長尺の海外ドラマを見ていても、LLM
は現在クエリの「家族で」を主制約として解釈し、キッズ・ファミリー向けの
filter
を検索計画へ入れられる。しかし、年齢・利用可能性の判定は依然として
rating_name、 kids_mature_flg、
service_type、配信期間などの構造化データで行う。
11.3 文脈拡張を使わない方がよい場合
固有作品名、人物名、エピソード名などの navigational query
では、個人化が邪魔になることがある。ユーザーが
インターステラー
と入力したなら、過去にアニメを多く視聴していたからといって、アニメ作品を上位に混ぜるべきではない。完全一致タイトル・entity
の候補を優先し、関連推薦は別棚に分ける方が期待に沿う。
また、共有プロフィール、キッズ利用、センシティブなテーマ、履歴が古いプロフィールでは、文脈を弱める・使わない方が安全な場合がある。パーソナライゼーションは常に強いほど良いのではなく、現在クエリの明確さ、履歴の信頼度、表示面の目的に応じて制御するべきである。
12. LLM 強化型検索 3: ハイブリッド検索
12.1 sparse と dense は競合ではなく補完である
sparse 検索の BM25
は、タイトル、固有名詞、出演者、監督、正確な用語に強い。例えば
クリストファー・ノーラン
や固有の作品タイトルを探すとき、文字列一致は強い根拠である。一方で dense
検索は、考えさせられる宇宙映画
のように、作品説明の語と完全一致しなくても意味が近い候補を拾える。
さらに Hulu には、テキストだけでなく avg_fingerprint、
avg_mood_tag、視聴共起などの候補経路がある。新着作品にはコンテンツ
embedding、warm な作品には視聴共起、曖昧な検索には sparse と dense
の併用、といった補完関係を利用する。
クエリまたはプロフィール
├─ BM25: タイトル・人名・タグの語一致
├─ text ANN: 概要・テーマの意味類似
├─ video ANN: 映像内容の近さ
├─ CF / Two-Tower: 視聴行動の近さ
└─ 人気・新着・編集: 非個人化の安全な候補
↓
candidate union + filter + fusion
↓
ranker / reranker
12.2 Reciprocal Rank Fusion の数値例
異なる検索器のスコアは、単純に足せないことが多い。BM25 のスコア 12.0 と cosine 類似度 0.78 は尺度も分布も異なるからである。学習データが十分でない初期段階には、順位だけを使う RRF(Reciprocal Rank Fusion)が扱いやすい。
ここで、 は作品 を返した検索器の集合、 は検索器 における順位、 は上位順位の影響をなだらかにする定数である。
例えば、 とする。作品 A は BM25 で 2 位、text ANN で 20 位、CF には出なかったとする。作品 B は text ANN で 2 位、CF で 3 位、BM25 には出なかったとする。
この例では B の方が高い。これは、二つの独立した検索経路で高順位だったことを評価している。一方、同じ作品が実質的に重複した三つの dense index から出ただけなら、RRF が根拠を過大評価する可能性がある。候補源の独立性、同一シリーズの重複、人気度バイアスを後段で確認する必要がある。
impression
と視聴のログが十分にあり、時間的リークを防げるなら、検索器別スコア・順位・候補源・ユーザー文脈・作品
metadata をランカーに入力して学習融合する方が柔軟である。ただし
unique_viewed_series
単体には表示機会がないため、未視聴を強い負例とみなして融合器を学習してはいけない。
13. Hulu での filter と retrieval の順序
embedding の近傍検索は、ハード制約を守る仕組みではない。例えば、ユーザー embedding に近い TVOD 作品が検索上位でも、SVOD 面で見放題作品だけを出す要件なら、その作品を出してはいけない。
原則として、次の制約は明示的な metadata filter または検索後 filter で保証する。
is_blacklist = false- リクエスト時刻 に対して
publish_start_at \leq t < publish_end_at。終了日時が null の場合の定義も明示する - 表示面と契約に合う
service_type - 年齢・キッズ表示面に関する制約
- 既視聴・継続視聴の扱い
- 同一シリーズ、同一フランチャイズ、同一人物への過剰集中の制御
filter を index 内で効率的に適用できるかは、候補 index の選定条件である。filter を検索後だけに適用する場合、上位 件を取得してから多くを除外すると候補不足になる。例えば最終的に 500 件必要で、配信条件・既視聴除外により検索候補の 60% が落ちるなら、平均的には少なくとも 件以上を oversample して取り出す必要がある。除外率はプロフィール・表示面・時期で変わるため、固定値を盲信せず計測する。
ここでも、unique_viewed_series の「25%
以上視聴」は作品に対する負の嗜好を意味しない。既視聴除外はホームの新規発見棚では自然でも、再視聴棚、継続視聴、シリーズ推薦では適切でない場合がある。表示面の目的ごとにルールを分けるべきである。
14. 評価とチューニング
14.1 index 単体の benchmark
同一の query set、同一の item embedding、同一の filter 条件で、Flat index を ground truth として ANN を比較する。
- ANN Recall@100、ANN Recall@500
- p50、p95、p99 レイテンシ
- QPS、CPU/GPU 使用率、メモリ使用量
- index 構築時間、更新反映時間、再構築時の二重容量
- filter 後の候補不足率
評価 query はランダムなユーザーだけでなく、短いクエリ、長いクエリ、新規プロフィール、長期プロフィール、SVOD/TVOD、キッズ面などに分ける。平均レイテンシだけがよくても、特定の filter や人気クエリで p99 が悪化すればプロダクト上の問題になる。
14.2 推薦・検索タスクとしての評価
推薦候補器なら、時点 を固定し、 より前の 25% 視聴だけで user representation を作る。 より後の評価窓に 25% 以上視聴したシリーズを正解とする。例えば、 が 2026 年 3 月 1 日なら、3 月 7 日の視聴をプロフィール特徴に含めてはいけない。未来情報を入れると候補生成 Recall が不当に高くなる。
検索クエリなら、query impression、検索結果、クリック・再生・25% 到達を時刻順に分割する。見るべき指標は検索面の目的で異なる。
| 表示面 | 主な offline 指標 | 補助指標 |
|---|---|---|
| 候補生成 | future-view Recall@K | ANN Recall、候補カバレッジ、候補不足率 |
| 検索結果 | query-to-watch Recall、NDCG@K | zero-result 率、query rewrite の改変率 |
| ホーム推薦 | NDCG@K、視聴開始・25% 到達の予測 | 多様性、新着・長尾の露出 |
| 継続視聴 | 次エピソード・次シリーズの到達率 | 再視聴との区別 |
オンラインでは、検索後の視聴開始、25% 到達、セッション継続、長期継続、zero-result、query reformulation 率を確認する。単にクリックが増えても、短い誤クリックや人気作品への過度な集中だけを増やしていないかを見る必要がある。
14.3 パラメータをどう決めるか
HNSW の
、efConstruction、efSearch、IVF
の nlist、nprobe、PQ の code 長、Annoy の tree
数・探索量は、すべて
Recall、レイテンシ、メモリの交換条件である。一つの推奨設定は存在しない。
実務的な手順は次の通りである。
- Flat index で exact な近傍と必要な候補数を測る。
- 許容する p95/p99 レイテンシとメモリ予算を決める。
- 少数の parameter grid で ANN Recall とレイテンシの Pareto frontier を作る。
- Pareto 上の構成だけを使い、未来視聴 Recall と最終ランカー指標を測る。
- shadow traffic で exact/ANN の候補差、filter 後候補数、エラーを監視する。
- 小さなトラフィックでオンライン実験を行い、index version を即時に戻せる状態で段階展開する。
この順序なら、「ANN Recall が 0.99 だから良い」という早すぎる結論を避けられる。例えば ANN Recall を 0.97 から 0.99 に上げても、最終的な future-view Recall やオンライン 25% 到達率が変わらず、p99 だけ大きく悪化するなら、その追加コストは正当化されない。
15. 本番運用で必要なデータ管理
検索 index はモデル成果物である。
series_id、embedding、metadata
をその場で更新するだけでは、再現性を失う。少なくとも次を version
として保持する。
- item embedding のモデル名、revision、次元、正規化方法
- 作品文書テンプレートと使用列。例えば
description、sockets_tag、人物、ジャンル - index 種別と parameter。例えば HNSW の
、
efConstruction、efSearch - item catalog のスナップショット時刻
series_idと index row ID の対応表- filter metadata の version と更新時刻
- query rewrite のモデル、プロンプト、schema、fallback 有無
新しい index は offline で構築・検証し、旧 index と並行して shadow
query を流し、候補・score・filter 後件数を比較してから切り替える。
publish_end_at を過ぎた作品が index
に残ること自体は許容できても、serving filter
に不備があって表示されることは許容できない。index
の更新遅延と配信可否の更新経路を分離して監視する必要がある。
16. よくある誤解
「ANN の Recall が高ければ推薦も良い」
誤りである。ANN Recall は exact embedding 近傍の再現であり、ユーザーが将来見る作品の再現ではない。embedding、候補源、filter、ランカー、表示面の評価を分ける必要がある。
「LLM に作品候補を直接生成させれば ANN は不要」
誤りである。LLM が存在しない作品、配信されていない作品、誤ったタイトルを生成する危険がある。LLM はクエリ理解・書き換え・候補説明・構造化検索計画に使い、候補 ID はカタログと index から取得する設計が監査しやすい。
「クエリ書き換えは長い方が良い」
誤りである。語を増やしすぎると query drift、意図しない filter、固有名詞の希釈を起こす。原クエリを残し、書き換えの目的を正規化・曖昧性展開・語彙補完に限定する。
「dense 検索だけで十分である」
誤りである。固有作品名・人物名は sparse 検索が強く、行動上の関連は CF
や Two-Tower が強く、利用可能性は metadata filter が担う。Hulu の
avg_fingerprint、avg_mood_tag、テキスト
embedding も互いの代替ではなく、異なる情報源である。
「未視聴を負例として自由に使える」
誤りである。 unique_viewed_series
にない作品は、嫌われたのではなく表示されなかった、時間がなかった、別のコンテキストで見たい、という状態を含む。融合ランカーや
query rewrite の学習・評価では、可能なら impression log
と位置情報を使い、時点・露出を考慮する。
まとめ
LLM を活用した検索では、LLM は検索意図を理解・正規化・文脈化し、ANN は埋め込み空間から候補を高速に取り出す。両者は補完関係にあり、どちらか一方で推薦は完成しない。
Hulu では、説明文・タグ・人物から作る text embedding、映像内容の
avg_fingerprint、雰囲気の avg_mood_tag、25%
視聴に基づく行動表現を複数経路として扱うのが自然である。その上で
service_type、配信期間、ブラックリスト、年齢条件、既視聴を明示的に制御し、ANN
Recall、将来視聴
Recall、レイテンシ、オンライン視聴指標を分けて検証する。最初に答えるべき問いは「最も高度な
index
は何か」ではなく、「どの候補経路が、必要なレイテンシの範囲で、将来の視聴を候補集合に追加できるか」である。