メインコンテンツへスキップ

8.1.5 検索戦略

Hybrid Search と Rerank のフローチャート

学習目標

この節を終えると、あなたは次のことができるようになります。

  • 検索戦略が RAG の品質を直接左右する理由を理解する
  • キーワード検索、ベクトル検索、ハイブリッド検索の違いを見分ける
  • rerank、query rewrite などのよく使う強化手法を理解する
  • 実行可能な例でハイブリッド検索の考え方を体験する

一、検索は「top-k だけ」ではない

キーワード検索:はっきりした語を探すのに向いている

キーワード検索は、もっと「目次を引く」感じです。

得意なのは次のようなものです。

  • 正確な専門用語
  • 製品名
  • エラーコード
  • 法令番号

たとえば、ユーザーがこう聞いたとします。

「エラーコード 403 って何ですか?」

このような場面では、キーワード検索はとても強いです。

ベクトル検索:意味が近い内容を探すのに向いている

ベクトル検索は、もっと「意味で似ているものを探す」感じです。

得意なのは次のようなものです。

  • 同義表現
  • 言い換えた質問
  • 口語的な質問

たとえば、

「講座をやめたいです」

と、

「コース購入後 7 日以内なら返金申請できます」

は、使われている言葉は違いますが、ベクトル検索なら結びつけられる可能性があります。


二、なぜ多くのプロジェクトは最後にハイブリッド検索へ向かうのか?

キーワードと意味には、それぞれ弱点があるから

キーワードだけを使うと、

  • 意味は近いのに、表現が違う内容を取りこぼしやすい

ベクトルだけを使うと、

  • とても重要な固有語を見落とすことがある

そのため、多くのシステムでは次のようにします。

キーワードスコア + ベクトルスコア = ハイブリッドスコア

これは「表面の文字」と「意味」の両方を見るのに近い

人間が資料を探すときも、だいたい同じです。

  • まず明確なキーワードがあるかを見る
  • 次に、同じ内容を言っているかを判断する

ハイブリッド検索は、この 2 つの判断を合わせたものです。

キーワード検索とベクトル検索の盲点図

検索戦略の比較図

BM25 は、古典的なキーワード順位付けの方法です。多くのハイブリッド検索では、BM25 風のスコアとベクトル類似度を組み合わせ、最後に rerank で並び順を整えます。

図の読み方

左は「文字が一致するか」、右は「意味が近いか」を見ています。Hybrid Search の価値は複雑さではなく、エラーコード、専門用語、口語的な質問にそれぞれ通り道を作り、すべての負担を embedding に押しつけないことです。


三、最小構成のハイブリッド検索の例

次の例では、

  • keyword_score がキーワード一致をまねる
  • vector_score が意味の近さをまねる
  • 最後に 2 つを重み付きで組み合わせる
import math
import re
from collections import Counter
import numpy as np

docs = [
{
"id": "d1",
"text": "講座購入後 7 日以内なら返金申請できます",
"vector": np.array([0.95, 0.10, 0.05])
},
{
"id": "d2",
"text": "すべてのプロジェクトを完了し、テストに合格すると証明書を取得できます",
"vector": np.array([0.10, 0.95, 0.10])
},
{
"id": "d3",
"text": "まず Python を学び、その後に機械学習と深層学習を学ぶのがおすすめです",
"vector": np.array([0.20, 0.30, 0.95])
}
]

query = "講座をやめて返金したい"
query_vector = np.array([0.90, 0.10, 0.10])

def tokenize(text):
words = re.findall(r"[a-zA-Z0-9_]+", text.lower())
cjk_chars = re.findall(r"[\u4e00-\u9fff\u3040-\u30ff]", text)
cjk_bigrams = ["".join(cjk_chars[i:i + 2]) for i in range(len(cjk_chars) - 1)]
return words + cjk_bigrams

def keyword_score(query, text):
q = Counter(tokenize(query))
t = Counter(tokenize(text))
return sum(min(q[k], t[k]) for k in q)

def cosine_similarity(a, b):
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

results = []
for doc in docs:
kw = keyword_score(query, doc["text"])
vec = cosine_similarity(query_vector, doc["vector"])
hybrid = 0.4 * kw + 0.6 * vec
results.append((hybrid, kw, vec, doc["id"], doc["text"]))

