Embedding検索とは?Dense Retrievalの仕組みをDPR論文から解説

質問と関連文書が意味空間の近さで結ばれるDense Retrievalの概念

質問と関連文書が意味空間の近さで結ばれるDense Retrievalの概念

Embedding検索は、質問と文書を数値vectorへ変換し、vector空間で近い文書を探す方法です。単語が一致しなくても、学習済みencoderが関連する表現を近づけていれば、「休暇の残りを知りたい」という質問から「年次有給休暇の残日数確認」を含む文書を取得できます。

ただし、文章をvector化するだけで検索が完成するわけではありません。何をpositive(正例)、何をnegative(負例)として学習したか、どのsimilarity(類似度)を使うか、近似indexが正解候補を落としていないかまでが検索品質を決めます。

この記事では、Dense Passage Retrieval(DPR、密なvector表現を使う文章検索)の原論文を軸に、Dual Encoder(質問と文書を別々にencodeする構成)の学習、文書index、ANN(Approximate Nearest Neighbor、近似最近傍探索)、評価、失敗診断を順に解説します。

3行要約

  • Dense Retrievalは、queryと文書を固定長のdense vectorへ変換し、内積やcosine similarityで上位文書を探します。
  • DPRは、関連passageのscoreを上げ、negative passageのscoreを下げるように2つのencoderを学習します。
  • 本番品質はencoderだけで決まりません。exact検索との差、metadata filter、index version、domain別のRecall@kを分けて確認します。

Embedding検索とDense Retrievalとは

Dense Retrievalは、query(検索質問)とdocumentまたはpassage(検索単位の文章)を、要素の多くが非ゼロである低次元のdense vectorへ写し、その近さで文書集合(corpus)を順位付けする検索方式です。一般向けには「Embedding検索」や「ベクトル検索」と呼ばれることがあります。

「セマンティック検索」は、意味や意図を使う検索体験・system全体を指す場合があります。Dense Retrievalはその実装手段の一つです。セマンティック検索が必ず1本のdense vector検索だけで構成されるわけではありません。keyword、metadata、rerankerを併用するsystemもあります。

キーワード検索との違い

Dense Passage Retrieval for Open-Domain Question Answeringは、従来のTF-IDFやBM25を高次元のsparse表現、DPRを低次元のdense表現として対比しています。

比較軸 sparse retrieval(BM25など) dense retrieval
主な手がかり 語の一致、頻度、希少性 学習したvector空間での近さ
得意な例 製品型番、エラーコード、人名 同義語、言い換え、自然な質問
index inverted index(転置index) vector index
学習 強力な未学習baselineを作りやすい encoderの学習data・domainに依存
説明しやすさ 一致語を追いやすい 各vector次元の意味は解釈しにくい
主な弱点 vocabulary mismatch(語彙不一致) exact token、domain shift、近似誤差

たとえばqueryが「PCが熱いときの対処」、文書が「ノートパソコンの過熱時は吸気口を清掃する」なら、dense retrievalは「PC」と「ノートパソコン」、「熱い」と「過熱」の近さを学習していれば取得できます。

一方、「E1042」や「AB-19X」のような文字列は、意味の近さより完全一致が重要です。Dense Retrievalだけに置き換えるのではなく、まずBM25をbaselineとして残し、queryの種類ごとに比較する必要があります。

キーワード検索とDense Retrievalの得意な検索例の比較

BEIRも、複数domainのzero-shot評価でBM25が堅牢なbaselineである一方、dense retrieverにはdomain外一般化の余地があると報告しています。

token Embeddingと検索用Embeddingは役割が違う

順番3で扱ったtoken Embeddingは、token IDをTransformerが計算できるvectorへ変える入口でした。Dense Retrievalで比較するのは、query全体やpassage全体を代表する検索用vectorです。

粒度 入力 出力 主目的
token embedding token ID tokenごとのvector neural network内部の入力表現
contextualized representation token列 文脈を反映したtoken表現 分類、抽出、生成など
retrieval embedding queryまたはpassage 検索単位を代表するvector 関連文書のranking

token Embeddingから検索用passage vectorまでの粒度の違い

検索用vectorは、単にBERTの出力を取り出せば自動的に良くなるわけではありません。「このqueryにはこのpassageが関連する」というdataを使い、検索順位に合うようencoderを調整します。

Embeddingの基礎から確認したい場合は、Embeddingとは?単語や文章をベクトルで表す仕組みも参照してください。

DPR論文が解いた問題

DPRが対象にしたopen-domain QA(大規模な文書集合から答える質問応答)では、すべての文書をreaderへ入力できません。先にretrieverが小さな候補集合を選び、その後にreaderが答えを抽出します。

