ベクトル検索は「意味が近い」を勝手に混ぜてくる — 種類の違うカタログが推薦に紛れ込む罠
はじめに
個人で、映像や音楽を一つに束ねる総合プラットフォーム(“EVERY DAYS”)を作っている。動画のストリーミング(VOD)、DVD や CD の宅配レンタルといった複数のサービスを、同じ会員・同じ画面の下にまとめる構成だ。一つのレンタル事業を DMM のような総合プラットフォームへ広げていく構想は DVDレンタルからDMM型プラットフォームへ に書いた。
この作りのため、映像作品(VOD・DVD)と音楽(CD)が、同じシステムの中に同居する。そして検索・関連レコメンド・パーソナライズは、一つの埋め込み(embedding)索引でまとめて賄っている。アイテムをベクトルにして持っておき、コサイン類似で「近いもの」を返すだけの、軽い作りだ。埋め込みや意味検索そのものの話は 測定駆動でRAGの検索精度を上げる に書いた。この記事は、その構成に静かに潜む一つの罠の話だ。
罠は、種類の違うカタログが同じ索引に同居しているときに出る。映像作品(VOD・DVD)と音楽CDが同じ索引に入っていると、「この映像作品に近いもの」を出したつもりが、音楽CDが混じって返ってくる。埋め込みにとって二つは”意味が近い”からだ。だが利用者からすれば、映像を探しているのに別物を差し込まれることになる。
何が起きるか
この構成では、同じ一つの索引が三つの出口を持つ。
┌─▶ ① 意味検索:クエリ文 ───────▶ 近いアイテム
一つの埋め込み索引 ─────┼─▶ ② 関連レコメンド:あるアイテム ─▶ 近いアイテム
(全アイテムが同居) └─▶ ③ パーソナライズ:履歴の重心 ──▶ 近いアイテム
│
└─ 異種カタログ(映像/音楽 …)が同じ索引に同居
│
└─▶「意味が近い」だけで別カテゴリが上位に混入
│
(①②③の三つの出口すべてで起きる)
三つの機能は同じ索引を共有しているので、混線も三つの出口すべてで同じように起きる。一つの画面で気づいても、直すべき場所は一つではない。
なぜ起きるのか
埋め込みの類似度が測るのは「意味の近さ」だけだ。そのアイテムがどのカテゴリに属すか、利用者に出してよいものか、もう見たものか——といった業務上の制約は、ベクトルの中に入っていない。だから類似度の高い順に上から取ると、意味は近いが出してはいけないものが平気で混ざる。
言い換えると、「意味が近い=出してよい」ではない。 埋め込みは業務の境界を知らない。境界は、埋め込みの外側にある知識だ。
どう塞ぐか
対策は二つで一組になる。片方だけでは効かない。
1. 業務フィルタは、各出口に掛ける
出してよいカテゴリだけを残すフィルタを、検索・関連・パーソナライズのそれぞれの出口に置く。結果を絞るか、問い合わせの側で除外するかはどちらでもいい。大事なのは、共通の索引だからと一箇所で済ませないことだ。罠は三つの出口にあるので、フィルタも三箇所に要る。
2. フィルタするなら、取得ウィンドウを広げる
フィルタは件数を減らす。目標が10件でも、先に上位10件だけ取ってからフィルタすると、除外後に足りなくなる。だから消える分を見越して、目標の数倍を取ってからフィルタする。パーソナライズでは「もう見たもの」も外すので、目標に加えて履歴の件数ぶんも余分に取っておく。
【素朴】上位N件だけ取る ─▶ フィルタ ─▶ 残りが目標に届かない/別カテゴリが残る
▲
(消える分を見込んでいない)┘
【対策】目標の数倍+履歴件数を取る ─▶ 業務フィルタ ─▶ 既視聴も除外 ─▶ 上位から目標件数
│
上位が消えても目標件数が残るよう、広めに取る
そして、埋め込みで十分な件数を出せないときは、人気順のような別の手に静かに切り替える(graceful degrade)。品質は落ちても、結果がゼロになって画面が空になるよりはいい。
「近い」と「関係ある」の間に置くもの
「意味が近い」は「出してよい」ではない。 埋め込みの類似度は意味の近さしか測らない。カテゴリの境界も、出してよいかどうかも、埋め込みの外側にある業務知識だ。似ているからと素通しにしない。
業務フィルタは、共通の索引でも出口ごとに掛ける。 入口(索引)は一つでも、出口は複数ある。一箇所直して直った気にならない。同じ索引を共有する機能は、同じ罠を共有している。一つで踏んだら、残りも点検する。
フィルタするなら、取得を広げる。 除外で件数が減ることを前提に、目標の数倍+除外予定の件数を取ってからフィルタする。「上位が消えても目標件数が残る」ように取得ウィンドウを設計する。
埋め込みは強力だが、素直すぎる。似ているものを全部連れてくる。業務の都合を教えるのは、埋め込みの外に置いた自分のフィルタの仕事だ。
関連する記事
- 同じ埋め込み/ベクトルDBでも、運用側で踏んだ別の落とし穴(索引を二度壊した話)は 記憶用のベクトルDBを二度壊した話 にまとめた。
- 検索精度を感覚ではなく数値で上げていく話は 測定駆動でRAGの検索精度を上げる に書いた。
- 「意味が近い=関係がある」ではないので、取得したものを渡す前に関連度でふるいにかける話は RAGに「関係あるか」を見分ける小さな判定器を置く にまとめた。
- そもそも、この意味検索を専用のベクトルDBを足さずに実装した話は 「字面」でなく「意味」で探す — 専用のベクトルDBを足さずに意味検索を実装する にある。
- 同じ索引を「おすすめ」に使い、履歴の重心で推薦しつつ既視聴やCDを出口で落とす話は 視聴履歴の「重心ベクトル」で、おすすめを作る にまとめた。
- このプラットフォームの顧客アプリ全体像と記事マップは エンドユーザー向けレンタルアプリを作っている話 にある。