for hybrid, kw, vec, doc_id, text in sorted(results, reverse=True):
print(doc_id, "hybrid=", round(hybrid, 4), "kw=", kw, "vec=", round(vec, 4), "->", text)

期待される出力:

d1 hybrid= 1.399 kw= 2 vec= 0.9983 -> 講座購入後 7 日以内なら返金申請できます
d3 hybrid= 0.1977 kw= 0 vec= 0.3295 -> まず Python を学び、その後に機械学習と深層学習を学ぶのがおすすめです
d2 hybrid= 0.1337 kw= 0 vec= 0.2228 -> すべてのプロジェクトを完了し、テストに合格すると証明書を取得できます

この例は簡略化されていますが、実際のシステムの核心にかなり近い考え方です。


四、Rerank:まず広く集めて、あとで細かく並べ替える

なぜ rerank が必要なのか?

多くのシステムでは、最初から「一発で正確に並べる」ことを目指しません。代わりに、

  1. まず、比較的安い方法で候補を集める
  2. 次に、より強力だが高コストな方法で並べ替える

これを rerank と呼びます。

直感的なたとえ

就職活動でいうと、

  • 1 次選考ではキーワードで履歴書をふるいにかける
  • 2 次選考では、本当に合っているかを丁寧に見る

RAG でも同じです。


五、Query Rewrite:ユーザーの質問は、そのままだと検索に向いていないことがある

ユーザーの質問が、よい検索語とは限らない

ユーザーはこう言うかもしれません。

「この場合、まだ返金できますか?」

でも、ナレッジベースにはこう書かれているかもしれません。

「購入後 7 日以内かつ学習進捗が 20% 未満の場合は返金可能」

このとき、システムは質問を検索しやすい形に書き換えることがあります。

おもちゃ版のクエリ書き換え

def rewrite_query(query):
replacements = {
"講座をやめたい": "返金したい",
"コースをやめてもいい?": "返金できますか?",
"証明書を取りたい": "証明書を取得したい",
"講座をやめる": "返金",
"コースをやめる": "返金",
"証明書を取る": "証明書",
"卒業証書": "証明書"
}
new_query = query
for old, new in replacements.items():
new_query = new_query.replace(old, new)
return new_query

queries = ["講座をやめたい", "証明書を取りたい", "コースをやめてもいい?"]

for q in queries:
print(q, "->", rewrite_query(q))

期待される出力:

講座をやめたい -> 返金したい
証明書を取りたい -> 証明書を取得したい
コースをやめてもいい? -> 返金できますか?

実際のシステムでは、query rewrite を LLM が担当することもあります。

Query Rewrite と Rerank の二段階ファネル図

図の読み方

Query Rewrite は検索の前に行い、ユーザーの質問を探しやすくします。Rerank は広く集めた後に行い、候補の並びを整えます。役割は別なので、同じものとして考えないでください。


六、ほかにはどんな検索強化戦略があるのか?

Multi-query

1 つの質問を複数の言い換えに変えて、それぞれ検索し、結果をまとめます。

Metadata filter

まず業務条件で範囲を絞ってから、意味検索を行います。

Parent-child retrieval

まず小さな chunk を検索し、そのあとでより大きなまとまりや元の段落に戻ります。

Self-query retrieval

モデル自身に、必要なフィルター条件や検索フィールドを判断させます。


七、どの検索戦略を選べばいいのか?

専門用語が多い場合

次の方法をより重視します。

  • キーワード検索
  • ハイブリッド検索
  • メタデータフィルター

ユーザーの表現がかなり口語的な場合

次の方法をより重視します。

  • ベクトル検索
  • query rewrite
  • rerank

ナレッジベースの構造化が進んでいる場合

次のような構成も考えられます。

  • まずルーティングする
  • 次に対象を絞って検索する
  • 最後に並べ替える

八、目標が「ナレッジベース駆動の教材生成アシスタント」なら、検索戦略はどう考えるべきか?

この種のプロジェクトでは、検索は単に「関連する内容を見つける」ことではありません。
むしろ、2 段階の選択のようなものです。

  1. まず、内部資料を見るか、外部資料で補うかを決める
  2. 次に、知識ポイントを探すのか、例題を探すのか、練習問題を探すのかを決める

