はじめに
本記事では、LLMsにおけるいくつかのチャンキング方法を検討し、チャンキングのサイズと方法を選択する際に考えるべきトレードオフについて説明します。最後に、あなたのアプリケーションに最適なチャンキングサイズと方法を決定するための推奨事項をいくつか紹介します。
チャンキングとは
- チャンキングとは、大きなテキストをより小さなセグメントに分割するプロセスのこと
- チャンキングを行う主な理由は、できるだけノイズの少ない、意味的に関連性のあるコンテンツを埋め込むため
- LLMを使ってコンテンツを埋め込むと、ベクトル・データベースから戻ってくるコンテンツの関連性を最適化するのに不可欠なテクニック
埋め込みの特性
文が埋め込まれると、結果のベクトルはその文固有の意味に焦点を当てる
- 他の文埋め込みと比較する場合、比較は当然そのレベルで行われる
- これは、埋め込みが段落や文書に見られるより広範な文脈情報を見逃す可能性があることを意味する
段落や文書全体を埋め込む場合、全体の文脈と、テキスト内の文やフレーズ間の関係の両方が考慮される
- その結果、テキストのより広い意味やテーマを捉えた、より包括的なベクトル表現が得られる。
- 一方、入力テキストのサイズが大きくなると、ノイズが混入したり、個々のセンテンスやフレーズの重要性が薄れたりする可能性があり、インデックスへのクエリ時に正確なマッチを見つけることが難しくなる
クエリの長さは、埋め込み同士がどのように関連しているかに影響する
- 1文や1フレーズのような短いクエリは、特定の情報に集中し、文レベルの埋め込みとのマッチングに適している
- 複数の文や段落にまたがるような長いクエリは、より広い文脈やテーマを探すため、段落レベルや文書レベルの埋め込みに適している
インデックスは非均質であり、様々なサイズのチャンクの埋め込みを含む可能性がある
- クエリー結果の関連性という点で問題を引き起こす可能性があるが、良い結果をもたらす可能性もある。
- 一方では、長いコンテンツと短いコンテンツの意味的表現の不一致により、クエリ結果の関連性が変動する可能性がある。
- 一方、非均一なインデックスは、異なるチャンクサイズがテキストの異なる粒度レベルを表すため、より幅広いコンテキストと情報を捕捉できる可能性がある。これにより、様々なタイプのクエリに柔軟に対応できる。
チャンキングに関する考察
最適なチャンキング戦略の決定にはいくつかの変数が関係し、これらの変数はユースケースによって異なります。
記事や書籍のような長い文書を扱っているのか、それともツイートのような短いコンテンツを扱っているのか
その答えによって、どのモデルがあなたの目的に適しているのか、その結果、どのチャンキング戦略を適用すべきかが決まる
どの埋め込みモデルを使っていますか?また、どのようなチャンクサイズで最適に動作しますか?
例えば、センテントランスフォーマーモデルは個々のセンテンスに対してうまく機能しますが、text-embedding-ada-002のようなモデルは、256や512のトークンを含むチャンクに対してより良いパフォーマンスを発揮します。
ユーザーからの問い合わせの長さや複雑さについて、どのようなことを想定していますか?
短くて具体的なものなのか、長くて複雑なものなのか。これは、埋め込みクエリと埋め込みチャンクの間に密接な相関関係があるように、コンテンツのチャンクを選択する方法にも影響するかもしれません。
検索された結果は、特定のアプリケーション内でどのように利用されるのか?
例えば、セマンティック検索、質問応答、要約、その他の目的に使用されるのでしょうか?例えば、トークン制限のある別のLLMに結果を入力する必要がある場合、それを考慮して、LLMへのリクエストに収めたいチャンクの数に基づいてチャンクのサイズを制限する必要があります。
これらの質問に答えることで、パフォーマンスと精度のバランスが取れたチャンキング戦略を立てることができ、その結果、クエリ結果の関連性を高めることができる。
チャンキング・メソッド
チャンキングにはさまざまな方法があります。
それぞれの方法の長所と短所を把握し、適した手法を選択しましょう。
固定サイズのチャンキング
- チャンクに含まれるトークンの数を決める方法
- 他のチャンキングに比べ計算量が少なく、NLPライブラリを使う必要がないため、使いやすい
- オプションでチャンク間の重複の有無を指定
- 一般的には、チャンク間で意味的な文脈が失われないようにするために、チャンク間の重なりを残す
# LangChainで固定サイズのチャンキングを行う例
text = "..." # ここに処理したいテキストを挿入
# langchainライブラリからCharacterTextSplitterをインポート
from langchain.text_splitter import CharacterTextSplitter
# テキスト分割器を設定
text_splitter = CharacterTextSplitter(
separator = "\n\n", # 文書の区切りとなるセパレーター
chunk_size = 256, # 各チャンクのサイズ(文字数)
chunk_overlap = 20 # チャンクの重複部分のサイズ
)
# テキストを文書に分割
docs = text_splitter.create_documents([text])
コンテンツを意識した チャンキング
チャンキングするコンテンツの性質を利用し、より洗練されたチャンキングを適用するための手法
素分割
- 最も素朴なアプローチは、句点("。")と改行で文章を分割する
- これは高速で単純かもしれないが、このアプローチではすべてのエッジケースを考慮することはできない
text = "..." # your text
docs = text.split("。")
NLTK
- Natural Language Toolkit (NLTK)は、人間の言語データを扱うためのPythonライブラリ
- NLTKはテキストをセンテンスに分割するセンテントークナイザを提供し、より意味のあるチャンクを作成するのに役立ちます。
# LangChainでNLTKを使う例
# 文字列を定義します。処理したいテキストをここに挿入してください。
text = "..." # your text
# langchainライブラリからNLTKTextSplitterをインポート
from langchain.text_splitter import NLTKTextSplitter
# NLTKTextSplitterオブジェクトを作成
text_splitter = NLTKTextSplitter()
# split_textメソッドを使用して、テキストを文に分割
docs = text_splitter.split_text(text)
spaCy
- spaCyはNLPタスクのためのもう一つの強力なPythonライブラリ
- センテンス・セグメンテーション機能を提供し、テキストを効率的に別々のセンテンスに分割することができる
# LangChainでspaCyを使う例
# 処理するテキストはここに挿入してください。
text = "..." # your text
# langchainライブラリからSpacyTextSplitterをインポート
from langchain.text_splitter import SpacyTextSplitter
# SpacyTextSplitterのインスタンスを生成
text_splitter = SpacyTextSplitter()
# split_textメソッドを使用して、テキストを文に分割
docs = text_splitter.split_text(text)
再帰的チャンキング
- セパレータのセットを使用して、入力テキストを階層的かつ反復的に小さなチャンクに分割する
- 最初にテキストを分割しようとしたときに、希望するサイズや構造のチャンクが生成されなかった場合、このメソッドは、希望するサイズや構造のチャンクが生成されるまで、別のセパレーターや基準を用いて、生成されたチャンクを再帰的に呼び出します。
- つまり、チャンクはまったく同じ大きさにはならないが、同じような大きさを「目指す」処理が行われる
# LangChainで再帰的チャンキングを使う例
# 処理対象のテキストを定義
text = "..." # your text
# langchainライブラリからRecursiveCharacterTextSplitterをインポート
from langchain.text_splitter import RecursiveCharacterTextSplitter
# RecursiveCharacterTextSplitterのインスタンスを生成
# ここでは、非常に小さなチャンクサイズ(256文字)を設定
# これは、分割のデモンストレーションを目的としています。
text_splitter = RecursiveCharacterTextSplitter(
chunk_size = 256, # 各チャンクのサイズ
chunk_overlap = 20 # チャンク間の重複部分のサイズ
)
# create_documentsメソッドを使用して、テキストを文書に分割
docs = text_splitter.create_documents([text])
文書フォーマットに特化したチャンキング
MarkdownとLaTeXではチャンキング処理中にコンテンツの元の構造を保持するために、特別なチャンキング方法を使用することができます。
マークダウン形式
Markdownの構文(見出し、リスト、コードブロックなど)を認識することで、その構造と階層に基づいてコンテンツをインテリジェントに分割し、より意味的に一貫性のあるチャンクを作成することができます。
# langchainライブラリからMarkdownTextSplitterをインポートします。
from langchain.text_splitter import MarkdownTextSplitter
# 処理するマークダウン形式のテキストを定義
markdown_text = "..."
# MarkdownTextSplitterのインスタンスを生成
# ここではチャンクサイズを100に設定し、重複は0に設定しています。
markdown_splitter = MarkdownTextSplitter(chunk_size=100, chunk_overlap=0)
# create_documentsメソッドを使用して、マークダウンテキストを文書に分割
docs = markdown_splitter.create_documents([markdown_text])
LaTex
LaTeXは学術論文や技術文書によく使われるマークアップ言語です。
LaTeXのコマンドや環境を解析することで、コンテンツの論理的な構成(セクション、サブセクション、数式など)を尊重したチャンクを作成することができ、より正確で文脈に即した結果を得ることができます。
# langchainライブラリからLatexTextSplitterをインポート
from langchain.text_splitter import LatexTextSplitter
# 処理するLaTeX形式のテキストを定義
latex_text = "..."
# LatexTextSplitterのインスタンスを生成
# ここではチャンクサイズを100に設定し、重複は0にしています。
latex_splitter = LatexTextSplitter(chunk_size=100, chunk_overlap=0)
# create_documentsメソッドを使用して、LaTeXテキストを文書に分割
docs = latex_splitter.create_documents([latex_text])
アプリケーションに最適なチャンクサイズを見つける
ここでは、最適なチャンクサイズを考えるのに役立ついくつかのポイントを紹介します。
データの前処理
アプリケーションに最適なチャンクサイズを決定する前に、まずデータを前処理して品質を確保する必要があります。例えば、データがウェブから取得されたものであれば、ノイズとなるHTMLタグや特定の要素を削除する必要があるかもしれません。
チャンクサイズの範囲を選択する
データの前処理が完了したら、次のステップは、テストするチャンクサイズの範囲を選択することです。
前述したように、コンテンツの性質(短いメッセージや長い文書など)、使用する埋め込みモデル、その機能(トークンの制限など)を考慮して選択する必要があります。
目的は、コンテキストの保持と精度の維持のバランスを見つけることです。より詳細な意味情報を取り込むには小さいチャンク(例:128または256トークン)、より多くのコンテキストを保持するには大きいチャンク(例:512または1024トークン)など、さまざまなチャンクサイズを検討することから始めましょう。
各チャンクサイズのパフォーマンスを評価する
様々なチャンクサイズをテストするには、複数のインデックスを使用するか、1つのインデックスで複数の名前空間を使用します。代表的なデータセットで、テストしたいチャンクサイズの埋め込みを作成し、インデックスに保存します。その後、品質を評価できる一連のクエリを実行し、様々なチャンクサイズのパフォーマンスを比較することができます。これは、コンテンツやクエリに最適なチャンクサイズを決定できるまで、さまざまなクエリに対してさまざまなチャンクサイズをテストする反復プロセスになります。
さいごに
チャンキングに万能のソリューションはないので、あるユースケースでうまくいっても、別のケースではうまくいかないかもしれません。この投稿が、あなたのアプリケーションのチャンキングへの取り組み方について、より良い直感を得る助けになることを願っています。