本文へスキップ
BecomeCoder

RAGコース · 第2章 文書を準備する ― チャンク分割 · レッスン8

意味の区切りで分割 ― 段落・文の単位

ブラウザで完結

導入

文字数で機械的に切ると、どうしても単語や文の途中でぶつ切りになりがちでした。文書には本来、段落や文という「意味のまとまり」があります。その切れ目で分ければ、チャンク1つ1つが読んでまとまりのある内容になります。

説明

import re

text = """RAG has two phases. First is indexing.
Second is retrieval and generation.

Chunking matters a lot. Small chunks are precise.
Large chunks keep more context."""

# 段落(空行)で分ける
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
print("Paragraphs:", len(paragraphs))
for p in paragraphs:
    print(" -", repr(p))

# 文(. ! ?)で分ける
sentences = [s.strip() for s in re.split(r"(?<=[.!?])\s+", text) if s.strip()]
print("\nSentences:", len(sentences))
for i, s in enumerate(sentences):
    print(f"[{i}] {s}")

段落分割は単純で、text.split("\n\n") で「空行」を区切りにしています。文分割はもう一段技巧的です。re.split(r"(?<=[.!?])\s+", text) は、「.!?直後にある空白(改行も含む)」で区切る正規表現です。(?<=...) は「肯定後読み」と呼ばれる書き方で、区切り文字そのものは残したまま、その直後の空白だけを区切り目にできます。だから文末の .? はちゃんと各文に残ります。

実務では、文単位で切っただけだと短すぎる文がたくさんできてしまうことがあるので、目標のチャンクサイズに近づくまで隣の文をまとめる「再結合」もよく行います。ここでは仕組みの基本だけ押さえれば十分です。

これでチャンクへの分割は一通り学びました。次章では、こうして切ったチャンクをベクトル(数値の並び)に変換し、質問文と似ているチャンクを実際に検索する方法に進みます。

やってみよう

text の内容を書き換えて、段落数と文数がそれぞれどう変わるか確かめてみましょう。空行を増やしたり、!? で終わる文を足したりしてみてください。

演習

上のコードの sentences を使い、文の数len(sentences))を print で表示してください。

ヒント1を見る

import re としてから、本文中の正規表現で sentences を作ります。

ヒント2を見る

最後に print(len(sentences)) を書きます。

実際に動かしてみよう

下のエディタにPythonを書いて「実行」を押すと、scikit-learn(TF-IDF)とnumpyで本物の「検索」をその場で動かせます。RAGの検索フェーズ――文書のチャンク分割・ベクトル化・類似度計算・並べ替え――が実際に動きます。scikit-learn を使う回は初回の読み込みに数十秒かかることがあります。検索デモのサンプル文書は英語です(TF-IDFの既定トークナイザが日本語を分割できないため。日本語検索の対処は読み物で解説します)。LLMの生成や本物の埋め込みAPIは、APIキーが要るため読み物の回でコードを示します。

Python — ライブラリ付きで実行(Pyodide)

numpy / pandas / matplotlib が使える本物のPython(Pyodide)を読み込みます。初回のみ読み込みに少し時間がかかります(以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。