そのため、検索条件は次のように整理するとよいです。

条件何を制御するか
topic現在のテーマ
content_type概念 / 例題 / 練習
source_origin内部資料 / 外部資料
grade学年や対象者

この線は、まず次の一文として覚えるとよいです。

教材生成プロジェクトの検索は、単に「関連を探す」のではなく、「欄ごとに合う資料を探す」ことです。

最小のフィルター例は、まず次のように書けます。

items = [
{"topic": "割引の応用問題", "content_type": "concept", "source_origin": "internal", "text": "割引 = 定価 × 割引率"},
{"topic": "割引の応用問題", "content_type": "example", "source_origin": "internal", "text": "商品が 100 円で、2 割引の後はいくらになりますか?"},
{"topic": "割引の応用問題", "content_type": "note", "source_origin": "external", "text": "外部資料の補足:割引のよくある誤解。"},
]

hits = [
x for x in items
if x["topic"] == "割引の応用問題" and x["content_type"] in {"concept", "example"}
]

for hit in hits:
print(hit)

期待される出力:

{'topic': '割引の応用問題', 'content_type': 'concept', 'source_origin': 'internal', 'text': '割引 = 定価 × 割引率'}
{'topic': '割引の応用問題', 'content_type': 'example', 'source_origin': 'internal', 'text': '商品が 100 円で、2 割引の後はいくらになりますか?'}

この例は特に初心者に向いています。なぜなら、次のことが分かるからです。

  • metadata filter は、しばしば「より大きなモデルに変える」より先に効く

九、初学者がよくやる間違い

ベクトル検索だけを試して、キーワード検索を試さない

企業の現場では、キーワード検索は弱くありません。むしろ土台になることも多いです。

検索戦略を最初から複雑にしすぎる

まずは次の順で始めるのがおすすめです。

  1. baseline を作る
  2. 明確な評価セットを用意する
  3. 1 回で変える戦略は 1 つだけにする

召回だけ見て、最終回答を見ない

検索スコアが高くても、最終的な答えが必ず良くなるとは限りません。
生成の段階でも結果は変わります。


検索戦略の調整表

検索を調整するときは、ただ「うまくいかない」と言うのではなく、現象を調整可能なレバーに対応づけることが大切です。

現象まず調整するもの理由
明確な専門用語やエラーコードが見つからないキーワード検索またはハイブリッド検索を増やすベクトル検索では正確な語が弱まることがある
ユーザーの口語的な質問が見つからないquery rewrite、multi-query、ベクトル検索ユーザー表現と文書表現が一致しない
top-k の中で関連内容の順位が低いrerank召回はできていても、並びが十分に正確でない
検索結果のテーマは合っているのに版が違うmetadata filter版、日付、出典で範囲を絞る必要がある
回答に複数の断片が必要parent-child retrieval、またはより適切な chunk 化小さな chunk は当たっても、文脈が足りない

この表は、評価セットと一緒に使うのに向いています。毎回 1 つだけ戦略を変え、Hit@k、MRR、引用の質、失敗サンプルの変化を記録してください。

検索実験の記録テンプレート

実験戦略top-krerank あり結果結論
baselineキーワード3いいえ正確な語には当たるが、同義表現を取りこぼすエラーや専門用語に向いている
exp-1ベクトル3いいえ同義表現には強いが、専門用語は不安定なことがあるキーワード経路を残す必要がある
exp-2ハイブリッド5はい全体的に最もよいが、遅延は増える標準版として使える

検索改善のポイントは、一度で完璧な戦略を見つけることではなく、変更ごとに記録があり、指標があり、失敗サンプルがある状態にすることです。

まとめ

この節で最も大切な理解は次の通りです。

RAG の「資料を探す」工程は機械的な作業ではなく、繰り返し設計し、改善できるシステムの一部です。

多くの場合、検索戦略を改善する効果は、より大きなモデルに変えることよりも直接的です。


練習

  1. ハイブリッド検索の例で重みを変え、キーワード重視とベクトル重視で並び順がどう変わるか比べてみましょう。
  2. 文書に「講座をやめる」という語を含む文を 1 つ追加し、キーワード検索の強みを観察してみましょう。
  3. もっと豊富な rewrite_query() のルール表を自分で作ってみましょう。