「Langchainを利用した半構造化データのためのRAGパイプラインの構築」

『美容とファッションのエキスパートがおすすめするLangchainを活用した半構造化データのためのRAGパイプラインの構築』

イントロダクション

Retrieval Augmented Generation(RAG)は長い間存在しています。この概念を基にしたツールやアプリケーションが多数開発されており、ベクトルストア、検索フレームワーク、LLMなどがあり、カスタムドキュメント、特にLangchainを使用した半構造化データとの作業が容易で楽しくなっています。長くて密度のあるテキストとの作業はこれまでになく簡単で楽しいものとなりました。従来のRAGはDOC、PDFなどのドキュメントやファイル形式の非構造化テキストにはうまく対応していますが、PDFの埋め込みテーブルなどの半構造化データには対応していません。

半構造化データとの作業時には通常2つの問題が生じます。

  • 従来の抽出およびテキスト分割方法ではPDFのテーブルを考慮していません。通常、テーブルが分割されてしまい、情報が失われます。
  • テーブルの埋め込みは正確な意味ベースの検索には適さない場合があります。

そのため、本記事ではLangchainを使用して半構造化データ用の検索生成パイプラインを構築し、これらの2つの問題に対処します。

学習目標

  • 構造化、非構造化、半構造化データの違いを理解する。
  • RAGとLangchainの基本をおさらいする。
  • Langchainを使用して半構造化データを処理するためのマルチベクトル検索生成システムを構築する方法を学ぶ。

この記事はData Science Blogathonの一環として公開されました。

データの種類

通常、データには構造化データ、半構造化データ、非構造化データの3つのタイプがあります。

  • 構造化データ:構造化データは標準化されたデータです。データは事前に定義されたスキーマ(行と列など)に従います。SQLデータベース、スプレッドシート、データフレームなどが該当します。
  • 非構造化データ:非構造化データは、構造化データとは異なり、データモデルに従いません。データはランダムな形式となっています。たとえば、PDF、テキスト、画像などです。
  • 半構造化データ:これは前述のデータタイプの組み合わせです。構造化データとは異なり、厳密な定義済みのスキーマを持ちませんが、データはいくつかのマーカーに基づいて階層的な順序を保持しています。これは非構造化データとは異なります。たとえば、CSV、HTML、PDFの埋め込みテーブル、XMLなどが該当します。

RAGとは何ですか?

RAGはRetrieval Augmented Generation(検索拡張生成)の略であり、大規模言語モデルに新しい情報を提供する最も簡単な方法です。RAGについて簡単に説明しましょう。

通常のRAGパイプラインでは、ローカルファイル、Webページ、データベースなどの知識源、埋め込みモデル、ベクトルデータベース、LLMがあります。さまざまなソースからデータを収集し、ドキュメントを分割し、テキストチャンクの埋め込みを取得し、それらをベクトルデータベースに保存します。そして、クエリの埋め込みをベクトルストアに渡し、ベクトルストアからドキュメントを取得し、最終的にLLMで回答を生成します。

これは従来のRAGのワークフローであり、テキストなどの非構造化データにはうまく機能します。ただし、半構造化データ(例:PDF内の埋め込みテーブル)に対してはうまく機能しないことがよくあります。本記事では、これらの埋め込みテーブルの対処方法について学びます。

Langchainとは何ですか?

Langchainは、LLMベースのアプリケーションを構築するためのオープンソースフレームワークです。発売以来、このプロジェクトはソフトウェア開発者の間で広く採用されています。Langchainには、ベクトルストア、ドキュメントローダー、検索システム、埋め込みモデル、テキスト分割ツールなどのツールが含まれており、AIアプリケーションの構築における総合的なソリューションを提供しています。しかし、Langchainを特別なものにする2つの中心的な特徴があります。

  • LLMチェーン:Langchainは複数のチェーンを提供します。これらのチェーンは複数のツールを連鎖させて単一のタスクを達成します。例えば、ConversationalRetrievalChainはLLM、ベクトルストアリトリーバー、埋め込みモデル、チャット履歴オブジェクトを連鎖させてクエリに対する応答を生成します。ツールはハードコーディングされており、明示的に定義する必要があります。
  • LLMエージェント:LLMチェーンとは異なり、AIエージェントにはハードコードされたツールはありません。ツールを一つずつ連鎖させるのではなく、テキストのツールの説明に基づいてLLMがどのツールを選択し、いつ選択するかを決定します。これにより、推論や意思決定を含む複雑なLLMアプリケーションの構築に適しています。

