コンテンツにスキップ

8.1.5 検索戦略

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

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

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

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

Section titled “一、検索は「top-k だけ」ではない”

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

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

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

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

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

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

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

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

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

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

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

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

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

たとえば、

「講座をやめたいです」

と、

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

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


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

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

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

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

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

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

ベクトルだけを使うと、

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

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

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

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

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

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

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

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

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

検索戦略の比較図

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


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

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

次の例では、

  • 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)

期待される出力:

Terminal window
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:まず広く集めて、あとで細かく並べ替える

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

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

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

これを rerank と呼びます。

就職活動でいうと、

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

RAG でも同じです。


五、クエリ書き換え:ユーザーの質問は、そのままだと検索に向いていないことがある

Section titled “五、クエリ書き換え:ユーザーの質問は、そのままだと検索に向いていないことがある”

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

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

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

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

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

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

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

def rewrite_query(query):
rewrite_rules = {
"講座をやめたい": "返金条件 講座キャンセル",
"コースをやめてもいい": "返金条件 講座キャンセル",
"この場合、まだ返金できますか": "返金条件 購入日 学習進捗",
"証明書を取りたい": "証明書条件 プロジェクト完了 テスト合格",
"卒業証書": "証明書条件 プロジェクト完了 テスト合格",
}
for phrase, retrieval_query in rewrite_rules.items():
if phrase in query:
return retrieval_query
return query
queries = ["講座をやめたい", "証明書を取りたい", "この場合、まだ返金できますか?"]
for q in queries:
print(q, "->", rewrite_query(q))

期待される出力:

Terminal window
講座をやめたい -> 返金条件 講座キャンセル
証明書を取りたい -> 証明書条件 プロジェクト完了 テスト合格
この場合、まだ返金できますか? -> 返金条件 購入日 学習進捗

書き換え後のクエリは、きれいな自然文である必要はありません。検索しやすいキーワードのまとまりにすることが目的です。

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

クエリ書き換え と Rerank の二段階ファネル図


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

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

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

メタデータフィルター(Metadata filter)

Section titled “メタデータフィルター(Metadata filter)”

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

親子チャンク検索(Parent-child retrieval)

Section titled “親子チャンク検索(Parent-child retrieval)”

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

自己クエリ検索(Self-クエリ retrieval)

Section titled “自己クエリ検索(Self-クエリ retrieval)”

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


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

Section titled “七、どの検索戦略を選べばいいのか?”

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

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

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

Section titled “ユーザーの表現がかなり口語的な場合”

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

  • ベクトル検索
  • クエリ書き換え
  • rerank

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

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

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

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

八、目標が「知識ベース駆動の SOP 文書アシスタント」なら、検索戦略はどう考えるべきか?

Section titled “八、目標が「知識ベース駆動の SOP 文書アシスタント」なら、検索戦略はどう考えるべきか?”

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

  1. まず、内部資料を見るか、外部資料で補うかを決める
  2. 次に、ポリシーを探すのか、対応ケースを探すのか、チェックリストを探すのかを決める

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

条件何を制御するか
topic現在のテーマ
content_typeポリシー / ケース / チェックリスト
source_origin内部資料 / 外部資料
teamサポートチームや対象者

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

SOP 文書プロジェクトの検索は、単に「関連を探す」のではなく、「セクションごとに合う証拠を探す」ことです。

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

items = [
{"topic": "返金エスカレーション", "content_type": "policy", "source_origin": "internal", "text": "重複請求の返金は、取引証拠を添えてエスカレーションする必要がある。"},
{"topic": "返金エスカレーション", "content_type": "case", "source_origin": "internal", "text": "サポートは決済失敗後の 2 件の成功請求を確認し、billing にエスカレーションした。"},
{"topic": "返金エスカレーション", "content_type": "note", "source_origin": "external", "text": "外部メモ:決済プロバイダでは一時的なオーソリ保留が表示される場合がある。"},
]
hits = [
x for x in items
if x["topic"] == "返金エスカレーション" and x["content_type"] in {"policy", "case"}
]
for hit in hits:
print(hit)

期待される出力:

Terminal window
{'topic': '返金エスカレーション', 'content_type': 'policy', 'source_origin': 'internal', 'text': '重複請求の返金は、取引証拠を添えてエスカレーションする必要がある。'}
{'topic': '返金エスカレーション', 'content_type': 'case', 'source_origin': 'internal', 'text': 'サポートは決済失敗後の 2 件の成功請求を確認し、billing にエスカレーションした。'}

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

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

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

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

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

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

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

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

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

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

Section titled “召回だけ見て、最終回答を見ない”

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


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

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

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

  • baseline: キーワード検索、top-k=3、rerank なし。正確な語には当たりますが、同義表現を取りこぼします。エラーや専門用語に向いています。
  • exp-1: ベクトル検索、top-k=3、rerank なし。同義表現には強いですが、専門用語は不安定なことがあります。キーワード経路を残します。
  • exp-2: ハイブリッド検索、top-k=5、rerank あり。全体品質は最もよいですが、遅延は増えます。遅延が許容できるなら標準版にします。

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

このページを終えたら、この証拠カードを残します。

クエリ
1つのユーザー質問またはテストケース
検索チャンク
chunk id、スコア、ソースタイトル
回答
引用または出典メモ付きの最終回答
失敗確認
証拠不足、誤ったチャンク、古い文書、または裏付けのない主張
次の行動
chunking、embedding、reranking、prompt、または eval の変更

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

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

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


  1. ハイブリッド検索の例で重みを変え、キーワード重視とベクトル重視で並び順がどう変わるか比べてみましょう。
  2. 文書に「講座をやめる」という語を含む文を 1 つ追加し、キーワード検索の強みを観察してみましょう。
  3. もっと豊富な rewrite_query() のルール表を自分で作ってみましょう。
参考実装と解説
  1. Keyword weight を高くすると exact term match が優先され、vector weight を高くすると semantic similarity が優先されます。どちらがよいかは、ユーザーが正確な製品用語で聞くのか、曖昧な自然言語で聞くのかによります。
  2. キーワード検索は、正確な phrase、identifier、講座名、policy term、error code が重要なときに強いです。Embedding がなめらかにしすぎる細かい語を拾えます。
  3. よい rewrite table は、同義語を正規化し、省略語を展開し、ユーザーの言い方を domain term に対応させます。ただし意図を変えてはいけません。悪い rewrite は retrieval を静かに壊すので、ログに残す必要があります。