corpusを C、questionを q、取得件数を k とすると、retrieverは概念的に次の写像です。

\[R(q, \mathcal{C}) \rightarrow \mathcal{C}_F,\qquad |\mathcal{C}_F|=k\ll|\mathcal{C}|\]

DPR論文では、top-kにanswer spanを含むpassageが少なくとも1件あるquestionの割合をtop-k retrieval accuracyとして評価しました。retrieverが正解passageを候補へ入れなければ、後段readerが高性能でも答えられません。

論文はNatural Questions、TriviaQA、WebQuestions、CuratedTREC、SQuADの5 datasetで評価し、強いLucene-BM25 baselineに対してtop-20 passage retrieval accuracyを多くの場合9〜19 absolute point改善したと報告しています。ただし、これは論文の英語Wikipedia、質問応答dataset、学習条件での結果です。「どのcorpusでもdense retrievalがBM25より優れる」と一般化はできません。

Dual Encoderは質問と文書を別々に処理する

DPRはquestion encoder E_Q とpassage encoder E_P を使います。原論文では独立した2つのBERT-baseを用い、それぞれの[CLS]表現から768次元vectorを作りました。

question q とpassage p のscoreは内積です。

\[\operatorname{sim}(q,p)=E_Q(q)^{\mathsf{T}}E_P(p)\]

関連passageのvectorがquestion vectorと近い方向・大きな内積になれば、scoreが上がります。

Question EncoderとPassage Encoderが共通の意味空間を作るDual Encoder

なぜCross EncoderではなくDual Encoderなのか

queryとpassageを連結し、両者のtokenをcross attentionで詳しく比較するmodelは、pairごとにneural network推論が必要です。corpusが100万passageなら、1 queryに対して100万pairを重いmodelへ通すことになります。

Dual Encoderでは、passage側をqueryと独立に計算できます。文書登録時に全passageをencodeしてindexへ保存し、検索時はqueryを1回encodeしてvector同士を比較します。

観点 Dual Encoder Cross Encoder
入力 queryとpassageを別々にencode queryとpassageを同時に入力
passage事前計算 可能 原則できない
大規模候補生成 向く 全corpus比較には重い
token間の詳細比較 encoder後の単一vectorに圧縮 query-document間attentionを使える
典型的な役割 first-stage retrieval 少数候補のreranking

Sentence-BERTも、文同士を毎回pairwiseにBERTへ入力する計算を避け、各文のembeddingを比較する構成を示しました。速さは「BERTが軽くなった」からではなく、corpus側の計算を使い回せることから生まれます。

Cross Encoderとrerankerの詳細は、次回のChunking / Rerank編で扱います。

positiveとnegativeから検索空間を学ぶ

DPRの学習instanceは、question q_i、関連するpositive passage p_i^+、関連しないnegative passages p_{i,j}^- で構成されます。

positive passageのsoftmax確率を高めるlossは次の形です。

\[L_i=-\log \frac{ \exp(\operatorname{sim}(q_i,p_i^+)) }{ \exp(\operatorname{sim}(q_i,p_i^+)) +\sum_j \exp(\operatorname{sim}(q_i,p_{i,j}^-)) }\]

このlossは、positiveのscoreだけを上げるのではなく、同じ候補集合にいるnegativeよりpositiveを上へ並べるよう学習します。

negativeの難しさが学習を変える

DPRは次のnegativeを比較しました。

negative 作り方 学べること 注意点
random negative corpusから無作為に選ぶ 明らかに無関係な文書を離す 簡単すぎて細かな順位差を学びにくい
BM25 hard negative 語は一致するがanswerを含まない上位文書 表面的に似た誤候補を見分ける 実は正解であるfalse negative混入に注意
in-batch negative 他questionのpositiveをnegativeとして再利用 1 batchで多数の比較を作る 同じ話題の別正解を誤ってnegativeにし得る

batch sizeを B とし、question vector matrixを Q、passage vector matrixを P とすると、score matrixは次の計算で一度に作れます。

\[S=QP^{\mathsf{T}},\qquad S\in\mathbb{R}^{B\times B}\]

対角成分 S_ii がpositive、同じrowの他要素がnegativeです。1 batchで B^2 個のquestion-passage scoreを再利用できます。

in-batch negativesのscore matrixとhard negative

DPRの実験では、in-batch negativesにBM25 hard negativeを1件加える構成が有効でした。2件へ増やしても同じ条件では改善しなかったため、hard negativeは多ければよいわけではありません。

false negativeを監査する

