RAGでは文書を検索単位(chunk)に分割してからインデックスする。chunkの切り方は検索精度に大きく影響し、RAGの精度を最も左右する工程の一つ。
chunkが
大きすぎる:1つのembeddingに複数の話題が混ざり、ベクトルがぼやけて検索でヒットしにくくなる。LLMに渡すコンテキストも無駄に増える
小さすぎる:文脈が失われ、chunk単体では意味が分からなくなる(「同社の売上は前年比10%増」の「同社」が誰なのか分からない、など)
というトレードオフがある。
基本的な分割方法¶
固定長分割¶
文字数やトークン数で機械的に区切る。前後のchunkと一部を重ねる overlap を設けて、境界で文が切れる問題を緩和するのが一般的。
実装が簡単だが、文や段落の途中で切れやすい。
再帰的分割(recursive splitting)¶
段落 → 改行 → 文 → 単語 のように区切り文字の優先順位を決めておき、chunkが上限サイズに収まるまで大きい単位から順に分割する。LangChainの RecursiveCharacterTextSplitter が代表例。
シンプルだが実用上かなり強いベースライン。
構造に沿った分割¶
Markdownの見出し、HTMLのタグ、PDFの章・節、コードの関数・クラスなど、文書の構造を使って区切る。構造がはっきりした文書では意味のまとまりと一致しやすい。
Semantic chunking¶
隣接する文のembeddingの類似度を計算し、類似度が大きく下がる箇所(話題が変わる箇所)で区切る。
計算コストが高い割に、再帰的分割と比べて常に良いとは限らない。
どれが良いのか¶
Chromaの評価(Evaluating Chunking Strategies for Retrieval)では、
RecursiveCharacterTextSplitterで 200〜400トークン・overlapなし がシンプルで性能も良いembeddingモデルを考慮してクラスタリングで区切る
ClusterSemanticChunkerは効率(precision)が最も良いが、実装は複雑
という結果だった。まずは再帰的分割をベースラインにして、評価しながら調整するのが無難。
文脈を補う手法¶
chunk単体では文脈が失われる問題に対して、いくつかの対策がある。
Contextual Retrieval¶
Anthropicが提案した手法(Introducing Contextual Retrieval)。
各chunkについて、文書全体とchunkをLLMに渡して「このchunkが文書全体の中でどういう位置づけか」を説明する短い文脈(50〜100トークン程度)を生成させ、chunkの先頭に付けてからembeddingとBM25のインデックスを作る。
例:「同社の売上は前年比10%増」→「この部分はACME社の2023年第2四半期の決算報告書からの抜粋。前四半期の売上は…。同社の売上は前年比10%増」
top-20の検索失敗率が
| 手法 | 失敗率 |
|---|---|
| ベースライン(embeddingのみ) | 5.7% |
| Contextual Embeddings | 3.7%(-35%) |
| + Contextual BM25 | 2.9%(-49%) |
| + reranking | 1.9%(-67%) |
と改善したと報告されている。prompt cachingを使えば文脈生成のコストは100万トークンあたり約$1.02。
Late chunking¶
通常は「分割 → 各chunkをembedding」の順だが、late chunkingでは
文書全体をlong-contextのembeddingモデルに通してトークンごとの埋め込みを得る
その後でchunkの範囲ごとにトークン埋め込みをmean poolingする
という順にする。各chunkの埋め込みが文書全体の文脈を反映したものになる。LLMを呼ばないのでContextual Retrievalより安い。
Small-to-big(parent document retrieval)¶
検索は小さいchunk(文や段落)で行い、ヒットしたらその親(節や周辺の段落)をLLMに渡す。検索の精度と生成に渡す文脈の豊かさを両立させる。
Proposition単位の分割¶
Chen et al. (2023). Dense X Retrieval: What Retrieval Granularity Should We Use?
LLMで文書を「それ単体で意味が通る最小の事実(proposition)」に書き換えて、それを検索単位にする。代名詞も解決された形になるので文脈の欠落が起きにくい。