RAGパイプラインの構築

これで概念について理解ができたので、パイプラインの構築方法について考えましょう。半構造化データを扱うことは一般的な情報の格納方法に従わないため、トリッキーな場合があります。また、非構造化データを扱うためには、情報を抽出するために特別に作られたツールが必要です。したがって、このプロジェクトでは、そのようなツールの一つである「unstructured」を使用します。これは、PDF、HTML、XMLなどの異なる非構造化データ形式から情報を抽出するためのオープンソースツールです。Unstructuredは、複数のデータ形式を処理するためにTesseractとPopplerを内部で使用しています。では、コーディングの前に環境を準備し、依存関係をインストールしましょう。

開発環境の設定

他のPythonプロジェクトと同様に、Python環境を開き、PopplerとTesseractをインストールします。

!sudo apt install tesseract-ocr!sudo apt-get install poppler-utils

これで、プロジェクトで必要な依存関係をインストールします。

!pip install "unstructured[all-docs]" Langchain openai

Unstructuredを使用して抽出する

依存関係がインストールされたので、PDFファイルからデータを抽出します。

from unstructured.partition.pdf import partition_pdfpdf_elements = partition_pdf(    "mistral7b.pdf",     chunking_strategy="by_title",     extract_images_in_pdf=True,     max_characters=3000,    new_after_n_chars=2800,    combine_text_under_n_chars=2000,    image_output_dir_path="./"    )

これを実行すると、OCRに必要なYOLOxなどのさまざまな依存関係がインストールされ、抽出されたデータに基づいてオブジェクトのタイプが返されます。extract_images_in_pdfを有効にすることで、unstructuredはファイルから埋め込まれた画像を抽出することができます。これはマルチモーダルなソリューションの実装に役立ちます。

さて、PDFから要素のカテゴリを探索しましょう。

# 各タイプのカウントを格納する辞書を作成category_counts = {}for element in pdf_elements:    category = str(type(element))    if category in category_counts:        category_counts[category] += 1    else:        category_counts[category] = 1# ユニークなカテゴリを取得unique_categories = set(category_counts.keys())category_counts

これを実行すると、要素のカテゴリとそのカウントが出力されます。

これで、処理を容易にするために要素を分離します。LangchainのDocumentタイプを継承したElementタイプを作成します。これにより、より整理されたデータが得られ、取り扱いやすくなります。

from unstructured.documents.elements import CompositeElement, Tablefrom langchain.schema import Documentclass Element(Document):    type: str# タイプによる分類categorized_elements = []for element in pdf_elements:    if isinstance(element, Table):        categorized_elements.append(Element(type="table", page_content=str(element)))    elif isinstance(element, CompositeElement):        categorized_elements.append(Element(type="text", page_content=str(element)))# テーブルの要素stable_elements = [e for e in categorized_elements if e.type == "table"]# テキストの要素text_elements = [e for e in categorized_elements if e.type == "text"]

マルチベクトルリトリーバー

テーブルとテキストの要素があります。これらを処理するためには、2つの方法があります。生の要素をドキュメントストアに保存するか、テキストの要約を保存します。テーブルは意味検索に課題を提供する可能性があるため、この場合は表の要約を作成し、生のテーブルと共にドキュメントストアに保存します。これを実現するために、MultiVectorRetrieverを使用します。このリトリーバーは、要約テキストの埋め込みを格納するベクトルストアと、生のドキュメントを格納するシンプルなインメモリドキュメントストアを管理します。

まず、以前に抽出した表とテキストデータを要約するための要約チェーンを構築します。