「answer文字列を含まないからnegative」と決めると、別表現で正しく答えるpassageをnegativeにする場合があります。社内文書でも、旧制度の説明、別部署の同義文、表の見出しを失ったchunkが混ざります。

training dataでは次を記録します。

  • query ID
  • positive source IDと選定根拠
  • negative source IDと採掘方法
  • encoder version
  • annotation version
  • false negative確認status

lossが下がっても、誤ったlabelへ適合していれば検索品質は上がりません。

Indexingとquery-timeを分ける

Dense Retrievalの運用は、文書を準備するoffline側と、質問へ応答するonline側に分かれます。

Indexing

  1. source documentを検索単位へ分ける。
  2. passage textとtitleをpassage encoderへ入力する。
  3. vectorとpassage ID、source ID、version、metadataを対応付ける。
  4. exactまたはANN indexへ追加する。
  5. index versionを固定し、評価済みsnapshotとして切り替える。

Query-time

  1. queryを同じmodel familyのquestion encoderでvector化する。
  2. tenantやdocument種別などのfilter条件を確定する。
  3. indexからtop-k候補を検索する。
  4. passage IDから本文と出典metadataを取得する。
  5. score、rank、encoder/index versionをtraceへ残す。

offline indexingとquery-time検索、index version切替

DPR公式repositoryは、static documentのrepresentation生成が並列化可能である一方、規模によっては長い処理になると説明しています。encoderを更新すると、古いpassage vectorと新しいquery vectorが同じ空間にある保証がなくなります。したがって、新encoder用indexを別versionで作り、評価後に切り替える方が安全です。

更新対象 必要な対応
passage本文 対象passageを再encodeしindex更新
metadataだけ vectorを変えずmetadata/filter indexを更新できる場合がある
passage encoder 原則としてcorpus全体を再encode
question encoderだけ offline vectorは残せるが、互換性を評価する
similarity/normalization vector再計算またはindex再構築が必要になり得る

内積・cosine similarity・L2距離を揃える

「vectorが近い」の定義は一つではありません。

Inner product

\[s_{\mathrm{IP}}(x,y)=x^{\mathsf{T}}y\]

内積は方向だけでなくvector norm(長さ)の影響を受けます。DPRは内積を使います。

Cosine similarity

\[s_{\cos}(x,y)=\frac{x^{\mathsf{T}}y}{\lVert x\rVert_2\lVert y\rVert_2}\]

queryとdatabase vectorをL2 normalizeしてunit vectorにすれば、cosine similarityは内積と一致します。

L2 distance

\[d_{\mathrm{L2}}(x,y)^2=\lVert x-y\rVert_2^2\]

unit vector同士では、Faissのmetric資料が示すように次の関係になります。

\[\lVert x-y\rVert_2^2=2-2x^{\mathsf{T}}y\]

したがって、両側を正規化した条件では「cosine similarity最大」「inner product最大」「L2 distance最小」は同じ順位になります。正規化していないvectorでは同じではありません。

正規化したvectorにおけるcosine similarity、内積、L2距離の関係

model評価時はcosine、indexはinner product、queryだけnormalizeという不一致があると、Notebookと本番で順位が変わります。metric名だけでなく次を一組で固定します。

  • encoder名とversion
  • pooling方法
  • vector次元
  • query/documentのnormalization有無
  • index metric
  • scoreが大きいほど良いか、小さいほど良いか

exact searchとANNを分ける

exact searchは、query vectorと対象vectorをすべて比較します。正しいmetricのもとで真のtop-kを返せますが、vector数が増えるほど計算量とmemory帯域が問題になります。

ANNは、graph(近傍関係)、cluster(vectorのまとまり)、quantization(量子化)などを使って探索対象や表現量を絞ります。高速化やmemory削減と引き換えに、exact top-kを落とす可能性があります。

Faissは、index選択の比較軸としてsearch time、search quality、memory、training time、adding timeなどを挙げています。

方式 検索結果 latency memory build/update 向く用途
Flat exact metric上のexact top-k corpus増加で重くなる 生vectorを保持 単純 小規模、品質baseline
graph ANN(HNSWなど) 近似 高recallと速度を調整 graph edge分が増える 構築・削除制約を確認 memoryを確保できる低latency検索
IVF系 cluster候補だけ探索 probe数で調整 構成による trainingとparameter調整 大規模corpus
quantized index 圧縮vectorで近似 高速化しやすい 削減できる 圧縮学習・再構築 memory制約が強い環境

全件比較するexact searchと近道を使うANNの比較

HNSW論文は、階層化したproximity graphをたどるANN手法を提案しています。ここで大切なのは製品名ではなく、同じencoder・同じcorpusでexact結果を基準にANN recallとlatencyを測ることです。

