本文へスキップ
BecomeCoder

RAGコース · 第5章 精度を上げる · レッスン21

クエリ変換 ― HyDE・Multi-Query・クエリ拡張

ローカル実施

導入

検索がうまくいかないとき、原因は文書側だけでなく質問の書き方にあることも多いです。ユーザーが打ち込んだそのままの質問が、検索にとって都合の良い形とは限りません。そこでLLM自身に質問側を整えさせてから検索する、という発想のテクニックがあります。

説明

  • クエリ拡張(Query Expansion):元の質問に同義語や関連語を足してから検索します。「有給休暇」で検索するなら「年次有給休暇 有給 休暇制度」のように語彙を増やし、語彙ミスマッチによる取りこぼしを減らします。
  • Multi-Query:1つの質問をLLMに複数の言い換えとして生成させ、それぞれで検索した結果をまとめて統合します。1回の検索では拾いきれない文書も、視点を変えた複数回の検索なら拾える可能性が上がります。
  • HyDE(Hypothetical Document Embeddings):質問に対して「もしこの質問に完璧に答えるとしたら、どんな文章になるか」という仮の答えをLLMに書かせ、その仮の答えのベクトルで検索します。質問文と回答文は言い回しが違うことが多いですが、「答えは答えに似ている」という発想で、仮の答え同士の近さを使うことで検索精度を上げます。
  • query rewriting(クエリの書き換え):第4章レッスン17で扱った「会話の文脈を踏まえて質問を独立した形に畳む」処理も、広い意味ではクエリ変換の一種です。「それについてもっと詳しく」のような文脈依存の質問を、単独で検索できる形に直してから検索に渡します。
flowchart TD
  q["元の質問"] --> exp["クエリ拡張<br/>同義語・関連語を追加"]
  q --> multi["Multi-Query<br/>複数の言い換えを生成"]
  q --> hyde["HyDE<br/>仮の答えをLLMに書かせる"]
  q --> rewrite["query rewriting<br/>会話の文脈を1つのクエリに畳む"]
  exp --> search["検索"]
  multi --> search
  hyde --> search
  rewrite --> search
  search --> merge["結果をまとめて統合し生成へ"]

Multi-Queryの考え方を、擬似コードで示します(実行はしません)。

# Multi-Query の擬似コード(掲示のみ)
def multi_query_search(question, llm, retriever, n=3):
    # ① LLMに question の言い換えを n 個作らせる
    variants = llm.generate_variants(question, n=n)
    variants.append(question)  # 元の質問も忘れずに含める

    # ② それぞれの言い換えで検索し、結果をまとめる
    all_hits = []
    for q in variants:
        all_hits.extend(retriever.search(q, k=3))

    # ③ 重複を除いて返す(同じ文書が複数の言い換えでヒットすることも多い)
    return list(dict.fromkeys(all_hits))

いずれの手法も、共通しているのは「検索する前に、LLMをもう1回使って質問側を整える」という点です。LLM呼び出しの回数は増えるぶんコストと時間がかかりますが、検索の取りこぼしを減らす効果は大きく、実務のRAGでもよく組み込まれています。

まとめ

  • クエリ拡張は同義語・関連語を足して語彙ミスマッチを減らす。
  • Multi-Queryは質問を複数の言い換えに増やし、それぞれの検索結果を統合する。
  • HyDEは「仮の答え」を作ってそのベクトルで検索する、「答えは答えに似ている」という発想。
  • query rewritingは会話の文脈を独立したクエリに畳む処理(第4章レッスン17の続き)。