PDFから文章単位で切り出す（ベクトル検索向け）

本記事は [OpenAI活用法 Advent Calendar 2023 by ナレコム](https://qiita.com/advent-calendar/2023/openai-knowledgecommunication) の5日目の記事です。

OpenAI活用法 Advent Calendar 2023 by ナレコム ではGPTsを含めた最新のOpenAIの活用法について紹介します。

### はじめに

生成AIに対してPrivateなデータを利用して自社向けにカスタマイズして利用したいというニーズは非常に多いです。

取得拡張生成 (RAG) をしたいものの元データがPDFとなっていて、適切に分割できないということがよく課題になります。本記事ではPDFを適切な文章に分割する１つのアプローチ例を紹介します。

https://learn.microsoft.com/ja-jp/azure/search/retrieval-augmented-generation-overview

### RAGとは

RAGを理解するための一つの方法は、それを「情報検索を助けとして使う賢い図書館員」と考えることです。この図書館員（RAG）は、あなたが求める情報を見つけるために、巨大な図書館（インターネットやデータベース）を探し回ります。情報を見つけたら、それをもとにして、あなたの質問に対する理解しやすく、関連性の高い回答を生成します。（GPT-4にて生成）


### PDFを分割するためのアプローチ

#### 1. 出力させるフォーマットを決める
PDFから文章を取り出すには、まず文章からどの様にデータを取り出したいかを決める必要があります。

今回は私が熊本にいることもあり、熊本県立大学の「文学部 履修の手引き」から文章の分割を試みます。

https://www.pu-kumamoto.ac.jp/academic-information/

PDFを確認すると、複数の文章のフォーマットや文章以外の表も含まれます。今回は以下のフォーマットで出力することを目指します。

ページ番号|章番号|章|内容
-|-|-|-
13|1|単位について|本学では、次のとおり
13|2|学期区分及び授業時間|単位とは、学修の量に

このフォーマットであれば、生成AIで検索したときにも元データの章やページ番号も出力することができ、ファクトチェックが容易となります。

#### 2. 文章のパターンを見つける

ChatGPTのGPT-4であれば、PDFを読み込ませてその内容から希望するプログラムを作成することが可能ですが、残念ながら様々なPDFで試みましたが、PDFを適切に分割するようなプログラムを出力させることはできませんでした。

そのために、ステップ２ではPDFの内容を読み込みパターンを見つけ出すことが必要となります。

今回のケースでは、章ごとに切り出すためには概ね以下で可能なことがわかりました。
1. 行の先頭が大文字
1. 2文字目が半角もしくは全角スペース
1. 20文字以内

#### 3. プログラムの実装

上記のパターンをベースにプログラムを組みます。ここからは可能な限りChatGPTを活用しましょう。
コツとしては、元データを読み込むでしまうと、指示以上に元データを気にする傾向が高いので、PDFは読み込ませずやりたいことのみ入力してプログラムを生成します。

生成したプログラムにいくつか修正を加えたものが以下です。

```Python
from PyPDF2 import PdfReader

def custom_rstrip(string):
    # 半角スペースと全角スペースを除去
    return string.rstrip(' \u3000')

# PDFファイルを読み込み、処理する
file_path = '01_tebiki2023bun.pdf'
reader = PdfReader(file_path)

# セクションとそのページ番号を格納するための辞書
sections_with_pages = {}
current_section = ''
current_page = 0
current_section_number = 0
section_text = ''  # セクションテキストの初期化

for page in reader.pages:
    current_page += 1  # ページ番号のインクリメント
    page_text = page.extract_text()
    if page_text:
        for line in page_text.split('\n'):
            # 行が全角数字で始まるかチェック
            if line and '\uff10' <= line[0] <= '\uff19' and line[1] in ['\u3000', ' ']:
                try:
                    # 新しいセクションの開始
                    current_section_name = custom_rstrip(line[2:])
                    if ("、" in current_section_name or "。" in current_section_name) or len(current_section_name) > 15:
                        # セクション名の条件に一致する場合、テキストを追加
                        section_text += line + '\n'
                    else:
                        if current_section:
                            current_section_number += 1
                            # 前のセクションを保存
                            sections_with_pages[current_section_number] = (start_page, current_section, current_section_name, section_text.replace("\n", ""))
                        
                        current_section = line.split()[0]  # セクション名を抽出
                        start_page = current_page
                        section_text = ''
                except IndexError:
                    continue  # セクション名が存在しない場合は次の行へ
            else:
                # 現在のセクションにテキストを追加
                section_text += line + '\n'

# 最初の数セクションを表示
list(sections_with_pages.items())
```

実行すると、例に出した表の項目のデータを出力することができます。
あとは、出力されたデータを目視でもチェックし、問題なければベクトル検索用に使うことができます。


### まとめ

どの企業でも社内規定やその他の様々な情報がPDFで保管されていると思います。Wordなどの元データがあれば、もう少し簡単にデータを取り出すことも可能ですが、元データを利用することが難しい場合の１つのアプローチ例として紹介しました。数十～数百ページのファイルであっても、ルールが統一されている場合には比較的容易に出力することが可能です。逆に、今回の様に１つのファイルの中にもいくつかのルールが混在する場合は、プログラムを利用しても分割することは難しく、十分な期間や工数を見込む必要があります。

---

また、ナレッジコミュニケーションでは  **「[Musubite](https://recruiting.musubite-job.com/features/company/knowledge-communication)」** というエンジニア同士のカジュアルトークサービスを利用しています！この記事にあるような生成AI 技術を使ったプロジェクトに携わるメンバーと直接話せるサービスですので興味がある方は是非利用を検討してください！

https://recruiting.musubite-job.com/features/company/knowledge-communication