DEV Community

plasmon
plasmon

Posted on

RAGの検索精度を3軸で測ったら最適解が条件で全く変わった

RAGの検索精度を3軸で測ったら最適解が条件で全く変わった

RAG(Retrieval-Augmented Generation)を組むとき、embeddingモデル・検索アルゴリズム・チャンクサイズの3つを「なんとなく」で選んでいないだろうか。

「BGE-M3が安定」「ベクトル検索で十分」「チャンクは500-1000文字」。よく見るアドバイスだ。しかし、この3軸を日本語テクニカルコーパスで実測したら、デフォルト設定の落とし穴が見えてきた。

E5-small(384次元)がBGE-M3(1024次元)より高品質で9倍速い。BM25は形態素解析を入れるだけでJ-Mean@3が5.50→8.97に跳ね上がり、300文字チャンクが600文字・1200文字に圧勝した。

最大のインパクトは「アルゴリズムの選択」ではなく、「日本語トークナイザが壊れていた」という基盤の問題だった。この記事では実測データを全て公開した上で、あなたの条件に合った構成の選び方を整理する。


実験の設計

コーパス

日本語テクニカル記事(Zenn/Qiita)217件から生成した約1,500チャンク。AI・半導体・ハードウェア領域の技術記事が中心。英語コンテンツは含まない。

評価方法: Judge-based

検索結果の品質をLLMジャッジ(mmarco multilingual cross-encoder)が10点スケールで評価する。従来のrecall/precisionではなく、「検索されたチャンクが質問に対してどれだけ有用か」を直接測る。

主要指標:

  • J-MRR(Judge Mean Reciprocal Rank): 最も関連性の高い結果が上位に来ているか
  • J-Mean@k: 上位k件の平均品質スコア(10点満点)
  • High-hit@k: 高品質チャンクがk件中に含まれる確率

3班分離

パイプライン(検索実行)・ジャッジ(品質評価)・分析(統計処理)を分離し、評価バイアスを抑制した。


軸1: Embeddingモデル — 小さい方が速くて品質も高かった

4つのembeddingモデルを同一コーパス・同一クエリで比較した。

モデル 次元 J-Mean@3 NDCG@3 速度 (texts/s) 埋め込み時間
E5-small 384 9.03 0.929 234 6.5秒
E5-large 1024 9.04 0.932 27 52.7秒
BGE-M3 1024 8.76 0.879 25 57.5秒
MiniLM 384 8.32 0.845 521 2.8秒

E5-smallはE5-largeと品質がほぼ同じ(J-Mean@3で0.01差)で、速度が9倍、VRAM使用量が約1/5。BGE-M3は品質でもE5-smallに負けている。

MiniLMは最速(521 texts/s)だが、k=10での品質低下が顕著(NDCG@10: 0.726 vs E5-small: 0.933)。上位3件だけ使う用途なら選択肢になるが、広く情報を集めたい場合は不向き。

なぜ小さいモデルが勝つのか

直感的には、1024次元の方が384次元より多くの情報をエンコードできるはずだ。しかし、1,500チャンクの日本語テック記事というコーパスに対して、1024次元は過剰だった可能性が高い。限られたドメインのテキストでは、384次元でも意味的な差異を十分に表現でき、むしろ空間が密に使われることで検索精度が安定する。

この解釈はスケーリング実験でも支持された。ノイズチャンクを追加して最大19,477チャンクまで拡大したテストで、E5-smallはBGE-M3よりもノイズ耐性が高かった(19K時点でMRR 0.856 vs 0.742)。

ただし注意: これは約20Kチャンクまでの結果。10万チャンク超の大規模コーパスでは逆転する可能性がある。


軸2: 検索アルゴリズム — 壊れたトークナイザが全ての元凶だった

5つの検索戦略を比較した。最も重要な発見がここで出た。ただし、その意味を正確に理解するには順を追って見る必要がある。

まず、形態素解析なしの結果(embeddingモデル: BGE-M3)

戦略 J-MRR J-Mean@3 備考
dense(ベクトル検索) 0.918 6.81 BGE-M3での意味的類似度検索
hybrid RRF 0.825 5.91 dense + BM25のスコア融合
BM25(.split()) 0.802 5.50 スペース分割トークナイザ

ベクトル検索がBM25に圧勝。よく見る「BM25は古い」「denseが強い」という話と一致する。ここまでは定説通り — に見えた

形態素解析を入れた瞬間

戦略 J-MRR J-Mean@3 備考
BM25(fugashi) 1.000 8.97 形態素解析トークナイザ
hybrid RRF(fugashi) 1.000 8.76 dense + BM25(fugashi)
dense(BGE-M3) 0.918 6.81 変化なし