2種類の「検索漏れ」を分ける

  • encoder ranking error: exact検索でもgold passageのscoreが低い。
  • ANN approximation error: exact検索ならtop-kに入るが、ANNが候補を見つけられない。

ANN parameterだけを調整しても、encoder ranking errorは直りません。反対にmodelを再学習しても、ANNの探索幅が狭すぎればgold passageは落ちます。

検索器をどう評価するか

RAGの最終回答だけを見ると、検索漏れと生成誤りを区別できません。retriever単体のtest setを作り、gold passageまたはrelevance labelを付けます。

Recall@k

relevant passage集合を R_q、top-k検索結果を D_q^k とします。

\[\operatorname{Recall@}k=\frac{1}{|Q|}\sum_{q\in Q}\frac{|R_q\cap D_q^k|}{|R_q|}\]

1質問に正解passageが1件だけなら、「top-kに正解が入ったquestionの割合」として読めます。DPRのtop-k retrieval accuracyはanswer spanを含むpassageが少なくとも1件あるかを見る定義です。自社評価では、同じ内容を説明する複数passageをrelevantとして認めるかを先に決めます。

順位も見る

metric 見るもの 向く条件
Recall@k / Hit@k relevant文書を候補集合へ入れたか 後段にrerankerやLLMがある
MRR 最初のrelevant文書が何位か 早い順位の1件が重要
nDCG@k 複数段階のrelevanceと順位 複数文書に重要度差がある
exact-match against Flat ANNがexact top-kをどれだけ再現したか index parameter調整
p50/p95 latency 典型・tail latency service運用
index size/build time memoryと更新負荷 大規模・頻繁な更新

retrieval metricが上がっても、最終回答が必ず改善するとは限りません。後段が根拠を使えない、context budgetで落とす、gold passage自体が回答に不十分という失敗があるためです。RAGとは?LLMに外部知識を使わせる仕組みで扱ったend-to-end評価と分けて記録します。

query category別に見る

平均値だけでは弱点が隠れます。test queryへ次のlabelを付けます。

  • 言い換え
  • exact keyword
  • 製品型番・エラーコード
  • 数値・日付
  • 否定を含む質問
  • domain固有略語
  • 複数条件
  • corpusに答えがない質問

「意味検索だから意味を理解する」という抽象的な評価ではなく、どのquery categoryでどの方式がgold passageを拾うかを比較します。

最小のexact検索でcontractを確認する

最初からANN parameterを増やすより、正規化とscore順を確認できるexact baselineを作ると診断しやすくなります。次は学習済みencoderが返したvectorを比較するだけの独自例です。

from __future__ import annotations

import logging

import numpy as np
from numpy.typing import NDArray

LOGGER = logging.getLogger(__name__)


def cosine_top_k(
    query: NDArray[np.float32],
    documents: NDArray[np.float32],
    k: int,
) -> tuple[NDArray[np.int64], NDArray[np.float32]]:
    """L2正規化したvectorの内積でtop-kを返す。

    Args:
        query: shape (dimension,) のquery vector。
        documents: shape (count, dimension) のdocument vector。
        k: 返す候補数。

    Returns:
        score降順のdocument indexとcosine score。

    Raises:
        ValueError: shape不一致、zero vector、不正なkの場合。

    Example:
        >>> docs = np.array([[1.0, 0.0], [0.8, 0.2]], dtype=np.float32)
        >>> ids, scores = cosine_top_k(
        ...     np.array([1.0, 0.0], dtype=np.float32), docs, 1
        ... )
        >>> ids.tolist()
        [0]
    """
    if query.ndim != 1 or documents.ndim != 2:
        raise ValueError("queryは1次元、documentsは2次元で指定してください")
    if documents.shape[1] != query.shape[0]:
        raise ValueError("vector次元が一致していません")
    if not 1 <= k <= documents.shape[0]:
        raise ValueError("kは1以上document数以下で指定してください")

    query_norm = np.linalg.norm(query)
    document_norms = np.linalg.norm(documents, axis=1)
    if query_norm == 0 or np.any(document_norms == 0):
        raise ValueError("zero vectorはcosine similarityを計算できません")

    # 評価と本番indexで同じmetric contractを守るため、両側を正規化する。
    normalized_query = query / query_norm
    normalized_documents = documents / document_norms[:, None]
    scores = normalized_documents @ normalized_query
    top_ids = np.argsort(scores)[::-1][:k].astype(np.int64)

    LOGGER.info("dense retrieval complete: documents=%d k=%d", len(documents), k)
    return top_ids, scores[top_ids].astype(np.float32)

