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


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627015/d0fd3cc3-433d-5a39-24ba-107665bdf3f7.png)

出典：[LangChain公式ドキュメント/Retrieval](https://python.langchain.com/docs/modules/data_connection/)

## はじめに

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

https://www.microsoft.com/en-us/research/people/cmbishop/prml-book/

### 対象読者様

この記事は、以下のような方をメインに想定して執筆しました。
- RAG（Retrieval-Augmented Generation）について学びたい方
- LangChainやOpenAI APIに興味がある方
- LLM（Large Language Model）ベースのアプリケーションを開発したい方


## インストール

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

```bash
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チェーンを初期化し、質問応答を行います。




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


**<details><summary>ソースコード全文</summary><div>**



```python
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)

```

</div></details>


### 1. PDF文書の読み込み
```python
def load_pdf_document(file_path):
    loader = PyPDFLoader(file_path)
    return loader.load()
```
この関数は、`PyPDFLoader` を使用して指定されたファイルパスのPDF文書を読み込みます。読み込まれた文書は、後続の処理のために返されます。

### 2. ドキュメントのチャンク分割
```python
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. 埋め込みモデルの初期化
```python
def initialize_embeddings():
    return OpenAIEmbeddings()
```
この関数は、`OpenAIEmbeddings` を使用してテキストの埋め込みモデルを初期化します。このモデルは、テキストの意味的内容を数値的なベクトルに変換するために使用されます。

### 4. ベクトルストアへのドキュメントの格納
```python
def store_documents_in_vector_store(docs, embeddings):
    db = Chroma.from_documents(docs, embeddings)
    return db.as_retriever()
```
この関数は、`Chroma` を使用して分割されたドキュメントをベクトルストアに格納し、リトリーバーを生成します。これにより、各ドキュメントの埋め込みベクトルが保存され、後続の検索処理に使用されます。

### 5. ドキュメントの抽出
```python
def retrieve_documents(retriever, retrieval_query):
    return retriever.get_relevant_documents(retrieval_query)
```
この関数は、リトリーバーを使用して指定されたクエリに関連するドキュメントを抽出します。これにより、特定の情報を含む文書を取得することができます。

### 6. QAチェーンの初期化と実行
```python
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`を使っています。

### メインの処理
```python
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 misclassiﬁcation-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 classiﬁcation 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 classiﬁcation
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 sufﬁcient 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 deﬁned 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 deﬁne 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を用いることで、ハルシネーションの発生確率を下げることができました。


## 参考文献

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

- [LangChain公式ドキュメント/Retrieval](https://python.langchain.com/docs/modules/data_connection/)

- [Pattern recognition and machine learning](https://www.microsoft.com/en-us/research/people/cmbishop/prml-book/)

- [ChatGPT/LangChainによるチャットシステム構築［実践］入門](https://gihyo.jp/book/2023/978-4-297-13839-4)


### ソースコード

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

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