3.3 非構造化データからの埋め込み表現
結論: 埋め込みは「似ている」を計算できる共通座標である
埋め込み表現とは、文章、画像、動画、人物、作品などを固定長の実数ベクトルへ変換したものである。例えば、作品 を 次元のベクトルとして表すと、 となる。近い意味・内容を持つ作品ほどベクトル空間でも近くなるように、モデルはあらかじめ学習されている。
動画推薦での実用的な目的は、ベクトルそのものを解釈することではない。次のような操作を高速に行うことである。
- ユーザーが最近視聴した作品と内容が近い未視聴作品を探す
- 検索クエリの意味に近い作品を、単語の完全一致がなくても探す
- 視聴ログがない新着作品を、説明文・人物・映像の特徴から候補に入れる
- 作品の説明文、ポスター画像、映像内容、雰囲気を一つの候補検索器で補完的に使う
ただし、埋め込みの近さは「視聴される確率」や「満足度」と同じではない。例えば、内容が非常に近い作品でも、すでに見終えたシリーズ、配信終了済みの作品、年齢制限に合わない作品、同じ棚に並べるには重複しすぎる作品は推薦できない。埋め込みは主に候補生成と特徴量提供を担い、最終順位は視聴履歴、鮮度、利用可能性、表示面、ビジネスルールを含むランカーで決めるべきである。
1. 埋め込み空間で何が起きているか
作品 と のベクトルを正規化しておけば、内容の近さは内積、すなわち cosine 類似度で測れる。
値が大きいほど、モデルが見た入力上は近い。例えば、作品説明文から作った埋め込みでは、「宇宙開発」「人類の存続」「時間」「父娘」の要素を持つ SF 映画どうしが近くなりやすい。一方で、同じ「SF」でも怪獣アクション、学園ロボットアニメ、宇宙を舞台にした思索的な映画は、説明文の内容次第で別の位置に配置される。
数値を入れた最小例を考える。実際の embedding は 768 次元や 1,536 次元などの高次元ベクトルであるが、計算の仕組みは 2 次元でも同じである。仮に、ある SF 映画 と、同じく宇宙を舞台にした新着アニメ のベクトルが次のようになったとする。
まず内積は である。両ベクトルの長さは、どちらも である。従って、cosine 類似度は次のようになる。
は 1 にかなり近く、この embedding モデルは二作品を強く似ていると見なしている。ここで大切なのは、2 次元の各軸を「SF 度」「アニメ度」と解釈しないことである。実モデルの軸は多数の情報が混ざった潜在表現である。数値例は内積と正規化の計算を示すためだけのものである。
比較として、候補 のベクトルが なら、 である。両者の長さは 5 なので、 となる。この場合、モデル上は と に方向の共通性がない。実務ではこのように、各作品との類似度を全カタログに対して計算し、上位だけを候補として残す。
この性質は one-hot 表現と対照的である。
series_id=500001745 を one-hot 化したベクトルは、別の
series_id
との距離がすべて同じであり、作品内容の近さを表せない。ジャンルの one-hot
表現なら「同じ SF」という一致は表せるが、「SF
の中でも静かで思索的」「犯罪ドラマだがコメディ要素が強い」といった連続的な違いは表しにくい。埋め込みはこの中間的・複合的な関係を連続値で持たせる表現である。
一方で、各次元を「第 12 次元は悲しさ」のように読むことは通常できない。説明可能性は、近傍作品、元テキスト、明示タグ、ムードタグを併記して確保する。埋め込み単独を説明の根拠にしてはいけない。
2. 教科書の三つの利点を、動画推薦として正確に捉える
2.1 事前学習済み知識と使いやすさ
大規模なテキスト埋め込みモデルは、多様な文章の対照学習や検索学習を経ている。そのため、Hulu 固有のクリック・視聴ログだけでゼロから意味表現を学習する場合より、少量の実装で説明文や検索クエリの意味的な近さを扱い始められる。
例えば、作品 A
の紹介文に「孤独な宇宙飛行士が未知の惑星で生存を目指す」とあり、作品 B
の紹介文に「極限環境でのサバイバルと人類の希望を描く」とあるとする。語の完全一致が少なくても、事前学習済みモデルは「宇宙」「極限」「生存」「人類」という文脈から両者を近く置き得る。
genre や sub_genre
が粗い、または欠損している作品の補助として有効である。
ただし、これは「業界知識が正しい」ことを保証しない。固有の人物名、邦題・原題、シリーズ続編関係、日本の視聴習慣、Hulu 内の編成意図は、汎用モデルだけでは不十分である。また、汎用埋め込みの類似性は視聴嗜好と必ずしも一致しない。事前学習済みモデルは出発点であり、Hulu の時系列検証と必要に応じた対照学習・再ランキングで検証する対象である。
重要なのは、生成用の LLM と検索用 embedding モデルを分けることである。GPT や BERT の隠れ状態を平均しただけのベクトルでも実験はできるが、意味検索のために対照学習された embedding モデルに比べ、検索品質が安定しないことが多い。特に BERT や GPT-2 の最終層平均 pooling は、推薦・検索のために最適化された標準解ではない。まずは日本語を含む検索用の埋め込みモデルを選び、同一モデル・同一バージョンで作品とクエリを符号化するべきである。
2.2 文脈適応性とコールドスタート耐性
「銀行」という語を金融機関と川岸で区別できるという説明は、単語を単独で埋め込む場合ではなく、前後を含む文や文書を符号化する場合に成立する。例えば、次の二つは同じ語を含むが、異なるベクトルになるべきである。
金融危機に巻き込まれた銀行員のサスペンス
川岸の小屋で静かに暮らす家族の物語
動画作品では、短いタイトルだけを embedding 化するより、タイトル、概要、ジャンル、人物、タグ、公開年などを区別可能な形でまとめる方が文脈を与えられる。
新着作品 に視聴ログがない場合、協調フィルタリング由来の item embedding は学びにくい。しかし作品メタデータがあれば、コンテンツ埋め込み を直ちに作れる。過去に宇宙 SF、特定監督、思索的な雰囲気の作品を視聴したプロフィールには、その内容ベクトルが近い新着作品を候補として出せる。これがアイテム・コールドスタートを緩和する仕組みである。
ただし、コールドスタートを「解決」するわけではない。配信開始直後の視聴率は、作品内容に加えて認知度、出演者、UI 上の露出、配信タイミング、サムネイル、キャンペーンに左右される。また、プロフィールが初回利用で何の入力も履歴も持たない場合、個人化の根拠はない。初期選好アンケート、人気・新着、地域・端末・時間帯、セーフティ制約などを併用する必要がある。
2.3 異種モダリティ間の整合性
ここは用語を厳密にする必要がある。テキスト専用 LLM が画像や動画を自動的に同一空間へ投影するわけではない。画像とテキストを対応づける対照学習を行った CLIP のようなマルチモーダルモデル、またはそれと同等の設計を持つモデルが、画像とテキストを比較可能な空間に写像する。
CLIP 型モデルでは、画像エンコーダとテキストエンコーダを別々に通した後、対応する画像・説明文の内積が高く、対応しない組合せの内積が低くなるように学習する。バッチ内の 組を用いる単純化した損失は、次のように書ける。
ここで、 は画像ベクトル、 は対応するテキストベクトル、 は温度パラメータである。この学習により、テキストクエリとポスター画像、またはポスター画像どうしの近傍検索が可能になる。
この損失の一項を、二作品だけの小さなバッチで計算してみる。仮に、アニメ作品 A のポスター画像を入力し、正しい説明文 A との内積を 、別作品 B の説明文との内積を とする。温度を とすれば、画像 A に対して正しい説明文 A を選ぶ確率は次である。
このとき画像 A の損失は と小さい。反対に、学習初期などで正しい組の内積が 、誤った組の内積が なら、確率は 、損失は となる。損失を下げる学習は、前者のように「このポスターにはこの説明文」を他の説明文より強く選べるように、画像・テキストのベクトルを動かす処理である。
Hulu では、ポスター・キービジュアルは作品の世界観、人物、色調を補う。しかし画像だけから「配信終了日」「年齢レーティング」「キャスト」「吹替の有無」といった事実を確定してはいけない。情報源ごとの役割を分けるべきである。
| 情報源 | 主に表せるもの | 使い方の例 | 注意点 |
|---|---|---|---|
description, sentence,
sockets_tag |
あらすじ、テーマ、訴求 | テキスト検索、内容類似 | 宣伝文の誇張や表記揺れ |
genre, sub_genre, 人物 ID |
明示された分類・関係 | フィルタ、説明、特徴量 | 粒度・付与基準の揺れ |
avg_mood_tag |
映像から推定した雰囲気 | ムード棚、同ジャンル内の順位付け | 全作品には存在しない推定値 |
avg_fingerprint |
映像内容から作った既存ベクトル | 動画内容の近傍検索 | 次元数・生成モデルの管理が必要 |
| ポスター・映像フレーム | 見た目、人物、色調、構図 | 画像・テキスト横断検索 | 非視覚的な事実は判断できない |
3. Hulu のアイテムを embedding 化する前の設計
3.1 何を一つのアイテムとするか
item_information_table の主キーは series_id
と service_type
である。したがって、少なくとも現在のカタログと
unique_viewed_series
を接続する最初の単位はシリーズである。映画であればほぼ作品単位として扱えるが、連続シリーズの場合は「シリーズを見始める推薦」と「次のエピソードへ進む推薦」を混同してはいけない。
hj-data-01.report.unique_viewed_series
は、プロフィールが動画コンテンツ長の 25%
以上を視聴したシリーズを記録し、 last_viewing_date
は最終視聴日である。このテーブルだけからは、エピソード進捗、正確な視聴秒数、再生開始・離脱、表示されたが選ばれなかった作品を復元できない。従ってこのテーブルを使うコンテンツ
embedding
の最初の検証は、「シリーズ開始候補を出す」問題として定義するのが自然である。
SVOD と TVOD はユーザーの支払い・視聴可能性が異なる。学習時にも
serving 時にも、 service_type
を忘れて同一候補集合を作ると、視聴できない候補や意図しない課金作品を上位に出す。embedding
が近いことは、配信条件が同じことを意味しない。
3.2 作品文書をどう作るか
タイトルだけを入力すると、短すぎる、同名作品がある、続編番号しか意味を持たない、といった問題が起きる。構造化列を自然言語または明示ラベル付きテキストへ整形してから符号化すると、情報の種類を保ちやすい。
例えば、映像作品を表す入力文書を次のように構成する。欠損値は文字列
null として書くのではなく、その行を省く。
作品名: インターステラー
種別: 映画
ジャンル: 洋画 / SF
公開年: 2014
制作国: US
監督: クリストファー・ノーラン
出演: マシュー・マコノヒー, アン・ハサウェイ
タグ: アメリカ制作, 壮大な世界観, 感動的, 知的
概要: 地球の寿命が尽きかけた時代に、居住可能な惑星を探すミッションへ向かう元エンジニアの物語。
ここで、次の項目は原則として本文に混ぜない方がよい。
series_idのような意味を持たない内部 IDpublish_end_atのように日々変化し、内容類似ではなく配信可否のために使う値is_blacklistのような候補除外に使う運用フラグservice_typeのように、内容理解より serving フィルタとして確実に扱うべき値
これらを埋め込み器に読ませても、厳密なフィルタ条件にはならない。配信期間は
publish_start_at <= t < publish_end_at
のような明示ルールで候補を絞り、 is_blacklist = false
も検索前後のフィルタで保証する。
3.3 一つのベクトルに全部を詰め込まない
同じ作品でも、何を「似ている」としたいかで良い表現は異なる。例えば、人物経由の類似、映像の雰囲気、あらすじのテーマ、シリーズ継続の近さは別物である。一つの万能 embedding に全責務を負わせるより、複数の表現と検索経路を持つ方が堅牢である。
| 表現 | 入力 | 得意な近さ | Hulu での役割 |
|---|---|---|---|
| テキスト embedding | タイトル、概要、タグ、人物 | テーマ、設定、物語、人物 | 意味検索、コールドスタート候補 |
| 映像 embedding | 映像内容 | 見た目、演出、映像的な内容 | avg_fingerprint を使う類似候補 |
| ムード特徴 | avg_mood_tag |
感情、緊張感、思索性 | ムード棚、ランカー特徴 |
| 協調フィルタリング embedding | 視聴共起 | 実際に一緒に見られる傾向 | warm アイテムの候補生成 |
| 明示メタデータ | ジャンル、人物、年、尺 | 確定した条件・説明 | フィルタ、特徴量、説明 |
例えば、テキスト埋め込みで「宇宙」が近くても、ユーザーは映像的には明るい子ども向け作品を好むかもしれない。
avg_mood_tag
により「intense」「melancholic」が高い候補を抑える、といった調整は最終ランカーの方が適している。
4. テキスト embedding を得る方法
4.1 まずは専用モデルでオフライン生成する
作品カタログは、毎リクエストで LLM に渡して embedding
を生成する必要がない。作品メタデータの更新、配信開始、説明文の修正を契機としてオフライン生成し、
series_id
とバージョンに紐付けて保存する。リクエスト時は保存済みのベクトルを検索するだけにする。
概念的な Python 例は次の通りである。実際には採用したモデルの入力長、正規化方法、バッチサイズ、認証方法に合わせる必要がある。
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("<retrieval-embedding-model>")
documents = [
"作品名: ...\\nジャンル: ...\\n概要: ...",
"作品名: ...\\nジャンル: ...\\n概要: ...",
]
# normalize_embeddings=True なら内積検索が cosine 類似度と一致する。
item_embeddings = model.encode(
documents,
normalize_embeddings=True,
batch_size=128,
)教科書にある last_hidden_state.mean(dim=1)
は、説明用の最小例としては理解できる。しかし padding token
を除外せず単純平均すると表現が歪む可能性があり、また GPT-2
は文埋め込み検索を目的に学習されたモデルではない。独自に pooling
を実装する必要がある場合は attention mask を考慮する。
ここで、 は位置 の隠れ状態、 は padding でないことを表す mask、 は系列長である。それでも、検索用に学習済みのモデルが提供する推奨 pooling を優先する方がよい。
数値例として、作品文書を token 化した結果、実トークン 3 個と padding 1 個があり、2 次元に単純化した隠れ状態が次の通りだったとする。
ここで、 は文書の内容ではなく、系列長をそろえるために追加した padding の位置である。mask を 、 とすると、pooling 結果は次のようになる。
もし mask を無視して 4 個を平均すると、結果は になり、padding の値に不当に支配される。このため、独自実装では attention mask が必須である。ただし実在のモデルでは特殊 token、位置情報、推奨 pooling 方式も関わるので、この数値例をそのまま実装仕様にしてはいけない。
4.2 入力テンプレートとバージョンを保存する
埋め込みの再現性は、モデル名だけでは足りない。例えば、ある日に
description
だけを入力し、別の日にキャストとタグも追加すると、ベクトル空間が変わる。古いベクトルと新しいベクトルを混ぜて検索すると近傍関係が不安定になる。
最低限、各ベクトルに次の情報を持たせる。
series_id,service_type- embedding の種類。例:
item_text,item_video,item_multimodal - モデル名、モデル revision、出力次元数
- 入力テンプレートのバージョン
- 元データの更新時刻、embedding の生成時刻
- 正規化の有無
- 入力に使った列の欠損状況
これにより、「新しい sockets_tag を足したことで
Recall@100 が下がった」といった変化を追跡できる。
avg_fingerprint は dim_fingerprint=1688
のように次元数が明示されているため、別モデルの 768 次元テキスト
embedding と同じ index に無理に混ぜてはいけない。別 index
にするか、学習済みの射影層で共通空間へ写す必要がある。
4.3 長い説明文と日本語の注意点
モデルには最大トークン長がある。説明文、受賞歴、人物、タグを無制限に連結すると末尾が切り捨てられ、作品間で入力情報量も揺れる。長文は次のいずれかにする。
- 推薦に効くフィールドを優先順位付きで選び、上限長まで入れる
- 文書を複数 chunk に分け、各 chunk を埋め込み、最大類似度または集約で検索する
- LLM で事実に忠実な短い要約を作り、原文と要約の生成元を保存する
タイトルの表記揺れ、邦題・原題、全角半角、人名の中黒の違いも検索品質へ影響する。
logica_people_ids や logica_cast_ids
は文字列名より安定した正規化キーである。人名そのものを文書へ書く場合でも、データ品質上は
ID により同一人物を結び、別名辞書を管理するべきである。
5. ユーザーを embedding 化する方法
5.1 視聴済み作品の重み付き集約
プロフィール が、カットオフ時刻 より前に 25% 以上視聴したシリーズの集合を とする。最も単純な嗜好ベクトルは、視聴した作品の content embedding の平均である。
例えば、あるプロフィールがカットオフ時刻 より前に、仮にカタログに存在する「SPY×FAMILY」と「名探偵コナン」を 25% 以上視聴していたとする。説明のため、それぞれの正規化済み content embedding を次の 2 次元ベクトルと仮定する。
どちらも長さ 1 である。単純平均のユーザープロファイルは次の通りとなる。
この は、「二作品の内容的な共通性と中間的な位置」を表す。ここでも、第一軸をスパイ要素、第二軸を推理要素と読んではいけない。実際の embedding の軸はそのように分離されていない。
しかし、このままでは 5 年前に一度見た作品と昨夜見た作品を同じ重さにする。直近性を入れるなら、例えば日数差 に対する指数減衰を使う。
同じプロフィールについて、 の 1 日前に「SPY×FAMILY」を、30 日前に「名探偵コナン」を 25% 以上視聴したとする。ここでは説明用に と置く。重みは次の通りである。
重み付き平均に代入すると、プロフィールは次のように最近の視聴へ寄る。
単純平均の と比べ、直近で見た「SPY×FAMILY」の方向へ近づいたことが分かる。候補との cosine 類似度を取る前には、このユーザーベクトルも L2 正規化する。上のベクトルの長さはおよそ なので、正規化後はおよそ となる。
このデータカタログで得られる行動は「25% 以上視聴」と最終視聴日であり、明示的な高評価ではない。そのため、上式の は好意の強さではなく、利用可能な行動の重みである。もし視聴秒数、完走率、再視聴、検索、マイリスト、スキップ、表示ログが別途利用できるなら、行動種別と強度を分けて重みを設計するべきである。
5.2 平均ベクトルが失うもの
1 本のプロフィールに子ども向けアニメ、韓国ドラマ、スポーツ中継の視聴が混ざると、平均はどの嗜好にも近くない中心へ寄る。これは家族共有プロフィールや複数意図を持つユーザーで顕著である。
改善策は次の通りである。
- 直近 件だけを使い、短期意図を強くする
- 作品 embedding をクラスタリングし、複数の興味ベクトルを持つ
- 時間帯、端末、閲覧面、セッションを条件として別プロファイルにする
- sequence model や attention で、候補ごとに参照する履歴を変える
- content embedding と協調フィルタリング embedding をランカーで併用する
例えば、平日夜に海外ドラマ、休日午前にキッズ作品を視聴するプロフィールでは、候補との単一 cosine 類似度だけでなく、現在時刻・視聴面を使うランカーが必要である。
5.3 候補スコアは嗜好スコアであって最終スコアではない
ユーザーベクトルと候補作品の類似度は、候補生成の一つのスコアになる。
直前の正規化済みプロフィール を使う。二つの未視聴候補を、仮にカタログ上の新作アニメ X と新作ドラマ Y とし、それぞれの正規化済みベクトルを次のように置く。
候補スコアは次の通りである。
従って内容類似だけなら X を Y より上に置く。この比較は「X を必ず表示する」という決定ではない。X が視聴済み、配信終了、年齢制限不適合、TVOD でプロフィールが SVOD 面を利用中、といった条件なら除外または順位を下げる。ここで計算した と は、あくまで候補生成経路の一特徴である。
ベクトルを L2 正規化していればこの内積は cosine 類似度である。上位 件を取り出しても、そのまま画面に出すべきではない。既視聴、配信終了、ブラックリスト、年齢・契約条件、同一シリーズの過剰重複を除外し、人気、鮮度、視聴継続確率、コンテンツ多様性を含めて再順位付けする。
6. 画像・動画を使う方法
6.1 画像を説明文に変換する方法と直接ベクトル化する方法
ポスター画像からキャプションを生成し、そのキャプションをテキスト embedding 化する方法は、表形式のテキストパイプラインに統合しやすい。例えば「青い宇宙服の人物、暗い宇宙空間、緊張感のある構図」という説明を得られる。
しかし、生成キャプションは情報を落とし、存在しない属性を付ける可能性もある。画像とテキストを直接対応づける CLIP 型の embedding は、視覚的な近さを保ちやすい。実務では、次のように使い分けるのがよい。
- キャプション: 人が読める説明、タグ候補、ランカーの補助特徴
- 画像 embedding: 視覚検索、ポスター類似、テキストからのポスター検索
- テキスト embedding: あらすじ・テーマ・人物・作品名の検索
- 映像 embedding: フレームや音声を含む内容類似。既存の
avg_fingerprintが該当し得る
6.2 avg_fingerprint と avg_mood_tag
の位置づけ
カタログにある avg_fingerprint
は、映像の動画内容そのものから外部 AI が推定した embedding であり、
dim_fingerprint は例では 1688 である。これは LLM のテキスト
embedding
を改めて作る代替ではなく、独立した強いコンテンツ特徴である。説明文には現れない画面の色調、テンポ、人物の映り方、演出上の類似を補える可能性がある。
avg_mood_tag は、例えば emotional、
intense、 melancholic、
thought-provoking といった次元に対する 0 から 1
の推定スコアである。これを丸めて文章へ埋め込むより、連続値ベクトルとしてランカーへ渡す方が、意味と欠損を保ちやすい。
二つの作品のムードの近さを補助的に測るなら、欠損を適切に扱った上で cosine 類似度や距離を使える。ただし、タグは全作品に付与されていない。欠損を 0 と見なすと「感情的でない」のか「未推定なのか」を区別できなくなるため、欠損フラグを別特徴として持たせるべきである。
例えば、ムードの三次元だけを抜き出し、順に emotional、
intense、 melancholic とする。作品 A
の推定値が 、作品 B が
なら、内積は である。ベクトル長はそれぞれ
、 なので、cosine 類似度は
となる。この三つのムードに限れば非常に似ている、という意味である。
ただし、作品 A の値が欠損である場合に
を代入してはいけない。これは「三つのムードが全て 0
と推定された」という強い意味へ変わってしまう。また zero vector は cosine
類似度の分母を 0 にしてしまう。この場合はムード類似度を計算せず、例えば
has_mood_tag=0
をランカーへ渡し、テキスト・人物・視聴共起など別経路で候補を評価する。
6.3 異なる埋め込みを安易に平均しない
テキスト embedding が 768 次元、動画 fingerprint が 1688 次元であれば、単純な足し算はできない。仮に同じ次元へ変換できても、スケールと学習目的が異なるため、無根拠な平均は検索品質を悪化させ得る。
最初は複数経路で候補を取り、後段で統合するのが安全である。
視聴履歴
├─ テキスト埋め込み近傍検索 → 候補 A
├─ avg_fingerprint 近傍検索 → 候補 B
├─ 視聴共起・協調フィルタリング → 候補 C
├─ 人気・新着・編集棚 → 候補 D
└─ A ∪ B ∪ C ∪ D をフィルタし、ランカーで再順位付け
統合は、各経路の順位、類似度、候補源、ムード一致、人物一致、ジャンル一致をランカーの特徴量にする方法が扱いやすい。後に十分な正解データがあれば、テキスト・画像・動画を共通空間へ射影する学習や、二塔モデルを検討できる。
7. embedding の保存と近傍検索
7.1 vector database は「ID とフィルタ」を持つ検索 index である
ベクトル index には、 series_id と embedding
を追加する。近傍検索の結果を正しく候補へ変換するには、embedding
以外のメタデータも必要である。
| 保存するもの | 用途 |
|---|---|
series_id, service_type |
元作品との結合、候補の一意性 |
| embedding 本体、次元、正規化 | 類似検索 |
| embedding 種別、モデル・テンプレート版 | 再現性、index 切替 |
| 生成時刻、元レコード更新時刻 | 再生成漏れ検出 |
publish_start_at, publish_end_at |
時点における配信可能性のフィルタ |
is_blacklist、レーティング関連の情報 |
候補除外・安全制約 |
| ジャンル、人物、タイトル | デバッグ、説明、後段特徴 |
Pinecone や Weaviate のような vector database は検索とメタデータフィルタを一体で扱いやすい。FAISS は高速な近傍検索ライブラリであり、メタデータの永続化、更新、フィルタ、アクセス制御はアプリケーションまたは別ストアで設計する必要がある。選択は、候補数、更新頻度、レイテンシ、運用能力、フィルタ要件で決める。
7.2 L2 距離と cosine 類似度を混同しない
FAISS の IndexFlatL2 は二乗 L2 距離で完全探索する index
である。cosine 類似度で検索したい場合は、ベクトルを L2 正規化して
IndexFlatIP
の内積検索を使う構成が分かりやすい。正規化済みの と
については次が成り立つ。
従って、単位長ベクトルに限れば L2 距離の最小化と内積の最大化は同じ順序になる。正規化していない場合、内積はベクトルの長さの影響を受けるため、意味的な cosine 類似度とは異なる。
数値を入れると、この関係は明確になる。ユーザーの正規化済みプロファイルを 、候補作品の正規化済み embedding を とする。内積は である。従って二乗 L2 距離は次の通りである。
実際に成分ごとの差を計算しても、
となる。ここで別候補を とすると、内積は
、二乗 L2 距離は
である。つまり、内積で探すなら の
を の
より上位に置き、L2 距離で探すなら の
を の
より上位に置く。同じ順序になるため、正規化後は IndexFlatIP
でも IndexFlatL2 でも意味上は同じ近傍を得られる。
概念例は次の通りである。
import faiss
import numpy as np
# item_embeddings: shape = (n_items, dimension), float32
item_embeddings = item_embeddings.astype(np.float32)
faiss.normalize_L2(item_embeddings)
index = faiss.IndexFlatIP(item_embeddings.shape[1])
index.add(item_embeddings)
# user_embedding も同じ前処理を行う。
user_embedding = user_embedding.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(user_embedding)
scores, row_ids = index.search(user_embedding, k=500)この row_ids は index 内の行番号であり、推薦に用いる
series_id ではない。index 行番号から series_id
への不変な対応表を保存し、更新時に整合性を検証する必要がある。また、
k=500 は画面に 500
件表示するという意味ではない。配信不可・既視聴・重複除外の後にも十分な候補を残すための
oversampling である。
7.3 完全探索と近似近傍探索
アイテム数が小さい段階では IndexFlatIP
の完全探索が品質の基準となる。大規模カタログでレイテンシやコストが問題になって初めて、HNSW、IVF、PQ
などの approximate nearest neighbor を検討する。
近似 index
は高速化の代わりに、本来の近傍を取り逃がすことがある。このため、embedding
自体の Recall と index の ANN Recall
を分けて測るべきである。まず完全探索に対する recall@K
を測り、近似設定が候補検索の取り逃がしをどれだけ増やすかを確認する。
8. 埋め込みの評価: 三層を混ぜない
埋め込みの評価は、「意味空間として妥当か」「候補を取り逃がさないか」「推薦全体を改善したか」を分ける。類似度の見栄えがよくても視聴を増やすとは限らず、逆に協調フィルタリングとの組合せで初めて価値が出る場合もある。
8.1 第一層: 定性的な近傍監査
まず代表クエリと代表作品で近傍を目視する。例えば「宇宙を舞台にした思索的な SF」「心温まる家族向けアニメ」「緊張感のある犯罪サスペンス」といった検索クエリ、または既知作品を用意する。
各クエリについて、上位 20 件に次が混ざっていないかを見る。
- 作品名の偶然の一致だけで内容が無関係な作品
- 同名リメイク、別人の同名人物、別シリーズ
- 配信終了、ブラックリスト、契約対象外の作品
- 同一フランチャイズだけで埋まり、多様性がない結果
- キッズ・成人向けの安全制約に反する結果
これは主観的な評価ではあるが、致命的なデータ結合・テンプレート・フィルタの不具合を最も早く発見できる。特に日本語タイトルと人名は、少数の監査セットを継続的に持つ価値が高い。
8.2 第二層: ラベル付き類似性
STS-B のような一般テキストのベンチマークは、モデル選定の参考にはなる。しかし Hulu の「推薦上の類似」と完全には一致しない。例えば、同じ監督作品はあらすじが似ていなくても推薦上は関連することがあり、内容が非常に似た続編は既視聴ゆえに推薦しても価値が低いことがある。
そのため、ドメイン内のペア評価セットを作る方が有効である。作品ペアに対し、複数の軸を分けて 0 から 4 などでラベル付けする。
| 軸 | 問い |
|---|---|
| 内容類似 | あらすじ、テーマ、設定は近いか |
| 視聴体験類似 | 雰囲気、テンポ、感情は近いか |
| 代替推薦性 | 一方を見たい人に他方を出す価値があるか |
| 関連推薦性 | 続編、同じ人物、同じ世界観として出す価値があるか |
人手ラベル と embedding 類似度 の単調な整合性は、Spearman の順位相関係数 で測れる。 が高ければ、少なくともその評価軸に対して近い順序が人の判断と整合する。ただし「 なら良い」という固定閾値は、データ量、ラベルの一致度、評価軸によって変わる。既存モデル、単純なジャンル一致、人物一致ベースラインと比較して判断すべきである。
8.3 第三層: 時点を固定した候補検索の評価
暗黙的フィードバックで最も重要なのは、未来の行動を過去の情報だけで予測する評価である。評価時点を とし、次のように作る。
- より前の視聴をユーザー履歴にする。
- より後の評価窓、例えば 14 日間に 25% 以上視聴されたシリーズを正解集合 にする。
- 時点で配信可能だった作品だけを候補にし、履歴だけから推薦集合 を作る。
- 推薦集合と正解集合を比較する。
日付を置くと、時点固定の意味が明確になる。例えば を 2026 年 3 月 1 日 00:00 JST、評価窓を 3 月 1 日から 3 月 14 日までとする。プロフィール が 2 月 28 日までに「アニメ A」と「映画 B」を 25% 以上視聴していれば、この二作品はプロファイル作成に使える。一方、3 月 7 日に初めて 25% 以上視聴した「ドラマ C」は、正解集合には入れられるが、3 月 1 日時点の入力には入れてはいけない。C を入力に混ぜれば、モデルは未来に視聴する作品をあらかじめ知った状態で C を推薦でき、offline 指標が不当に高くなる。
同様に、3 月 1
日時点では配信されていない新作を候補に含めてはいけない。評価では、作品の
embedding、 publish_start_at、
publish_end_at、 is_blacklist を全て時点
にそろえる。この時点整合性は、モデル選定より先に守るべき評価の前提である。
複数の正解がある場合の Recall@K は次である。
Precision@K は次である。
二プロフィール、 の具体例で計算する。作品名は説明用の仮名であり、各プロフィールについて評価窓の 14 日間に 25% 以上視聴したシリーズだけを正解とする。
| プロフィール | 正解集合 | 上位 5 件 | 共通する作品 |
|---|---|---|---|
| {SF 映画 A, アニメ B} | [SF 映画 A, ドラマ C, アニメ D, アニメ B, 映画 E] | {SF 映画 A, アニメ B} | |
| {ドラマ F, 映画 G, アニメ H} | [ドラマ F, 映画 I, アニメ J, 映画 K, ドラマ L] | {ドラマ F} |
では正解 2 本を両方候補に含めたので、 、 となる。 では正解 3 本のうち 1 本だけなので、 、 となる。
上の式はプロフィールごとの値を平均する macro average なので、全体値は次の通りである。
この結果は「正解の約 66.7% を候補に残せたが、上位 5 件のうち評価窓で 25% 以上視聴されたのは平均 1.5 件だった」と読む。ただし、 のドラマ C などを「嫌い」とは言えない。今回の 14 日間に視聴されなかっただけであり、表示されなかった可能性もあるからである。
ただし、視聴されなかった候補を「無関係」と断定できないことが暗黙的フィードバックの本質的な問題である。表示されなかった、時間がなかった、すでに別サービスで見た、といった理由があり得る。したがって Precision@K の絶対値を「正答率」と解釈してはいけない。比較可能な候補集合・同じログ条件の下で、モデル間の相対差を見る指標として使う。
正解が複数あるなら、順位も評価する NDCG@K が有用である。
先ほどの を用いる。上位 5 件では、正解の SF 映画 A が順位 1、アニメ B が順位 4 にある。二値 relevance、すなわち視聴されたら 、それ以外は 0 とすると、DCG は次の通りである。
には正解が 2 本ある。理想的には両方が順位 1 と順位 2 にあるので、理想 DCG は次のようになる。
従って、 となる。Recall@5 は 1.0 であり、正解を取り逃がしていない。しかし一つを順位 4 に置いたため、順位を考慮する NDCG は 1.0 より低くなる。候補生成の評価では Recall@100 や Recall@500 を重視し、最終表示順を改善するランカーでは NDCG@10 などを重視する理由がこの違いにある。
ここで、 は順位 の候補が評価窓で視聴されたかを表す relevance、 は理想順序での DCG である。後段のランカーを評価する場合は特に重要である。
8.4 unique_viewed_series を使うときのリークと限界
このテーブルの partition key は last_viewing_date
である。未来を知らない評価では、テスト対象日より後に更新された作品情報や、将来の視聴により更新された最終視聴日を学習特徴に混ぜてはいけない。
また、同じシリーズを過去にも見ていて評価窓内に
last_viewing_date
がある場合、これは新規の作品発見ではなく再視聴または継続視聴を表す可能性がある。シリーズ開始推薦の評価なら、ユーザーとシリーズの最初の
25%
視聴日が別途必要になる。利用できない場合は、少なくとも過去履歴に存在する
series_id
を正解・候補からどう扱うかを明示し、指標の意味を限定する必要がある。
候補生成器を評価するなら、画面の最終的な 10 件ではなく、ランカーに渡す前の大きな 、例えば 100 や 500 で Recall@K を重視する。候補に正解がなければ、後段のランカーは回復できないためである。最終表示では NDCG、視聴開始率、25% 視聴率、長期継続などを合わせて見る。
8.5 第四層: 下流モデルとオンライン効果
embedding を入れた候補生成器またはランカーは、既存システムと同じ分割、同じ候補ルールで比較する。見るべき項目は単一の AUC だけではない。
- 候補生成 Recall@K: 将来視聴される作品を候補段階で残せたか
- ランキング NDCG@K、MRR、視聴開始率: 上位に適切に並べられたか
- 25% 到達率、完走率、次セッション継続: 短いクリックだけを増やしていないか
- 新着作品・長尾作品・新規プロフィール別の指標: コールドスタートで本当に改善したか
- ジャンル、年齢区分、サービス種別別の指標: 一部のカタログだけを過度に優遇していないか
- 多様性、カバレッジ、同一シリーズ重複: 体験を悪化させていないか
オンライン A/B テストでは、露出の違いがフィードバックを変える。視聴が増えたとしても、人気作品への集中、長期解約率、コンテンツ消費の偏りを確認する必要がある。offline の embedding 類似度は、オンラインでの因果効果の代わりにはならない。
9. 実装の最小構成
まずは大きな LLM をオンラインで呼ぶ構成ではなく、次の小さな検証から始めるのがよい。
item_information_tableから、配信可能・非ブラックリストのシリーズ文書を作る。タイトル、genre、sub_genre、sockets_tag、sentence、description、人物、公開年を使い、入力テンプレートを固定する。- 日本語を扱える検索用 embedding モデルで
item_textベクトルをオフライン生成する。モデルとテンプレートの version を保存する。 unique_viewed_seriesの過去履歴から、直近性で重み付けしたプロフィールベクトルを作る。候補はservice_type、配信期間、ブラックリスト、既視聴を明示的にフィルタする。- 完全探索で上位 100 または 500 件を取得し、時間分割した Recall@K と近傍監査で、人気ベースライン・ジャンルベースライン・協調フィルタリングと比較する。
- 改善が確認できたら、
avg_fingerprint経路とavg_mood_tagの特徴を追加し、候補の和集合をランカーで統合する。 - カタログ規模・更新頻度・レイテンシが必要になった時点で ANN index とオンライン実験へ進む。
この順序では、「embedding が意味的にもっともらしい」ことと、「Hulu
の暗黙的視聴で新たな候補を増やす」ことと、「最終的に良い視聴体験を作る」ことを分けて検証できる。特に既に
avg_fingerprint と avg_mood_tag
を持つ環境では、テキスト embedding
を追加して既存の映像表現を置き換えるのではなく、各々が取り逃がす作品を補えるかという観点で評価するのが適切である。
10. よくある誤解
「embedding が高類似なら必ず推薦してよい」
誤りである。高類似は候補の根拠の一つにすぎない。配信可否、年齢制限、既視聴、同一シリーズの連続、同じ候補の重複、短期意図との不一致を別途扱う必要がある。
「未視聴は負例である」
誤りである。未視聴は未露出、後で見る、興味はあるが時間がない、という状態も含む。暗黙的フィードバックで負例を作る場合は、少なくとも表示されたが選ばれなかったログ、同じ時点で利用可能だった候補、時間窓を考慮する。
unique_viewed_series
だけなら未視聴を強い負例として扱わず、ランキングの評価解釈にも注意する。
「LLM embedding は画像・動画もそのまま理解する」
誤りである。テキスト専用モデル、画像・テキスト対照モデル、動画モデル、マルチモーダルモデルは区別する。
avg_fingerprint のような動画 embedding と、説明文のテキスト
embedding は別表現として管理する。
「全作品の embedding を毎リクエスト作れば個人化できる」
誤りである。費用、レイテンシ、再現性の面で不利である。アイテムはオフラインで作成・更新し、リクエスト時にはユーザー状態の更新、近傍検索、フィルタ、ランク付けに計算を使う方が実務的である。
まとめ
非構造化データからの embedding
は、作品の文章・映像・画像に含まれる連続的な意味を、候補検索とランキングで使える数値へ変換する技術である。Hulu
のように主な信号が暗黙的な視聴である環境では、特に新着作品や視聴履歴の薄いプロフィールを、
series_id の共起だけでは扱えない内容情報で補える。
一方で、embedding は推薦器全体ではない。
unique_viewed_series の 25%
視聴という曖昧な正例、配信期間・サービス種別・年齢制約、既存の
avg_fingerprint と avg_mood_tag
の欠損や役割を明示した上で、複数候補経路と時点固定の評価を組み合わせる必要がある。最初に検証すべき問いは「ベクトルがきれいか」ではなく、「未来の視聴を候補集合に増やせるか、特に新着・長尾・履歴の薄い領域で既存手法を補完できるか」である。