このbaselineでgold passageの順位を測り、その後にANN indexへ差し替えます。結果が悪化した場合、同じquery vector、document vector、filter、metricで比較すれば近似indexの影響を確認できます。

Dense Retrievalが失敗する原因

症状 分離して確認する箇所 代表的な原因 次の確認
exactでもgoldが低順位 encoder/data domain shift、negative不足、label誤り query category別評価、hard negative監査
exactは正しいがANNで落ちる vector index 探索幅、cluster、quantization ANN recall、latency、memory比較
scoreは高いが内容が違う training unit false positive、topicだけ近い fine-grained relevance label
型番や数値を落とす retrieval方式 tokenizer、dense表現、exact match弱さ BM25 baseline、次回のhybrid設計
更新文書が出ない indexing 再encode漏れ、index version混在 source/vector/index lineage
本番だけ順位が違う contract normalization・metric・filter不一致 versionと前処理をtrace
別tenant文書が候補に出る filter/data ACL metadata欠落、filter漏れ 検索前filterと権限test

Dense Retrievalの検索漏れをencoder、ANN、filter、versionへ分ける診断図

domain shiftを「modelが悪い」で済ませない

DPRの結果はopen-domain QA向けです。社内規程、医療略語、商品catalog、source codeではqueryとrelevanceの関係が異なります。BEIRが示したように、あるdatasetで学習したretrieverは別domainで同じ順位性能を保つとは限りません。

少量でも実queryに近い評価setを作り、pretrained model、domain fine-tuning、BM25を同じ条件で比較します。fine-tuningする場合は、どのquery categoryが改善し、どこが悪化したかを残します。

導入時の最小チェックリスト

  1. 検索単位とrelevance labelを決める。
  2. 実queryからtest setを作り、query categoryを付ける。
  3. BM25とFlat exact denseをbaselineにする。
  4. encoder、pooling、normalization、metricをversion固定する。
  5. positive、negative、hard negativeのlabelを監査する。
  6. corpusをencodeし、source IDとvector IDを対応付ける。
  7. corpus規模とSLOに応じてANNを追加する。
  8. Flatとの差としてANN recallを測る。
  9. Recall@k、順位metric、latency、memory、build時間を記録する。
  10. encoder更新時は新indexを作り、評価後にversionを切り替える。

この順序なら、「Embedding model」「vector database」「ANN parameter」を一度に変えて原因が分からなくなる事態を避けられます。

よくある質問

Dense Retrievalとベクトル検索は同じですか?

文章検索の文脈では近い意味で使われます。Dense Retrievalは、queryと文書をdense vectorで表し、近さに基づいて関連文書を取得する情報検索の方式です。ベクトル検索はtext以外の画像・音声・商品特徴量にも使える、より広い実装概念です。

cosine similarityと内積はどちらを使うべきですか?

modelが想定する学習時のscoreと揃えます。両側をL2 normalizeするmodelなら、cosine similarityとinner productは同じ順位になります。未正規化vectorではnormの影響が異なるため、無条件に交換しません。

vector databaseは必須ですか?

必須ではありません。小規模ならFlat exact searchや行列積でも検証できます。更新、filter、永続化、分散、latency要件が増えるとvector indexやdatabaseが有用になります。

kはいくつにすべきですか?

万能な値はありません。retrieverの候補kと、後段へ渡す文書数を分け、Recall@k、latency、後段costを同じtest setで比較します。正解がtop-kへ入らない問題をrerankerで直すことはできません。

encoderを変更したら再indexが必要ですか?

passage encoderやpooling、normalization、vector次元を変えた場合は、原則としてcorpusを再encodeし、新しいindexを作ります。question encoderだけの変更でも、古いpassage vectorとの互換性を評価してから切り替えます。

まとめ

Embedding検索は、文章をvectorへ変換する処理と、そのvectorが検索順位に役立つよう学習する処理の組み合わせです。DPRはquestionとpassageを別々のencoderで表し、positiveの内積をnegativeより高くすることで、大規模corpusの候補検索を可能にしました。

実装では、encoderの精度とANNの近似精度を分けます。まずBM25とFlat exact denseを比較し、その後にANNを追加します。normalizationとmetric、encoder/index version、filterを固定し、query category別のRecall@kとlatencyを測れば、検索漏れを直せるstageへ落とし込めます。

次回は、retrievalへ渡す文書の切り方と、dense・sparse候補をどう組み合わせてrerankするかを、Chunking / Hybrid Search / Rerankerとして扱います。

関連記事

参考資料

コメント

タイトルとURLをコピーしました