開発
ML
RAG
AI
文脈付き検索 (Contextual Retrieval) によるRAG性能強化の手法
約1か月前
0 コメント
文脈付き検索 (Contextual Retrieval) によるRAG性能強化の手法
RAGの概要とその課題
Retrieval-Augmented Generation (RAG)は、大規模言語モデル (LLM) に外部の知識を与える一般的な手法です。あらかじめ用意した知識ベースから関連情報を検索し、それをユーザープロンプトに追加することで、モデルの応答精度を大幅に高めます。典型的なRAGシステムでは、以下の手順で知識ベースを構築・活用します:
- ドキュメント分割: 知識ベース内の文書群を数百トークン程度の小さなチャンク(断片)に分割する。
- 埋め込みベクトル化: 各チャンクをベクトル表現に変換する(埋め込みモデルで意味を符号化)。
- ベクターデータベース格納: それら埋め込みをベクターデータベースに格納し、類似度検索ができるようにする。
クエリが来ると、ベクターDBからクエリに意味的に類似したチャンクを検索し、上位のチャンク群をモデルへの追加コンテキストとして提示します。この方法により、LLMの固定長コンテキスト(数千~数万トークン)の制約を超えて、大量の知識を参照できるようになります。実際、知識ベースが約200,000トークン(500ページ相当)以下であれば、すべてを直接プロンプトに含めることも可能ですが、それ以上に規模が大きい場合はRAGのような検索ベースの手法が不可欠です。
しかし、RAGにはいくつかの課題があります。
-
コンテキスト長の制限と断片化: 前述の通りLLMには1回のプロンプトに与えられるテキスト長に上限があるため、大きな文書は細切れ(チャンク化)にする必要があります。このチャンク化によって文脈が失われる問題があります。例えば、「Its more than 3.85 million inhabitants make it the European Union's most populous city」というチャンクだけを見ても、それが「どの都市」の話か不明です。このように内容単体では意味が通じないチャンクが生じ、検索クエリとのマッチングが難しくなります。その結果、ユーザーの質問に対して不完全または的外れ(ノイズの多い)な検索結果につながる恐れがあります 。
-
検索精度の限界: RAGで主に用いられるベクトル(意味)検索は、高度な意味的類似性に基づいて関連箇所を見つけられる一方、クエリに含まれる固有名詞や数字などの厳密な一致が要求される情報を見落とす場合があります。例えばクエリにユニークなIDや専門用語が含まれていても、ベクトル類似度だけでは適切なチャンクを上位に出せないことがあります。また意味的には近いが文脈的には無関係な断片が上位に混ざり込むケースもあり、重要な情報がノイズに埋もれてしまう可能性もあります。このような理由から、従来型のRAGでは情報検索の取りこぼしやノイズによる精度低下が発生しやすいのです。
以上の課題により、せっかく知識ベースを参照しても肝心の関連情報を取り逃す(検索失敗)ことがRAGのボトルネックとなっていました。
Contextual Retrievalの概要と利点
これらRAGの課題を解決するために、2024年にAnthropic社が提案した新手法がContextual Retrieval(文脈付き検索)です 。Contextual Retrievalでは、各チャンクに対し元の文書の文脈となる説明を付加してから検索インデックスを構築します。具体的には、「Contextual Embeddings」と「Contextual BM25」という2つのアプローチを組み合わせています:
-
Contextual Embeddings(文脈埋め込み): チャンクを埋め込む前に、そのチャンクが「どの文書の何に関する内容か」を短く要約した説明文を前置します。これにより、埋め込みベクトルにチャンク単体では失われていた文脈情報(例: 固有名詞や日付、章題など)が織り込まれ、意味検索でも文脈を考慮したマッチングが可能になります。
-
Contextual BM25(文脈BM25): 上記の文脈付きテキストを用いてBM25アルゴリズムに基づく検索インデックス(単語ベースの逆引きインデックス)を構築します。各チャンクに文脈説明が追加されているため、本来チャンク単体では含まれていなかったキーワード(企業名・地名・日時など)でもインデックスに登録されます。その結果、ユーザークエリに含まれる重要語との直接的な単語マッチも可能となり、ベクトル検索だけでは見逃す情報を補完できます。
Contextual Retrievalの核心は「チャンクを元文書内の位置づけごと自己完結型のテキストにする」ことです。例えば、従来のチャンクが
"The company's revenue grew by 3% over the previous quarter."
(この会社の売上高は前四半期比3%成長した)という内容だったとします。この一文だけでは「どの会社のどの期間の話か」不明瞭ですが、Contextual Retrievalでは埋め込み前に「これはACME社の2023年第2四半期のSEC報告書の一部で、前四半期の売上は3億1400万ドルでした。」といった説明を付加し、以下のような文脈付きチャンクに変換します:PYTHON# 例: 文脈追加前のチャンク original_chunk = "The company's revenue grew by 3% over the previous quarter." # 文脈追加後のチャンク contextualized_chunk = "This chunk is from an SEC filing on ACME Corp's performance in Q2 2023; the previous quarter's revenue was $314 million. The company's revenue grew by 3% over the previous quarter."
(上記はAnthropicの記事からの例 (Introducing Contextual Retrieval \ Anthropic))
このようにチャンク単体でも必要な背景がわかる自己完結したテキストになるため、検索クエリが例えば「ACME社の2023年Q2の売上成長率は?」という内容であれば、ベクトル検索でも**"ACME"や"Q2 2023"**といった情報を含む適切な埋め込みがヒットしやすくなります。またBM25によるキーワード検索でも、チャンク内に"ACME"や"Q2 2023"が含まれているおかげで該当チャンクを上位に見つけられます。結果として、関連する情報を見落とす確率が大幅に減少します。
Anthropic社の報告によると、Contextual Retrievalを導入することで検索失敗(関連チャンクを取得できないケース)が劇的に減少しました。具体的には、まずチャンクに文脈を追加して埋め込み検索を行うことで検索失敗率が35%減少し、さらに文脈BM25によるキーワード検索も組み合わせることで49%減少しました。そして最後に結果のリランキング(後述)まで行うことで、最終的には67%もの検索エラー削減を達成しています。検索精度の大幅な向上は、そのまま下流の質問応答精度の向上に直結します。実際、Contextual RetrievalによりRAGシステムの回答がより一貫性のある正確なものとなり、ユーザーの求める情報に的確に応答できるようになります。
具体的な実装方法
それでは、Contextual Retrievalをどのように実装できるかを具体的に見ていきます。基本的な流れは以下の通りです:
- ドキュメントのチャンク化 – 対象となる文書全体を小さなテキスト片(チャンク)に分割します。チャンクのサイズは文脈が損なわれない程度に調整します(例: 段落単位や数百文字程度)。
- 文脈情報の生成 – LLMを用いて各チャンクに対する短い文脈説明を生成します。説明には「そのチャンクが元の文書内で何について書かれているか」を要約させます。
- 文脈付きチャンクの構築 – 得られた文脈説明テキストを元のチャンクの先頭に付加し、一つの self-contained なチャンクテキストにします。
- ベクトル埋め込みの作成 – 文脈付きチャンクごとに埋め込みベクトルを計算し、ベクターデータベースやインデックスに保存します。
- BM25インデックスの構築 – 文脈付きチャンク群をコーパスとして、BM25アルゴリズムによる単語検索用のインデックスを構築します(例えば各チャンクを単語リストに分割してBM25スコア計算ができるようにします)。
- クエリ処理(検索ステップ) – ユーザーからクエリが来たら、まずそのクエリ文を埋め込みベクトルに変換し、ベクトル類似度検索で関連度の高い上位N件のチャンクを取得します。同時に、クエリ文を単語に分割してBM25インデックスに問い合わせ、スコア上位N件のチャンクも取得します。これらを統合し、候補チャンク一覧を作成します。
- リランキング(再順位付け) – 必要に応じて、候補チャンクをクエリとの関連性が高い順に並び替えます。例えば、専用のリランキングモデル(クロスエンコーダなど)にクエリと各チャンクを入力して関連スコアを再評価し、上位K件に絞り込みます。Anthropicの実験では、このリランキングを行うことでさらなる精度向上(最終的な検索エラー率が67%減まで改善)が得られています。
- 回答生成 – 最後に上位K件の関連チャンクをコンテキストとしてLLMに与え、ユーザーの質問に対する回答を生成させます。通常K=5〜20件程度のチャンクをモデルに渡しますが、Anthropicの検証では上位20件程度まで文脈を入れることで応答精度が向上することが報告されています。
以上が高レベルな処理フローです。次に、Pythonを用いた実装例のコードスニペットを示します。以下の例では、OpenAI APIやオープンソースの埋め込みモデル、簡易BM25実装を使って上記のステップを実現しています(擬似コードを含みます)。
PYTHON# 前提: `document_text`に検索対象の文書全文が入っているとする。 # Step 1: 文書をチャンクに分割する(例として簡易に段落ごとに分割) chunks = document_text.split("\n\n") # 2つの改行で区切る例 # Step 2: LLMを使って各チャンクの文脈説明を生成する import openai openai.api_key = "YOUR_API_KEY" contextual_chunks = [] for chunk in chunks: prompt = f"<document>\n{document_text}\n</document>\n" \ f"Here is the chunk we want to situate within the whole document\n<chunk>\n{chunk}\n</chunk>\n" \ f"Please give a short succinct context to situate this chunk within the overall document." response = openai.ChatCompletion.create(model="gpt-4", messages=[{"role": "user", "content": prompt}]) context = response["choices"][0]["message"]["content"].strip() contextual_chunks.append(context + " " + chunk) # Step 3: 文脈付きチャンクを埋め込みベクトルに変換する from sentence_transformers import SentenceTransformer embed_model = SentenceTransformer('all-MiniLM-L6-v2') chunk_embeddings = embed_model.encode(contextual_chunks) # Step 4: 文脈付きチャンク群からBM25インデックスを構築する from rank_bm25 import BM25Okapi # 各チャンクをトークン化(空白区切りの単語リストに変換) tokenized_chunks = [c.split() for c in contextual_chunks] bm25_index = BM25Okapi(tokenized_chunks) # --- ここまででオフラインの事前準備完了。以下はクエリごとの処理 --- # Step 5: クエリに対するベクトル検索とBM25検索を実行する user_query = "ACME Q2 2023 revenue growth?" query_embedding = embed_model.encode(user_query) # ベクトル類似度計算(コサイン類似度の代わりに内積を使用) import numpy as np sim_scores = np.dot(chunk_embeddings, query_embedding) # 上位5件のインデックスを取得 top_vec_idx = np.argsort(sim_scores)[-5:][::-1] # BM25によるスコア計算と上位5件の取得 query_tokens = user_query.split() bm25_scores = bm25_index.get_scores(query_tokens) top_bm25_idx = np.argsort(bm25_scores)[-5:][::-1] # 結果を統合し候補チャンクを抽出 candidate_idx = set(top_vec_idx) | set(top_bm25_idx) candidates = [contextual_chunks[i] for i in candidate_idx] # Step 6: (オプション)候補チャンクをリランキングし、上位K件を選出する # ここでは簡易にベクトル類似度とBM25スコアの合計でスコアリングする combined_scores = {} for i in candidate_idx: combined_scores[i] = sim_scores[i] + bm25_scores[i] top_K_idx = sorted(combined_scores, key=combined_scores.get, reverse=True)[:5] top_chunks = [contextual_chunks[i] for i in top_K_idx] # Step 7: 選ばれたチャンクをコンテキストとしてLLMに渡し、最終回答を生成する context_text = "\n".join(top_chunks) final_prompt = user_query + "\n\n" + context_text answer = openai.ChatCompletion.create(model="gpt-4", messages=[{"role": "user", "content": final_prompt}]) print(answer["choices"][0]["message"]["content"])
上記コードでは、まずチャンクごとにGPT-4(任意のLLMで可)へ全文書と該当チャンクを与えて文脈説明を生成しています(Step 2)。Anthropic社が公開したプロンプト例では、
<document>...<chunk>...
という形式でClaudeに簡潔な文脈を作らせています。この結果得られた文脈テキスト(例では「ACME社のQ2 2023のレポートで…」の部分)を元のチャンクの先頭に付加し、新たな文脈付きチャンクテキストを作ります。この一連の処理は知識ベースを初期準備する段階(一度きりの前処理)で行います。次に、各文脈付きチャンクをベクトル化し(Step 3)、別途BM25用に単語リスト化したものを準備します(Step 4)。こうして意味検索用(ベクトル)と単語検索用(BM25)の両方の索引が完成します。クエリ処理時には、これら両方から上位候補を取得し(上のコードではそれぞれ上位5件ずつ)、集合ユニオンを取って候補チャンク集合を得ています (Introducing Contextual Retrieval \ Anthropic)。必要に応じてリランキングモデルで関連度スコアを再評価し、本当に関連性の高い上位K件に絞り込みます(ここでは簡易にスコア合算で代用しています)。最後に、そのK件のコンテキストをプロンプトに含めてLLMから回答を生成します。
実装上のポイント: 文脈生成にLLMを用いるコストは、対象文書が大きい場合に無視できなくなります。幸い、AnthropicがClaude向けに導入した**プロンプトキャッピング(Prompt Caching)**機能を活用すると、この文脈生成の繰り返し処理を効率化できます。ドキュメント全文を一度キャッシュに載せておけば各チャンク処理時に再送する必要がなくなるため、大量チャンクでも低コストで文脈付きチャンクを生成可能です。従って、大規模データに対してContextual Retrievalを適用する際は、こうしたキャッシュ機構やバッチ処理を活用し現実的なコスト・速度に収める工夫が重要です。
なお、EmbeddingモデルやベクトルDB、BM25実装などは用途に応じて様々な選択肢があります。埋め込みモデルについてはAnthropicの評価によればVoyageやOpenAIのAdaなど高性能なモデルを使うと効果が高く、BM25に関してはオープンソースのライブラリ(例:
rank_bm25
や Elasticsearch等)を利用できます。リランキングはCohere社のモデルなど市販のソリューションも存在し、追加の推論コストと引き換えに最終的な関連度をさらに高めることができます。自社のユースケースに合わせて、こうしたコンポーネントの選定や上位何件のチャンクを用いるか(例: 上位20件程度が有効を調整し、精度と性能のバランスを取ると良いでしょう。最後に、Anthropic社は同社のLLMサービスClaudeを用いたContextual Retrievalの実装例を**クックブック(cookbook)**として公開しています。興味がある開発者はそのGitHubリポジトリも参照すると、より詳細な実装ガイドや追加のベストプラクティスを得られるでしょう。
応用例と実際のユースケース
Contextual Retrievalは、大規模な知識を扱うあらゆるRAGシステムの精度向上に貢献し得る汎用的な手法です。Anthropic社内の実験でも、ソースコード、学術論文、小説など多様なドメインのデータセットでこの手法を検証し、いずれも検索エラー率がおおむね半減する顕著な効果が確認されています。これは、本手法がドメインを問わず広く有効であることを示しています。また第三者の分析でも「Contextual Retrievalは大規模知識ベースに対するAIシステムの文脈ロス問題を解決し、信頼性を高める画期的アプローチ」であると評価されています。以下に、具体的なユースケース例を挙げます。
-
カスタマーサポートAI: 製品マニュアルやFAQデータベースを参照するチャットボットでは、ユーザーの質問に対応する正確なページや項目を見つけ出す必要があります。Contextual Retrievalを使えば、各FAQやマニュアルの断片に「この内容は○○製品の△△機能に関する説明」といった文脈を持たせて索引化できるため、ユーザー質問に含まれる製品名・機能名から関連箇所を漏れなく検索できます。結果として、従来よりも的確な回答(関連FAQ抜粋や手順案内など)を高速に返せるようになります。
-
法務文書検索アシスタント: 法律事務所向けのLLMアシスタントでは、判例や法令データベースから該当箇所を探し出して要約する能力が求められます。膨大な判例集も、文脈付き検索を導入することで「〇〇事件の判決文の結論部分」「△△法第X条の条文テキスト」といったメタ情報つきでチャンク化できます。これにより、ユーザーが「○○事件ではどのような判決理由でしたか?」と質問した際にも、該当事件名や関連条文を含むチャンクを正確に検索でき、見落としを防ぎます。結果、法律相談やリサーチにおいて信頼性の高い回答を短時間で提示できるようになります。
-
社内ナレッジ検索・ドキュメントQA: 企業内のWikiや技術文書、研究レポートなどを横断検索する社内向けQAシステムでもContextual Retrievalは有用です。例えば社内Wikiの各ページをチャンク化する際にページタイトルやセクション見出しを付加しておけば、ユーザーが曖昧なキーワードで検索した場合でも関連する文脈を持つページ断片を見つけられます。これにより社内情報を効率よく引き出し、従業員の情報検索や新人教育を支援します。
-
コードベースQ&A: 大規模なソースコードリポジトリを対象に、LLMでコードの説明やリファクタ提案を行うケースにも応用できます。関数やクラスの説明コメント、モジュール構成といった情報を各コード片チャンクに付与して索引化すれば、例えば「このエラーを投げる関数はどこに定義されていますか?」という質問に対し、関数名やエラー文言を含む正確なコード位置を検索できます。実際、AnthropicのテストでもコードドキュメントへのContextual Retrieval適用により検索精度が大きく向上することが示されています 。これによって開発者支援AIはコード上の該当箇所を正確に特定し、有用な回答(関数の用途や関連箇所の説明など)を生成できます。
以上のように、Contextual Retrievalはさまざまな現実シナリオでRAGの効果を高める強力な手法です。特にドメイン固有の大量データを持つ企業や組織にとって、単純なベクトル検索だけに頼らず文脈情報と従来型検索を組み合わせる本手法は、LLM活用の精度と信頼性を底上げする鍵となるでしょう。今後ますます大規模な知識を扱うAIシステムが増える中で、Contextual RetrievalはRAGの標準的な強化策として広く普及していくと期待されます。
参考文献:
最終更新: 2025年3月31日
コメント
まだコメントがありません