はじめに
RAG(Retrieval-Augmented Generation)は、情報を効率的に取得し、それを基に応答を生成する手法です。このプロセスにおいて、大きなドキュメントを適切に分割し、関連する情報を迅速に取り出すことが非常に重要です。特に、テキストの分割方法は、検索効率や回答精度の向上に寄与するため、適切なツールの選択がカギとなります。
ここでは、LangChainライブラリに用意されているテキストを簡単に分割するための「Text Splitters」の中から、CharacterTextSplitterの基本的な使い方とその実際の動作について詳しく見ていきます。
こちらも参考にして下さい
筆者のレベル
- RAGについて基本的な理解を持っている
- RAGを実装することで、さらに深く学びたいと考えている
- プログラミング経験はあまりないが、RAGを実装してみたい
- 実際にコードを作成して動かすことで、理解を深めていきたい
記載していること
- CharacterTextSplitterの基本的な説明
- いくつかの設定パターンで動かしてみた結果
- できなかったこと
CharacterTextSplitterの特徴
CharacterTextSplitterは、文字単位でテキストを分割する機能を持つText Splitterです。このツールを使用することで、文字列処理において、より効果的に情報を扱うことができます。以下に、CharacterTextSplitterの主要なパラメータを説明します。
パラメータの説明
- chunk_size: チャンクの最大サイズを指定します。このサイズに基づいてテキストが分割されます。
- separator: テキストを分割するためのセパレータを指定します。通常、句点や改行などが使われます。
- is_separator_regex: セパレータが正規表現かどうかを指定します。Trueにすると、指定したセパレータが正規表現として解釈されます。
- chunk_overlap: チャンク間のオーバーラップを指定します。これにより、前のチャンクと次のチャンクの一部が重複するようになります。
実装例
以下は、CharacterTextSplitterの基本的な使い方の例です。
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
source_text = "あいうえお、かきくけこさしすせそ。"
chunk_size = 10
chunk_overlap = 2
char_text_splitter = CharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separator='。'
)
documents = char_text_splitter.split_text(source_text)
動かしてみる
ドキュメントの準備
このようなテキストファイルを用意します。
あいうえお。
かきくけこ。さしすせそ。
たちつてと。なにぬねの。はひふへほ。
まみむめも。やゆよ。らりるれろわをん。
①チャンクサイズのみを指定してみる
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import CharacterTextSplitter
# テキストをロード
source_document = TextLoader('sample.txt').load()
source_text = source_document[0].page_content
# CharacterTextSplitterを設定
char_text_splitter = CharacterTextSplitter(
chunk_size=5,
chunk_overlap=0
)
# 分割を実行
char_documents = char_text_splitter.split_text(source_text)
# 分割結果を表示
print('\nCharacterTextSplitterによる分割結果: \n')
for i, chunk in enumerate(char_documents):
print(f"Chunk {i+1} (Length: {len(chunk)}):\n{chunk}\n")
exit(0)
CharacterTextSplitterによる分割結果:
あれ?思ったように分割されていない・・・
Chunk 1 (Length: 58):
あいうえお。
かきくけこ。さしすせそ。
たちつてと。なにぬねの。はひふへほ。
まみむめも。やゆよ。らりるれろわをん。
②チャンクサイズとセパレータを設定してみる
セパレータに空白文字('')を設定することで、chunk_sizeで分割されました。
# CharacterTextSplitterを設定
char_text_splitter = CharacterTextSplitter(
chunk_size=5,
chunk_overlap=0,
separator=''
)
CharacterTextSplitterによる分割結果:
改行は1文字とカウントされています。
Chunk 1 (Length: 5):
あいうえお
Chunk 2 (Length: 5):
。
かきく
Chunk 3 (Length: 5):
けこ。さし
Chunk 4 (Length: 4):
すせそ。
Chunk 5 (Length: 5):
たちつてと
Chunk 6 (Length: 5):
。なにぬね
Chunk 7 (Length: 5):
の。はひふ
Chunk 8 (Length: 5):
へほ。
ま
Chunk 9 (Length: 5):
みむめも。
Chunk 10 (Length: 5):
やゆよ。ら
Chunk 11 (Length: 5):
りるれろわ
Chunk 12 (Length: 3):
をん。
③オーバーラップを設定してみる
chunk_sizeは10、chunk_overlapは2を設定してみます。
# CharacterTextSplitterを設定
char_text_splitter = CharacterTextSplitter(
chunk_size=10,
chunk_overlap=2,
separator=''
)
CharacterTextSplitterによる分割結果:
2文字オーバラップしているのが分かります。
Chunk 1 (Length: 10):
あいうえお。
かきく
Chunk 2 (Length: 10):
きくけこ。さしすせそ
Chunk 3 (Length: 10):
せそ。
たちつてと。
Chunk 4 (Length: 10):
と。なにぬねの。はひ
Chunk 5 (Length: 10):
はひふへほ。
まみむ
Chunk 6 (Length: 10):
みむめも。やゆよ。ら
Chunk 7 (Length: 10):
。らりるれろわをん。
④セパレータを正規表現にしてみる
セパレータに句点(。)と改行(\n)を設定します。
また、分かりやすくするために、テキストファイルの内容を変更します。
あいうえお。かきくけこさしすせそたちつてと。
なにぬねの。はひふへほ、まみむめも、やゆよ。らりるれろわをん。
# CharacterTextSplitterを設定
char_text_splitter = CharacterTextSplitter(
chunk_size=10,
chunk_overlap=2,
is_separator_regex=True,
separator='[。|\\n]'
)
CharacterTextSplitterによる分割結果:
2つのセパレータで分割していることが分かります。
ですが、チャンクサイズが10を超えています、また、オーバーラップしていません。
Chunk 1 (Length: 5):
あいうえお
Chunk 2 (Length: 15):
かきくけこさしすせそたちつてと
Chunk 3 (Length: 5):
なにぬねの
Chunk 4 (Length: 15):
はひふへほ、まみむめも、やゆよ
Chunk 5 (Length: 8):
らりるれろわをん
仕様を調べてみる(ChatGPT)
チャンクサイズ、オーバーラップ、セパレータ の優先度は、テキストの分割において以下のように処理されるとのこと。
1. セパレータによる分割
- 最初に、指定されたセパレータ(文字や正規表現)でテキスト全体を分割します。
- ここでのセパレータは、テキストをどこで区切るかを決定するもので、チャンクサイズに関係なく先に処理されます。
- 例えば、セパレータとして句点(。)や改行(\n)を指定した場合、それを基にテキストを区切ってリストにします。
2. チャンクサイズの調整
- セパレータで分割した後、各チャンクが設定された chunk_size(最大チャンクサイズ)を超える場合、それを更に細かく分割します。
- 逆に、chunk_size よりも小さなチャンクであれば、そのまま次の処理へと進みます。
3. オーバーラップの適用
- 最終的に、チャンク間で重複させる文字数を chunk_overlap で指定します。
- 例えば、前のチャンクの最後の 2 文字が次のチャンクの最初に重なるように設定する場合、chunk_overlap=2 とします。
できなかったこと(考察)
④セパレータを正規表現にしてみる
では、想定していた動作の確認を取ることができませんでした。
②チャンクサイズとセパレータを設定してみる
では、セパレータに空白文字('')を設定することで、chunk_sizeで分割されるようになりました。
同じような設定をすればいいのかもしれませんが、正規表現で空白文字('')を設定ができないことから、この設定は難しいと考えています。
まとめ
RAG(Retrieval-Augmented Generation)においては、文脈や意味の単位でテキストを分割することが、精度向上につながります。そのため、特定の文字数で区切る方法は、利用シーンが少ないのかもしれません。