BM25のJ-Mean@3が5.50から8.97に跳ねた。これは+63%の改善だが、「BM25がベクトル検索に勝った」と解釈するのは早い。

正しい比較: 同一embeddingモデルで見る

上の表には落とし穴がある。denseの6.81はBGE-M3での結果だ。軸1の実験で確認した通り、E5-smallのdense検索はJ-Mean@3 = 9.03を出している。

戦略 J-Mean@3 備考
E5-small dense 9.03 軸1実験より
BM25 fugashi 8.97 形態素解析あり
BGE-M3 dense 6.81 軸2のベースライン
BM25 .split() 5.50 形態素解析なし

E5-small denseとBM25 fugashiはほぼ互角(差はわずか0.06)。つまり、BM25が勝ったのではなく、壊れたトークナイザを修正したら、BM25が本来の性能を取り戻したというのが正確な描写だ。

MRRが1.000(29クエリ全問で最上位にヒット)になったのも、コーパスが1,500チャンク・日本語テック記事という狭いドメインで、技術用語のキーワード一致が効きやすい条件だったことが大きい。汎用ドメインや大規模コーパスでは異なる結果になり得る。

本当の教訓: デフォルト設定が日本語を壊している

日本語にはスペースがない。.lower().split()で分割すると、文全体が1トークンになるか、改行位置でしか切れない。つまりBM25がまともに動いていなかった

fugashi(MeCab)で形態素解析すると、「半導体」「アーキテクチャ」「推論」のような技術用語が正しく分割される。BM25は本来キーワード一致に強いアルゴリズムで、正しいトークナイザさえあればベクトル検索と互角以上になる。

これは日本語(および中国語など、スペースで単語が区切られない言語)に特有の問題だ。英語では.split()で概ね正しくトークン化されるため、同じ問題は起きにくい。LangChainやLlamaIndexのデフォルト設定で日本語BM25を使うと、能力の半分も出ていない可能性がある。

「ハイブリッド検索が最強」は条件つき

LangChainやLlamaIndexのドキュメントでは「hybrid検索を使いましょう」が定石だが、今回の実測ではBM25単独(fugashi)がhybrid RRFをわずかに上回った。ベクトル検索の結果を混ぜることで、BM25の完全一致のシグナルが薄まっているためと考えられる。

ただし、これは技術用語の完全一致が重要な狭いコーパスでの結果だ。意味的に近い文を探したい場合(例: 同義語での検索、抽象的な質問への回答)や、コーパスが大規模化した場合は、denseやhybridの方が適している可能性が高い。


軸3: チャンクサイズ — 小さいチャンクが一貫して勝った

3つの固定サイズチャンクを比較した。

サイズ チャンク数 J-MRR J-Mean@3 NDCG@3
300文字 2,877 0.855 6.48 0.566
600文字 1,497 0.825 5.91 0.493
1,200文字 718 0.785 3.97 0.355

300→1200で品質がほぼ半減。チャンクが大きくなるほど、関連情報がノイズに埋もれて検索精度が落ちる。

NVIDIAのベンチマーク(2025)でも256-512トークンが最適とされており、300文字(約150-200トークン)は妥当な範囲内にある。また、NAACL 2025で発表されたVectaraの研究では、固定サイズ分割がセマンティック分割を一貫して上回った。

LLMの長文脈対応とチャンクサイズは別問題

「LLMのコンテキスト窓が128Kになったからチャンクを大きくすべき」という意見もあるが、これは検索と生成を混同している。検索の精度は「必要な情報だけを的確に取り出せるか」で決まる。大きなチャンクは関連情報を含む確率は上がるが、無関係な情報も同時に持ち込む。検索精度と生成品質は別の最適点を持つ。


MMR(多様性検索): 品質が壊滅した

Maximal Marginal Relevance(MMR)は検索結果の多様性を上げる手法として知られるが、今回のテストでは品質が壊滅した。

戦略 J-Mean@3 Source Diversity@3
BM25 fugashi 8.97 0.51
hybrid MMR (λ=0.7) 1.12 0.84
hybrid MMR (λ=0.5) 0.98 0.86

多様性は0.51→0.86に向上したが、品質が10点満点中1点に崩壊。λを0.7に上げても回復しない。

記事生成のように多角的な情報が必要な用途では多様性は重要だが、MMRは現時点で品質を犠牲にしすぎる。クラスタリングベースの多様性確保など、別のアプローチが必要。


あなたの条件で選ぶ: 構成選定マトリクス

