LoginSignup
1
4

【2023年12月最新】LangChainを用いてPDFから演習問題を抽出する方法【RAG】

Last updated at Posted at 2023-12-14

image.png

出典:LangChain公式ドキュメント/Retrieval

はじめに

この記事では、LangChainを活用してPDF文書から演習問題を抽出する方法を紹介します。これは、いわゆるRAG(Retrieval-Augmented Generation)の実践例となります。使用するPDF文書としては、PRML(Pattern Recognition and Machine Learning)の原著を選びました。この文書は無料で公開されており、以下のリンクからアクセスできます。

対象読者様

この記事は、以下のような方をメインに想定して執筆しました。

  • RAG(Retrieval-Augmented Generation)について学びたい方
  • LangChainやOpenAI APIに興味がある方
  • LLM(Large Language Model)ベースのアプリケーションを開発したい方

インストール

この記事のソースコードを実行するためには、以下のライブラリをインストールする必要があります:

pip install openai
pip install langchain
pip install pypdf
pip install tiktoken
pip install chromadb

PDFから演習問題を抽出する手順

LangChainを用いてPDF文書から演習問題を抽出する手順は以下の通りです:

  1. PDF文書の読み込み:
    • PyPDFLoader を使用してPDFファイルを読み込みます。
  2. ドキュメントのチャンク分割:
    • CharacterTextSplitter を用いて文書を小さなテキストチャンクに分割します。
  3. 埋め込みモデルの初期化:
    • OpenAIEmbeddings でテキストの埋め込みモデルを初期化します。
  4. ベクトルストアへのドキュメントの格納:
    • 分割されたドキュメントを Chroma ベクトルストアに格納します。
  5. ドキュメントの抽出:
    • リトリーバーを使用して、特定のクエリに関連するドキュメントを抽出します。
  6. QAチェーンの初期化と実行:
    • ChatOpenAI モデルとリトリーバーを組み合わせてQAチェーンを初期化し、質問応答を行います。

ソースコードのざっくり解説

ソースコード全文
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import PyPDFLoader
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import Chroma


def load_pdf_document(file_path):
    loader = PyPDFLoader(file_path)
    return loader.load()

def split_documents_into_chunks(documents, chunk_size=1000, chunk_overlap=0):
    text_splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
    return text_splitter.split_documents(documents)

def initialize_embeddings():
    return OpenAIEmbeddings()

def store_documents_in_vector_store(docs, embeddings):
    db = Chroma.from_documents(docs, embeddings)
    return db.as_retriever()

def retrieve_documents(retriever, retrieval_query):
    return retriever.get_relevant_documents(retrieval_query)

def initialize_qa_chain(retriever, model_name="gpt-4-1106-preview"):
    chat = ChatOpenAI(model_name=model_name)
    return RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=retriever)

## メインの処理
if __name__ == "__main__":
    file_path = "path_to_your_pdf_file.pdf"
    retrieval_query = "Exercisesを抽出してください。"
    qa_query = "Exerciseを1題選択し日本語に翻訳してください。"

    # 1. PDFを読み込む
    pages = load_pdf_document(file_path)

    # 2. ドキュメントをチャンクに分割
    docs = split_documents_into_chunks(pages)

    # 3. 埋め込みモデルの初期化
    embeddings = initialize_embeddings()

    # 4. ベクトルストアにドキュメントを格納
    retriever = store_documents_in_vector_store(docs, embeddings)

    # 5. ドキュメントを抽出
    context_docs = retrieve_documents(retriever, retrieval_query)
    print(f"len = {len(context_docs)}") # 抽出したドキュメントの数

    first_doc = context_docs[0] # 1つ目のドキュメント
    print(f"metadata = {first_doc.metadata}") # 1つ目のドキュメントのメタデータ
    print(first_doc.page_content) # 1つ目のドキュメントの中身

    # 6. QAチェーンの初期化と実行
    qa_chain = initialize_qa_chain(retriever)
    result = qa_chain.run(qa_query)
    print(result)

1. PDF文書の読み込み

def load_pdf_document(file_path):
    loader = PyPDFLoader(file_path)
    return loader.load()

この関数は、PyPDFLoader を使用して指定されたファイルパスのPDF文書を読み込みます。読み込まれた文書は、後続の処理のために返されます。

2. ドキュメントのチャンク分割

def split_documents_into_chunks(documents, chunk_size=1000, chunk_overlap=0):
    text_splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)
    return text_splitter.split_documents(documents)

この関数は、CharacterTextSplitter を使用して読み込んだPDF文書を小さなテキストチャンクに分割します。これにより、大きな文書を扱いやすいサイズの部分に分けることができます。

3. 埋め込みモデルの初期化

def initialize_embeddings():
    return OpenAIEmbeddings()

この関数は、OpenAIEmbeddings を使用してテキストの埋め込みモデルを初期化します。このモデルは、テキストの意味的内容を数値的なベクトルに変換するために使用されます。

4. ベクトルストアへのドキュメントの格納

