論文概要(Abstract)
RAGシステムではユーザーのクエリをそのまま検索器に渡すが、ユーザーの自然言語クエリと検索器が必要とするクエリ形式の間にはギャップがある。本論文は従来のRetrieve-then-ReadパイプラインをRewrite-Retrieve-Readに拡張し、検索前にクエリを書き換えるステップを導入する。さらに、小規模言語モデル(T5)をRewriterとして訓練し、Reader(LLM)からのフィードバックを強化学習で活用する手法を提案する。WebQuestions、TriviaQA、HotpotQAなどのベンチマークで、一貫した精度向上を実証した。
この記事は Zenn記事: LangGraph Agentic RAGで社内検索の回答精度を大幅改善する実装手法 の深掘りです。
情報源
- 会議名: EMNLP 2023(Empirical Methods in Natural Language Processing)
- 年: 2023
- URL: https://aclanthology.org/2023.emnlp-main.322/
- 著者: Xinbei Ma, Yeyun Gong, Pengcheng He, Hai Zhao, Nan Duan
- 所属: 上海交通大学 / Microsoft Research Asia
カンファレンス情報
EMNLPについて: EMNLP(Empirical Methods in Natural Language Processing)は自然言語処理分野の最高峰会議の1つであり、ACLと並んでNLPコミュニティで最も権威のある会議である。採択率は通常20-25%程度で、本論文はメインカンファレンスに採択されている。
技術的詳細(Technical Details)
従来のRetrieve-then-Read vs 提案手法Rewrite-Retrieve-Read
従来のRAGパイプラインは以下の流れである:
\[q \xrightarrow{\text{Retrieve}} D = \{d_1, \ldots, d_k\} \xrightarrow{\text{Read}} a\]しかしこのアプローチには根本的な問題がある。ユーザーの自然言語クエリ$q$は、検索器が最適に動作するクエリ形式と一致しない場合が多い。例えば:
- 曖昧なクエリ: 「社長は誰?」→ どの会社の社長か不明
- 会話的クエリ: 「それはいつ?」→ 前文脈が検索器に渡らない
- 複合クエリ: 「Pythonの作者の母親はいつ生まれた?」→ 2段階推論が必要
提案手法はRewriterステップを追加する:
\[q \xrightarrow{\text{Rewrite}} q' \xrightarrow{\text{Retrieve}} D' = \{d'_1, \ldots, d'_k\} \xrightarrow{\text{Read}} a\]ここで重要なのは、Readerは元のクエリ$q$を受け取る点である。書き換えられたクエリ$q’$は検索にのみ使用し、生成時にはユーザーの意図を正確に反映した$q$を使用する。
アルゴリズム
graph LR
A[ユーザークエリ q] --> B[Rewriter]
B -->|書き換えクエリ q'| C[Retriever]
C -->|文書 D'| D[Reader LLM]
A -->|元クエリ q| D
D --> E[回答 a]
凍結LLMによるRewriting(プロンプトベース)
まず、GPT-3.5-TurboなどのLLMをゼロショットプロンプトでRewriterとして使用する:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def frozen_rewriter(question: str) -> str:
"""凍結LLMによるクエリ書き換え
Args:
question: ユーザーの元のクエリ
Returns:
書き換えられたクエリ
"""
prompt = (
"Provide a better search query for web search engine "
"to answer the given question, end the queries with '**'.\n"
f"Question: {question}\n"
"Answer:"
)
response = llm.invoke(prompt)
rewritten = response.content.split("**")[0].strip()
return rewritten
訓練可能なRewriter(強化学習ベース)
凍結LLMへの依存を排除するため、T5-large(770Mパラメータ)を強化学習で訓練する。
報酬関数:
\[r = F_1(a_{q'}, a^*) - F_1(a_q, a^*)\]ここで、
- $a_{q’}$: 書き換えクエリ$q’$で検索した場合のReaderの回答
- $a_q$: 元クエリ$q$で検索した場合のReaderの回答
- $a^*$: 正解回答
- $F_1$: トークンレベルのF1スコア
書き換えによって回答品質が向上すれば正の報酬、悪化すれば負の報酬が与えられる。
REINFORCE with Baseline:
方策勾配法のREINFORCEアルゴリズムを使用する:
\[\nabla_\phi J(\phi) = \mathbb{E}_{q' \sim \pi_\phi(\cdot|q)} \left[ (r - b) \nabla_\phi \log \pi_\phi(q'|q) \right]\]ここで、
- $\phi$: Rewriterのパラメータ
$\pi_\phi(q’ q)$: Rewriterの方策($q$が与えられた時に$q’$を生成する確率) - $b$: 分散削減のためのベースライン(直近100ステップの報酬移動平均)
報酬ハッキング防止:
Rewriterが空文字列やクエリのコピーなど自明な書き換えに収束するのを防ぐため、以下の正則化を導入:
- KLダイバージェンス正則化: SFT初期化からの乖離にペナルティ
- 長さペナルティ: 極端に短い/長い書き換えにペナルティ
訓練の詳細
| パラメータ | 値 |
|---|---|
| アルゴリズム | REINFORCE with baseline |
| オプティマイザ | AdaFactor |
| 学習率 | $1 \times 10^{-4}$ |
| バッチサイズ | 32 |
| RLステップ数 | ~2000 |
| ビームサイズ | 5(訓練時サンプリング) |
| 最大系列長 | 64トークン(書き換えクエリ) |
実装のポイント
LangGraphとの対応
Zenn記事のrewrite_questionノードは、本論文のRewriterに直接対応する。論文の知見を活かして実装を改善できる:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def rewrite_question(state: GraphState) -> dict:
"""論文のRewrite手法に基づくクエリ書き換え
改善ポイント:
- 元クエリは生成時に保持(Readerは元クエリを使用)
- 書き換えは検索最適化に特化
- BM25向けにキーワードを明示化
"""
rewritten = llm.invoke(
f"以下の質問をベクトル検索でヒットしやすい形に書き換えてください。\n"
f"具体的用語を使い、曖昧表現を排除してください。\n"
f"元の質問: {state['question']}"
)
return {
"question": rewritten.content,
"original_question": state.get("original_question", state["question"]),
"retry_count": state["retry_count"] + 1
}
注意すべき実装詳細:
- 元クエリの保持:
original_questionを別フィールドで保持し、生成時には元クエリを使用する - 検索器タイプへの適応: BM25にはキーワード抽出型、Dense Retrieverには文脈豊富な書き換えが効果的
- レイテンシ管理: Rewriterの呼び出しは200-500msのオーバーヘッド。T5-largeをローカル推論すれば50ms以下に削減可能
実験結果(Results)
主要ベンチマーク結果
| 手法 | WebQ EM | WebQ F1 | TriviaQA EM | HotpotQA Joint F1 |
|---|---|---|---|---|
| No Retrieval (ChatGPT) | 41.8 | 54.2 | - | - |
| Retrieve-Read (BM25) | 44.3 | 56.7 | 63.4 | 28.3 |
| Retrieve-Read (Bing) | 46.1 | 58.3 | - | - |
| RRR (ChatGPT rewriter, Bing) | 51.3 | 63.2 | 67.2 | 34.7 |
| RRR (T5-large rewriter, RL) | 50.8 | 62.9 | 66.5 | 36.1 |
Rewrite-Retrieve-Read(RRR)はRetrieve-Readに対して、WebQuestionsで+6.5 F1ポイント、TriviaQAで+3.1 EMポイントの改善を達成している。
検索品質への影響
書き換えによる検索品質(nDCG@10)の改善:
| 条件 | nDCG@10 |
|---|---|
| 元クエリ | 0.412 |
| 凍結LLM書き換え | 0.487 |
| 訓練可能Rewriter (RL) | 0.503 |
検索品質が+22%向上しており、書き換えが検索段階で効果を発揮していることがわかる。
Rewriterモデルサイズのスケーリング
| Rewriter | WebQ F1 |
|---|---|
| T5-small (60M) | 59.8 |
| T5-large (770M) | 62.9 |
| T5-3B | 63.5 |
| ChatGPT (~175B) | 63.2 |
T5-large(770M)でChatGPT(175B)とほぼ同等の性能を達成しており、コスト効率に優れる。
検索器タイプ別の効果
| 検索器 | 書き換えなし | 書き換えあり | 改善幅 |
|---|---|---|---|
| BM25 | 56.7 | 61.4 | +4.7 |
| Bing | 58.3 | 63.2 | +4.9 |
| DPR | 55.1 | 59.8 | +4.7 |
全検索器タイプで一貫した改善が見られ、書き換えは検索器に依存しない汎用的手法であることが確認された。
Production Deployment Guide
AWS実装パターン(コスト最適化重視)
クエリ書き換え機能をRAGパイプラインに組み込む際のAWS構成を示す。
| 規模 | 月間リクエスト | 推奨構成 | 月額コスト | 主要サービス |
|---|---|---|---|---|
| Small | ~3,000 (100/日) | Serverless | $40-120 | Lambda + Bedrock + S3 |
| Medium | ~30,000 (1,000/日) | Hybrid | $200-600 | Lambda + SageMaker Endpoint + ElastiCache |
| Large | 300,000+ (10,000/日) | Container | $1,500-4,000 | EKS + SageMaker + OpenSearch |
Small構成の詳細(月額$40-120):
- Lambda: Rewriter呼び出し + パイプライン制御($15/月)
- Bedrock: Claude 3.5 Haiku(Rewriter + Reader兼用)、Prompt Caching有効($70/月)
- S3: 検索インデックス格納($5/月)
Medium構成の詳細(月額$200-600):
- Lambda: パイプライン制御($20/月)
- SageMaker Endpoint: T5-large Rewriter専用推論エンドポイント($100/月、ml.g5.xlargeスポット)
- Bedrock: Claude 3.5 Sonnet(Reader)($350/月)
- ElastiCache: 書き換えクエリのキャッシュ($30/月)
コスト試算の注意事項: 上記は2026年2月時点のAWS ap-northeast-1リージョン料金に基づく概算値です。最新料金はAWS料金計算ツールで確認してください。
Terraformインフラコード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
resource "aws_lambda_function" "query_rewriter" {
filename = "rewriter.zip"
function_name = "rag-query-rewriter"
role = aws_iam_role.lambda_rewriter.arn
handler = "index.handler"
runtime = "python3.12"
timeout = 30
memory_size = 512
environment {
variables = {
BEDROCK_MODEL_ID = "anthropic.claude-3-5-haiku-20241022-v1:0"
ELASTICACHE_ENDPOINT = aws_elasticache_cluster.rewrite_cache.cache_nodes[0].address
CACHE_TTL_SECONDS = "3600"
}
}
}
resource "aws_elasticache_cluster" "rewrite_cache" {
cluster_id = "rewrite-query-cache"
engine = "redis"
node_type = "cache.t3.micro"
num_cache_nodes = 1
parameter_group_name = "default.redis7"
}
コスト最適化チェックリスト
- 頻出クエリの書き換え結果をElastiCacheにキャッシュ
- T5-large RewriterはSageMaker Spot Instancesで最大90%削減
- Bedrock Prompt Caching有効化でRewriterプロンプト部分を30-90%削減
- 同一クエリの重複書き換えを検出し、キャッシュヒット率を向上
- CloudWatch Custom MetricsでRewriter呼び出し回数を監視
- AWS Budgets月額予算設定
実運用への応用(Practical Applications)
Zenn記事のAgentic RAGにおけるrewrite_questionノードは、本論文のRewrite-Retrieve-Readフレームワークの実用的実装に相当する。論文の知見を活かして以下の改善が可能:
- 段階的導入: まず凍結LLM(Bedrock Claude)でのプロンプトベースRewriterから始め、効果を確認してからT5-largeのRL訓練に移行
- 元クエリの保持: 書き換えクエリは検索にのみ使用し、生成時は元クエリを使う設計がF1向上に重要
- 検索器タイプへの適応: BM25にはキーワード抽出型書き換え、Dense RetrieverにはHyDE的な書き換えが効果的
まとめ
Rewrite-Retrieve-Readは、RAGパイプラインにクエリ書き換えステップを追加するシンプルだが効果的な手法である。T5-large(770M)のRL訓練によるRewriterがChatGPT(175B)と同等の性能を達成する点は、コスト効率の観点で実務上重要である。Zenn記事のLangGraph Agentic RAGのrewrite_questionノードの設計根拠として、本論文の知見は直接活用できる。
参考文献
- Conference URL: https://aclanthology.org/2023.emnlp-main.322/
- arXiv: https://arxiv.org/abs/2305.14283
- Related Zenn article: https://zenn.dev/0h_n0/articles/4c869d366e5200