from langchain.chat_models import coherefrom langchain.prompts import ChatPromptTemplatefrom langchain.schema.output_parser import StrOutputParserprompt_text = """You are an assistant tasked with summarizing tables and text. \Give a concise summary of the table or text. Table or text chunk: {element} """prompt = ChatPromptTemplate.from_template(prompt_text)model = cohere.ChatCohere(cohere_api_key="your_key")summarize_chain = {"element": lambda x: x} | prompt | model | StrOutputParser()tables = [i.page_content for i in table_elements]table_summaries = summarize_chain.batch(tables, {"max_concurrency": 5})texts = [i.page_content for i in text_elements]text_summaries = summarize_chain.batch(texts, {"max_concurrency": 5})

私は要約のためにCohere LLMを使用しましたが、GPT-4のようなOpenAIモデルを使用することもできます。より優れたモデルはより良い結果を生み出します。モデルは時には表の詳細を完璧に捉えることができないことがありますので、優れたモデルを使用することが良いでしょう。

そして、MultivectorRetrieverを作成します。

from langchain.retrievers import MultiVectorRetrieverfrom langchain.prompts import ChatPromptTemplateimport uuidfrom langchain.embeddings import OpenAIEmbeddingsfrom langchain.schema.document import Documentfrom langchain.storage import InMemoryStorefrom langchain.vectorstores import Chroma# 子チャンクをインデックスするために使用するベクトルストアvectorstore = Chroma(collection_name="collection",                     embedding_function=OpenAIEmbeddings(openai_api_key="api_key"))# 親ドキュメントのストレージレイヤーstore = InMemoryStore()id_key = ""id"# リトリーバretriever = MultiVectorRetriever(    vectorstore=vectorstore,    docstore=store,    id_key=id_key,)# テキストを追加doc_ids = [str(uuid.uuid4()) for _ in texts]summary_texts = [    Document(page_content=s, metadata={id_key: doc_ids[i]})    for i, s in enumerate(text_summaries)]retriever.vectorstore.add_documents(summary_texts)retriever.docstore.mset(list(zip(doc_ids, texts)))# テーブルを追加table_ids = [str(uuid.uuid4()) for _ in tables]summary_tables = [    Document(page_content=s, metadata={id_key: table_ids[i]})    for i, s in enumerate(table_summaries)]retriever.vectorstore.add_documents(summary_tables)retriever.docstore.mset(list(zip(table_ids, tables)))

要約されたテキストとテーブルの埋め込みを格納するためにChromaベクトルストアとインメモリドキュメントストアを使用しました。

RAG

リトリーバが準備できたので、Langchain Expression Languageを使用してRAGパイプラインを構築できます。

from langchain.schema.runnable import RunnablePassthrough# プロンプトテンプレートtemplate = """Answer the question based only on the following context, which can include text and tables::{context}Question: {question}"""prompt = ChatPromptTemplate.from_template(template)# LLMmodel = ChatOpenAI(temperature=0.0, openai_api_key="api_key")# RAGパイプラインchain = (    {"context": retriever, "question": RunnablePassthrough()}    | prompt    | model    | StrOutputParser())

これで、ベクトルストアから取得した埋め込みに基づいて質問をすることができます。

chain.invoke(input = "What is the MT bench score of Llama 2 and Mistral 7B Instruct??")

結論

半構造データ形式には、非常に多くの情報が隠されています。そして、これらのデータに対して抽出や従来のRAGの実行は困難です。この記事では、PDF内のテキストと埋め込まれた表を抽出して、Langchainを使用してマルチベクトルリトリーバとRAGパイプラインを構築しました。したがって、記事のキーポイントは次のとおりです。

キーポイント

  • 従来のRAGは、テキスト分割中の表の破壊や不正確な意味的検索など、半構造データの取り扱いにおいて問題を抱えていることがよくあります。
  • 半構造データ用のオープンソースツールであるUnstructuredは、PDFや類似の半構造データから埋め込まれたテーブルを抽出することができます。
  • Langchainを使用することで、テーブルやテキスト、要約を文書ストアに格納し、より良い意味的検索を行うためのマルチベクトルリトリーバを構築することができます。

よくある質問

この記事に表示されているメディアはAnalytics Vidhyaが所有しておらず、著者の裁量で使用されています。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more