あなたの条件 Embedding 検索 チャンク 根拠
日本語 + 技術用語多い + ~10Kチャンク以下 E5-small BM25 + 形態素解析 or dense 300文字 本実験の実測構成。BM25 fugashiとE5-small denseは品質ほぼ互角、速度・VRAMはE5-small優位
日本語 + 汎用テキスト + 大規模 (10K-100K+) BGE-M3 or E5-large hybrid RRF + 形態素解析 600文字 大規模ではdenseの意味検索が効いてくる可能性(未検証)
英語中心 E5系 or OpenAI ada-002 dense or hybrid 512トークン 英語ではBM25の形態素解析問題が発生しない
多言語混在 BGE-M3 hybrid RRF 600文字 BGE-M3の多言語対応が活きる
とにかく速度重視 MiniLM BM25 300文字 品質はやや落ちるが最速
多様性が必要 MMR以外を検討 MMRは品質壊滅。クラスタリングベース等を要検討

最も重要な1つだけ変えるなら

日本語RAGでまず最初に見直すべきはBM25のトークナイザだ。

今回の全実験で最大の効果量は、BM25のトークナイザを.split()からfugashi形態素解析に変えた時のJ-Mean@3: 5.50→8.97(+63%)。これは「BM25の最適化」ではなく「壊れていたトークナイザの修正」だ。正しくトークン化されたBM25はベクトル検索と互角の品質を出す。embeddingモデルの変更(+3%)やチャンクサイズの変更(+10%)に比べて、影響の大きさが桁違いに大きい。

LangChainのデフォルト構成でhybrid searchを使っている場合、BM25側のトークナイザが.split()のままになっていないか確認してほしい。日本語テキストに対してスペース分割は機能しない。fugashi(MeCab)やSudachi、Janeomeなどの形態素解析器を入れるだけで、検索品質が回復する。


この実験の限界

  1. コーパスが狭い: 日本語テック記事1,500チャンクでの結果。英語テキスト、汎用コーパス、10万チャンク級では異なる結果になり得る。一般に英語のopen-domain QAベンチマークでは、hybrid検索がBM25単独を上回る傾向が報告されている
  2. Judge biasの可能性: LLMジャッジは短く焦点の絞られた回答を好む傾向がある。小チャンクが「実際に良い」のか「ジャッジが好む」だけなのかは区別できていない
  3. End-to-endの検証がない: 検索品質の改善が最終的な生成品質(記事の質)に繋がるかは未検証
  4. MMRのλ範囲が不十分: λ=0.5と0.7のみテスト。0.9や0.95での結果は未確認

大規模・広範なコーパスでの追試を計画している。結果が出たら続報を書く。


実装: 最小構成のコード

E5-small + BM25 fugashi + 300文字チャンクの最小実装を示す。

形態素解析トークナイザ

import fugashi

tagger = fugashi.Tagger()

def tokenize_ja(text: str) -> list[str]:
    """日本語テキストを形態素解析でトークン化する"""
    tokens = []
    for word in tagger(text):
        surface = word.surface.strip()
        if not surface:
            continue
        # 1文字ひらがな(助詞等)を除外
        if len(surface) == 1 and surface in "はがのをにへとでやかもば":
            continue
        tokens.append(surface.lower())
    return tokens
Enter fullscreen mode Exit fullscreen mode

BM25検索(形態素解析版)

from rank_bm25 import BM25Okapi

# ドキュメントのトークン化
tokenized_docs = [tokenize_ja(doc) for doc in documents]
bm25 = BM25Okapi(tokenized_docs)

# 検索
query_tokens = tokenize_ja("E5-smallの検索精度")
scores = bm25.get_scores(query_tokens)
Enter fullscreen mode Exit fullscreen mode

.lower().split()をこのtokenize_jaに置き換えるだけで、検索品質が大幅に改善する。

Embeddingモデルの変更

from sentence_transformers import SentenceTransformer

# BGE-M3の代わりにE5-smallを使う
model = SentenceTransformer("intfloat/multilingual-e5-small")

# E5はプレフィックスが必要
query_vec = model.encode("query: E5-smallの検索精度")
doc_vec = model.encode("passage: E5-smallは384次元のembeddingモデルで...")
Enter fullscreen mode Exit fullscreen mode

参考文献

  1. Vectara. "Chunking Strategies and Embedding Models for RAG" (NAACL 2025) — 25チャンキング手法×48 embeddingモデルの網羅的比較で、固定サイズ分割がセマンティック分割を一貫して上回ることを確認
  2. NVIDIA. "Finding the Best Chunking Strategy for Accurate AI Responses" (2025) — 256-512トークンが最適範囲
  3. Milvus Blog. "Best Embedding Model for RAG 2026" — E5-smallが70倍大きいモデルを上回った事例を報告
  4. Ahogrammer. "ハイブリッド検索で必ずしも検索性能が上がるわけではない" — トークン化の違いによる性能差を指摘
  5. Zenn fp16. "日本語RAGのEmbeddingモデル、結局どれが最強なのか?" (2026) — 6構成2000問の日本語ベンチマーク

Top comments (0)