RAGを支える検索技術:Bi-EncodingとCross-Encoding#

Retrieval-Augmented Generation (RAG) は、大規模言語モデル (LLM) が持つ知識を、外部の情報源(例えばPDFやText, あるいは知識DBなど)で補強し、より正確で信頼性の高い回答を生成する技術です。このRAGの中核を担うのが、質問に関連する情報を効率的かつ正確に見つけ出す検索技術です。

ここでは、RAGでよく用いられる2つの重要な技術、Bi-EncodingCross-Encoding について、その仕組みと役割を解説します。

1. Bi-Encoding:高速な候補絞り込み (Retriever)#

Bi-encodingとはいわゆる埋め込み(Vector embedding) モデルそのものです。かなり雑に表現すれば、センテンスをベクトル化して、DBに突っ込むだけです。

ベクトル化してDB(俗に言うベクトルDB)に入れておくことで、ユーザーの質問に関連しそうな候補を高速に絞り込むことができます。

なお、Bi-Encoding(バイエンコーディング)の名前の由来は、「2つのものを別々に符号化(ベクトル化)する」 という意味合いを持ちます。2つのものとは「回答(Answer)」と「質問 (Query)」の2種類です。

仕組み:

Vector embedding (埋め込み)を知らない方のために少し解説します。(埋め込みに関しては、弊社ブログもご参照ください。

  1. 事前準備(Indexing):

    • 知識ソースとなる各文書(例: FAQ、社内規定、ウェブページの一部など)を、それぞれ独立してEmbeddingモデル(テキストをベクトルに変換するモデル)に入力し、文書ベクトルに変換します。
    • これらの文書ベクトルは、高速な検索を可能にするベクトルデータベース(Vector Database)に格納されます。
    文書1: "横浜市では火曜日は燃えるゴミの日" -> Vector1: [0.91, 0.32, -0.28, ...]
    文書2: "横浜市では水曜日は資源ごみの日" -> Vector2: [0.35, 0.88, -0.65, ...]
    文書3: "武蔵野市では火曜日は資源ごみの日" -> Vector3: [0.11, -0.32, 0.89, ...]
    ... (多数の文書ベクトル) ...
    
  2. 検索時(Retrieval):

    • ユーザーからの質問文も、同じEmbeddingモデルを使って質問ベクトルに変換します。
    質問: "横浜市の燃えるゴミの日はいつ?" -> QueryVector: [0.85, 0.25, -0.31, ...]
    
    • ベクトルデータベース内で、質問ベクトルと各文書ベクトルの類似度(よく使われるのはコサイン類似度)を計算します。コサイン類似度は、ベクトルの向きがどれだけ似ているかを示す指標で、-1から1の間の値を取ります(1に近いほど類似度が高い)。
    • 類似度が高い順に、文書をいくつか(Top-k)候補として選び出します。
    類似度計算:
    - CosineSimilarity(QueryVector, Vector1) = 0.98 (非常に高い類似度)
    - CosineSimilarity(QueryVector, Vector2) = 0.45 (やや低い類似度)
    - CosineSimilarity(QueryVector, Vector3) = 0.15 (低い類似度)
    
    => 類似度が高い文書1「横浜市では火曜日は燃えるゴミの日」などが候補として選ばれる。
    

図解 (Bi-Encodingプロセス):

graph LR
    %% Indexing Process
    D1["文書1
横浜市火曜燃えるゴミ"] --> EM{Embeddingモデル} D2["文書2
横浜市水曜資源ゴミ"] --> EM Dn["文書n
..."] --> EM EM --> V1["ベクトル1"] EM --> V2["ベクトル2"] EM --> Vn["ベクトルn"] V1 --> VDB["ベクトルDB"] V2 --> VDB Vn --> VDB %% Retrieval Process Q["質問
横浜市の燃えるゴミの日は?"] --> EM_ret{Embeddingモデル} EM_ret --> QV["質問ベクトル"] QV --> VDB VDB --> Cands["候補文書群"] %% スタイル指定 (まずはVDBの色だけ変えてみる) style VDB fill:#f9f,stroke:#333,stroke-width:2px

ベクトルデータベースと近似最近傍探索 (ANN):

数百万、数千万といった大量のベクトルから類似ベクトルを高速に検索するために、ベクトルデータベース(例: Weaviate, Pinecone, Chroma, PostgreSQL+pgvector拡張など)が利用されます。これらのデータベースは、近似最近傍探索(Approximate Nearest Neighbor, ANN) アルゴリズム(例: HNSW, Faissなど)を用いて、厳密な最近傍ではないかもしれないが非常に近いベクトルを高速に見つけ出します。これにより、検索速度と精度のバランスを取っています。

なお、近似なので、もしかしたらDB内にもっと近いベクトルがあるのかもしれない、それが見つからない可能性もあるということ(厳密に近いベクトルを持ってくることは出来ない)ということに留意してください。

Bi-Encodingの利点と限界:

  • 利点:
    • 事前に文書をベクトル化しておけるため、検索時の計算量が少なく高速
    • 大量の文書に対応可能。
  • 限界:
    • 質問と文書を別々にベクトル化するため、両者の相互作用や文脈の細かいニュアンスを捉えきれない場合がある。
    • 類似度計算だけでは、意味的に本当に合っているかどうかの判断には限界がある(例: 否定形や微妙な意味の違いを区別しにくい)。
    • ANNによる検索は「近似」であるため、最も関連性の高い文書を見逃す可能性がゼロではない。

2. Cross-Encoding:高精度な再ランキング (Re-ranker)#

Bi-Encodingで絞り込んだ候補文書の中には、まだ質問との関連性が低いものが含まれている可能性があります。そこで、候補文書をより精度高く評価し、関連性の高い順に並び替える(リランキング) ために使われるのが Cross-Encoding です。re-rankingといわれるのは、cross-encodingのことです。

仕組み:

Cross-Encoding(クロスエンコーディング)は、「2つのものを一緒に入力して符号化(評価)する」 という点がBi-Encodingとの大きな違いです。

  1. 入力:

    • ユーザーの質問文と、Bi-Encodingによって選ばれた各候補文書ペアにします。
  2. 評価:

    • この「質問文」と「候補文書」のペアを、Cross-Encoderモデル(通常、Transformerベースのモデルが使われる)に一つの入力として与えます。モデル内部では、質問と文書の両方の情報を同時に考慮(Attention機構などにより相互作用を計算)して、そのペアの関連性スコア(通常、0から1の間の数値)を出力します。
    入力ペア1: "[CLS] 横浜市の燃えるゴミの日はいつ? [SEP] 横浜市では火曜日は燃えるゴミの日 [SEP]"
    => CrossEncoderモデル => 関連性スコア1: 0.95
    
    入力ペア2: "[CLS] 横浜市の燃えるゴミの日はいつ? [SEP] 武蔵野市では火曜日は資源ごみの日 [SEP]"
    => CrossEncoderモデル => 関連性スコア2: 0.15
    
    入力ペア3: "[CLS] 横浜市の燃えるゴミの日はいつ? [SEP] 横浜市のごみ分別ルールについて [SEP]" (Bi-Encodingで選ばれた別の候補)
    => CrossEncoderモデル => 関連性スコア3: 0.60
    
    • [CLS][SEP] は、モデルが入力の構造を理解するための特殊なトークンです。
  3. 並び替え(Re-ranking):

    • 得られた関連性スコアに基づいて、候補文書をスコアの高い順に並び替えます。これにより、より質問に適合した文書が上位に来るようになります。
    リランキング結果:
    1. 文書1 (スコア: 0.95) 「横浜市では火曜日は燃えるゴミの日」
    2. 文書3 (スコア: 0.60) 「横浜市のごみ分別ルールについて」
    3. 文書2 (スコア: 0.15) 「武蔵野市では火曜日は資源ごみの日」
    

図解 (Cross-Encodingプロセス):

graph LR
    Q["質問
横浜市の燃えるゴミの日は?"] C1["候補1
横浜市火曜燃えるゴミ"] C2["候補2
武蔵野市火曜資源ゴミ"] Cn["候補n
..."] subgraph Re-ranking by Cross-Encoder direction LR Q --> Pair1("ペア1 (Q+C1)") C1 --> Pair1 Pair1 -- "CrossEncoder評価" --> S1["スコア1 (0.95)"] Q --> Pair2("ペア2 (Q+C2)") C2 --> Pair2 Pair2 -- "CrossEncoder評価" --> S2["スコア2 (0.15)"] Q --> PairN("ペアn (Q+Cn)") Cn --> PairN PairN -- "CrossEncoder評価" --> Sn["スコアn (...)"] end S1 --> Rank[並び替え処理] S2 --> Rank Sn --> Rank Rank -- "高スコア順" --> RankedCands["最終候補リスト"] %% スタイル指定 (例: CrossEncoderの評価プロセスを示すノードの色を変える) %% Pair1, Pair2, PairN など評価プロセスのノードIDに合わせて調整が必要な場合があります %% ここではスコアのノード (S1, S2, Sn) の色を変えてみます style S1 fill:#ccf,stroke:#333,stroke-width:2px style S2 fill:#ccf,stroke:#333,stroke-width:2px style Sn fill:#ccf,stroke:#333,stroke-width:2px

Cross-Encodingの利点と限界:

  • 利点:
    • 質問と文書を同時に処理するため、両者の文脈的な相互作用を深く考慮でき、非常に高い精度で関連性を評価できる。
    • 微妙な意味の違いや否定関係なども捉えやすい。
  • 限界:
    • 候補文書ごとに質問とのペアでモデル計算を行うため、計算コストが非常に高い
    • Bi-Encodingのように事前に計算しておくことができず、検索のたびに計算が必要。
    • そのため、大量の文書すべてに適用するのは現実的ではなく、Bi-Encodingで絞り込んだ少数の候補に対して適用するのが一般的。

3. RAGにおける連携:速度と精度の両立#

RAGシステムでは、Bi-EncodingとCross-Encodingはそれぞれの長所を活かす形で連携して使われることが一般的です。

  1. Retriever (Bi-Encoding): まず、高速なBi-Encodingを用いて、膨大な文書データベースから関連性の高い候補文書を数十〜数百程度、素早く絞り込みます。
  2. Re-ranker (Cross-Encoding): 次に、計算コストは高いが高精度なCross-Encodingを用いて、絞り込まれた候補文書をより厳密に評価し、関連性の高い順に並び替えます。
  3. Generator (LLM): 最後に、Re-rankerによって選ばれた最も関連性の高い上位数件の文書をコンテキスト情報として、質問と共にLLMに与え、最終的な回答を生成させます。

この**「高速な絞り込み(Bi-Encoding) + 高精度な再ランキング(Cross-Encoding)」** という二段階のプロセスにより、RAGシステムは速度と精度のバランスを取りながら、的確な情報検索と高品質な回答生成を実現しています。

まとめ#

  • Bi-Encoding:
    • 役割: 高速な候補検索 (Retriever)
    • 方法: 質問と文書を別々にベクトル化し、類似度を計算。
    • 特徴: 高速だが、精度はCross-Encodingに劣る。ベクトルDBが使われる。
  • Cross-Encoding:
    • 役割: 高精度な候補の再ランキング (Re-ranker)
    • 方法: 質問と文書のペアをモデルに入力し、関連性スコアを計算。
    • 特徴: 高精度だが、計算コストが高い。Bi-Encodingで絞った候補に適用される。

Cross-encoding、bi-encodingともに様々なモデルがすでに用意されています。 注意ですが、日本語で扱う時は多言語に対応したモデルを使ってそれぞれのEncodingをする必要が有ります。

これらの技術を組み合わせることで、RAGは外部知識を効果的に活用し、より信頼性の高い情報を提供することが可能になります。