def store_documents_in_vector_store(docs, embeddings):
    db = Chroma.from_documents(docs, embeddings)
    return db.as_retriever()

この関数は、Chroma を使用して分割されたドキュメントをベクトルストアに格納し、リトリーバーを生成します。これにより、各ドキュメントの埋め込みベクトルが保存され、後続の検索処理に使用されます。

5. ドキュメントの抽出

def retrieve_documents(retriever, retrieval_query):
    return retriever.get_relevant_documents(retrieval_query)

この関数は、リトリーバーを使用して指定されたクエリに関連するドキュメントを抽出します。これにより、特定の情報を含む文書を取得することができます。

6. QAチェーンの初期化と実行

def initialize_qa_chain(retriever, model_name="gpt-4-1106-preview"):
    chat = ChatOpenAI(model_name=model_name)
    return RetrievalQA.from_chain_type(llm=chat, chain_type="stuff", retriever=retriever)

この関数は、ChatOpenAI モデルとリトリーバーを使用してQAチェーンを初期化します。このチェーンは、指定されたQAクエリに基づいて質問に答えるために使用されます。
モデルはOpenAI Dev Dayで発表されたgpt-4-1106-previewを使っています。

メインの処理

if __name__ == "__main__":
    # ...

このセクションでは、上記で定義した関数を使用して、PDF文書から特定の情報を抽出し、その情報に基づいて質問応答を行うプロセスを実行します。まずPDFを読み込み、ドキュメントをチャンクに分割し、埋め込みモデルを初期化してベクトルストアに格納します。その後、リトリーバーを使用してドキュメントを抽出し、最後にQAチェーンを実行して結果を出力します。

結果と解説

5. ドキュメントの抽出部分ではPDFから演習問題が抽出されています。
実行結果は下記です。抽出されたドキュメントの数、1つ目のドキュメントのメタデータ、1つ目のドキュメントの中身が表示されています。

len = 4
metadata = {'page': 376, 'source': 'path_to_your_pdf_file.pdf'}
Exercises 357
Exercises
7.1 (⋆⋆)www Suppose we have a data set of input vectors {xn}with corresponding
target values tn∈{ −1,1}, and suppose that we model the density of input vec-
tors within each class separately using a Parzen kernel density estimator (see Sec-
tion 2.5.1) with a kernel k(x,x′). Write down the minimum misclassification-rate
decision rule assuming the two classes have equal prior probability. Show also that,if the kernel is chosen to be k(x,x
′)=xTx′, then the classification rule reduces to
simply assigning a new input vector to the class having the closest mean. Finally,
show that, if the kernel takes the form k(x,x′)=φ(x)Tφ(x′), that the classification
is based on the closest mean in the feature space φ(x).
7.2 (⋆)Show that, if the 1on the right-hand side of the constraint (7.5) is replaced by
some arbitrary constant γ>0, the solution for the maximum margin hyperplane is
unchanged.
7.3 (⋆⋆)Show that, irrespective of the dimensionality of the data space, a data set
consisting of just two data points, one from each class, is sufficient to determine the
location of the maximum-margin hyperplane.
7.4 (⋆⋆)www Show that the value ρof the margin for the maximum-margin hyper-
plane is given by
1
ρ2=N∑
n=1an (7.123)
where {an}are given by maximizing (7.10) subject to the constraints (7.11) and
(7.12).
7.5 (⋆⋆)Show that the values of ρand{an}in the previous exercise also satisfy
1
ρ2=2˜L(a) (7.124)
where˜L(a)is defined by (7.10). Similarly, show that
1
ρ2=∥w∥2. (7.125)
7.6 (⋆)Consider the logistic regression model with a target variable t∈{ −1,1}.I f
we define p(t=1|y)=σ(y)where y(x)is given by (7.1), show that the negative
log likelihood, with the addition of a quadratic regularization term, takes the form(7.47).
7.7 (⋆)Consider the Lagrangian (7.56) for the regression support vector machine. By
setting the derivatives of the Lagrangian with respect to w,b,ξ
n, andˆξnto zero and
then back substituting to eliminate the corresponding variables, show that the dual
Lagrangian is given by (7.61).

6. QAチェーンの初期化と実行部分では演習問題が1題選択され、日本語に翻訳されています。
実行結果は下記です。

Exercise 4.6を選んで日本語に翻訳します。
4.6 (⋆) クラス間およびクラス内の共分散行列がそれぞれ(4.27)および(4.28)で定義されていること、(4.34)および(4.36)を使うこと、およびセクション4.1.5で説明されているターゲット値の選択を使うことによって、二乗和誤差関数を最小限にする式(4.33)が(4.37)の形で書けることを示せ。

上記は実際に存在する演習問題です。RAGを用いることで、ハルシネーションの発生確率を下げることができました。

参考文献

この記事は以下の情報を参考にして執筆しました。

ソースコード

本記事で使用したソースコードは、下記のGitHubレポジトリに格納しています。

https://github.com/Isaka-code/openai-api-hands-on

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4