医療系チャットボットの作成（Python×ChatGPT）その②

皆さん、こんにちは。東京都でクリニックを開業している医師の森　維久郎と申します。

前回に続き医療系チャットボットの作成（Python×ChatGPT）を作っていきたいと思います。

https://qiita.com/ikuro_mori/items/02b34392cc99d1294508

今回は参照するデーターベースをテキストファイル（text.txt）ではなく大元のPDFにしてやってみようと思います。

PDFにすることでガイドラインを参照するチャットボットを作ることが出来るようになります。

私自身はプログラマーではなく、プログラミングについては少し学んだ程度なので、この記事はプログラミングに興味がある医療現場の方々や、医療現場の課題について理解を深めたいプログラマーの方々向けになっています。

あくまでも、現場の医師として作り上げたものなので、改善点などがあればぜひ教えてください。

＊臨床の先生にはかなり込み入った内容かもしれません。。。

## 概要

●長期目標：データーベース（PDF、textファイル、Googleドキュメントなど）を参照にユーザーの質問に答えるQ&Aチャットボットを作成する。

●今回の記事の内容：Google Colabを使用してPDFデーターからデータベースを作成し、ユーザーの質問に答えるQ&Aチャットボットを作成する

●使う機能：Googleコラボ、ChatGPT

## できたコード

前回の記事のこのコードを変更するだけです。
```
##txtファイルからテキストデーターを読み取る
with open('text.txt', 'r') as file:
    database = file.read().splitlines()
```
↓
PDFライブラリからテキストを読み取るライブラリを選択します。
```
!pip install PyMuPDF
import fitz  # PyMuPDF
import openai
```
そして
①test.pdfからテキスト抽出
②テキストを6000文字ごとに分割
③一つずつchatgptに整形してもらう
④最後に整形した文字を結合して一つのテキストデーターにする
⑤このテキストデーターを行ごとに分割する

```
def extract_text_from_pdf(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()

    return text

def format_text_with_gpt3(text):
    openai.api_key = ''  # OpenAIのAPIキーを設定します。

    # GPT-3.5-turbo-16kを使用してテキストを整形
    response = openai.ChatCompletion.create(
      model="gpt-3.5-turbo-16k",
      messages=[
            {"role": "system", "content": "PDFから抽出されたテキストデーターには不自然な改行や空白、文字化けがあるため整形して日本語で読みやすくしてください。"},
            {"role": "user", "content": text},
        ]
    )

    return response['choices'][0]['message']['content']

def split_and_format_text(text, chunk_size):
    # テキストを指定された文字数ごとに分割
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]

    # 各チャンクを整形
    formatted_chunks = [format_text_with_gpt3(chunk) for chunk in chunks]

    # 整形したチャンクを結合
    return ''.join(formatted_chunks)

file_path = '/content/test3.pdf'  # ここにPDFファイルのパスを指定します
text = extract_text_from_pdf(file_path)
pretext = split_and_format_text(text, 6000)
database = pretext.splitlines()
```
後は前回の記事とほぼ同じです。

## 実行してみた

綺麗にできました！綺麗に期待通りの返答が返ってきました。
```
質問：SGLT2阻害薬は糖尿病でなくても使ってよい？

回答：SGLT2阻害薬は、糖尿病合併・非合併にかかわらず、慢性腎臓病(CKD)患者において腎保護効果を期待するため、リスクとベネフィットを十分に勘案して積極的に使用を検討する。
```

## 今回の学びのポイント＆苦悩の共有（長文注意）

冒頭に貼った記事の内容を踏襲して、データーベースのソースをテキストファイル（text.txt）からPDFファイルに変えるだけの簡単な作業かと思いきや、結構躓きました。。。

例えば、最初に動かしたコードでは以下のような返答になってしまいました。

```
質問：SGLT2阻害薬は糖尿病でなくても使ってよい？

回答：SGLT2 阻害薬使⽤における注意点
```
最初原因が分からなかったのでChatGPTに「返答がおかしいぞ」と質問をしたら以下のように返ってきました。

```
質問に対する不自然な回答が返ってくる原因はいくつか考えられます。以下にその可能性をいくつか挙げてみます：

1 データベースの内容: チャットボットが返す回答は、データベースの内容に大きく依存します。データベースに詳細な情報や適切な回答が含まれていない場合、チャットボットは適切な回答を生成できません。

2 ベクトル化の精度: テキストをベクトル化する際の精度も重要です。ベクトル化はテキストの意味を数値的に表現するため、このプロセスが不完全だと意味のある比較ができません。これは特に、特定のドメイン（この場合、医療）に特化した情報を扱う場合に重要です。

3 類似度の計算: ユーザーの質問とデータベースのエントリとの間の類似度を計算する方法も重要です。最も単純な方法はコサイン類似度を使用することですが、これが最善の方法であるとは限りません。

4 PDFの読み取りの精度: PDFからテキストを抽出する際の精度も問題になることがあります。特に、PDFが画像ベースのもの（スキャンされた文書など）の場合、OCR（光学的文字認識）の精度が問題になることがあります。

これらの要素を調査し、必要に応じて改善することで、チャットボットの回答の質を向上させることが可能です。例えば、データベースの内容を詳細に調査したり、異なるベクトル化手法を試したり、類似度の計算方法を調整したりすることが考えられます。
```
検証した結果「④PDFの読み取りの精度」にエラーが出て「②ベクトル化の精度」にもエラーが生じているという結論になりました。

どうやらPDFから抽出したデーターを前処理する必要があるようです。

医師の方なら臨床研究をやる際にデーターベースをExcelとかでクリーニングする作業をご存知だと思いますが、プログラミングの世界でもこれと同様の作業が必要になるということを学びました。

## 　PDFからのテキストデータ抽出

### ①まずライブラリを日本語に対応させる。

pythonを使ってPDFからテキストデーターを抽出するためにはライブラリという機能を使います。

最初PyPDF2というライブラリを使っていましたが、日本語には少し弱いようです。

以下の記事を参照にしてPyMuPDFというライブラリを使用することにしました。

https://python-work.com/pdf-read/

ライブラリを変更してみたところ抽出された日本語の文字化けが減りました。

ただまだまだ編集が必要な状況でした。


### ②不自然な改行、文字化けの修正

PDFからテキストデータを抽出するとき、不自然な改行、文字化けが存在する可能性があります。前回の記事で紹介したコードでは、改行が存在するとその部分でテキストを分割し、一つのまとまりとして処理していました。しかし、これにより不自然な断片が生じる可能性があり、情報量が著しく減少する場合があります。

たとえば、「私は猫である」という文章があった場合、「私は」と「猫である」の間に改行が存在すると、「私は猫である」という一つのまとまった情報が、「私は」と「猫である」に分割されてしまいます。その結果、本来参照すべき正しい情報がリファレンスされず、問題が発生します。

よって以下のようなライブラリを使用して不自然な改行を削除したり、ピリオドで終わる文章ごとに分割したりしました。

```
import fitz  # PyMuPDF
import re  # 正規表現モジュール

def extract_text_from_pdf(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()
    # 不自然な改行を削除
    text = re.sub(r'\n+', ' ', text)
    return text.split('. ')  # ピリオドで終わる文章ごとに分割

# 使用例：
file_path = '/content/test.pdf'  # ここにPDFファイルのパスを指定します
database = extract_text_from_pdf(file_path)  # データベースにPDFから抽出したテキストのリストを格納

```

このコードでは、re.sub(r'\n+', ' ', text)を使用して、テキスト中の連続する改行（\n）を単一のスペースに置き換えています。これにより、不自然な改行が削除されます。

また、text.split('. ')を使用して、テキストをピリオドで終わる文章ごとに分割しています。これにより、データベースは完全な文章を要素とするリストとなります。

これでコードを動かしてみました。

```
質問：SGLT2阻害薬は糖尿病でなくても使ってよい？

回答：CKD 患者に対する SGLT2 阻害薬使⽤における注意点 １）低⾎糖 特に糖尿病患者ではインスリン，スルホニル尿素薬，速効型インスリン分泌促進薬 と SGLT2 阻害薬との併⽤で⾎糖降下作⽤が増強する恐れがあり，これら製剤の減 量を検討する必要がある．糖尿病合併 CKD が進⾏して eGFR が低下した場合，イ ンスリン，スルホニル尿素薬，速効型インスリン分泌促進薬による⾎糖降下作⽤が 増強するため，SGLT2 阻害薬を併⽤する場合には注意を要する．SR による重症低 ⾎糖発症リスクは，SGLT2 阻害薬群において逆に低下しており(RR 0.89 [95% CI 0.80‒0.98]) 14，DAPA-CKD 試験，EMPA-KIDNEY 試験でも正常耐糖能もしくは 境界型の CKD 患者において重篤な低⾎糖イベントは⽣じていない 34, 2．しかしな がら，⾷事摂取不⾜，栄養不良状態，飢餓状態，激しい筋⾁運動，過度のアルコー ル摂取，副腎機能不全，下垂体機能不全，シックデイなどの状況下では低⾎糖を起 こす恐れがあり注意する． ２）正常⾎糖ケトアシドーシス SGLT2 阻害薬にはインスリン⾮依存性の尿中グルコース排泄促進作⽤がある．さ らにインスリンの低下とグルカゴンの上昇を伴って，⾎糖値が正常範囲であって も脂肪酸酸化が亢進し，ケトーシスからケトアシドーシスに⾄ることがある．特に 1 型糖尿病患者で，インスリン製剤の過度な減量や中⽌，過度な糖質制限，⾷事摂 取不良，感染症，脱⽔，シックデイの場合ケトアシドーシスを起こしやすいが，⾮ 糖尿病でも起こりえる．このような状況下では，SGLT2 阻害薬の休薬・中⽌を考 慮する．⾷事摂取ができない⼿術が予定されている場合には，術前 3 ⽇前から休薬 し，⾷事が⼗分摂取できるようになってから再開する．なお DAPA-CKD 試験で は 1 型糖尿病を合併する CKD 患者は除外されており臨床試験が実施されておら ず解釈に注意が必要であるが，正常耐糖能もしくは境界型の CKD 患者ではケトア シドーシスは⽣じていない 34．⼀⽅，1 型糖尿病を合併する CKD 患者を 2.2%含 む EMPA-KIDNEY 試験では，エンパグリフロジン群で 6 ⼈，プラセボで 1 ⼈がケ トアシドーシスを呈していた 2．SR においても，糖尿病患者においてケトアシド ーシス発症のリスクが増加しており（RR 2.12 [95% CI 1.49‒3.04]）14，発症頻度 は少ないものの，注意を要する．特に服薬に関して家族の協⼒が得られない認知機 能低下症例への投与は注意する．SGLT2 阻害薬投与時にはシックデイ時の休薬や 中⽌などの対応について指導を⾏う． 10 ３）体液量減少（脱⽔） SGLT2 阻害薬による利尿作⽤により，⾎糖コントロールが極めて不良な糖尿病患 者や利尿薬併⽤患者において脱⽔を来しやすい．CKD 患者では利尿薬を使⽤して いる割合が⾼く，SGLT2 阻害薬併⽤後には腎機能のモニタリングや電解質のチェ ックを⾏い，利尿薬や降圧薬の減量など⽤量調節が重要である．CREDENCE 試験 35 や DAPA-CKD 試験 1，糖尿病合併 CKD ステージ G3-4 患者におけるエンパグ リフロジンの安全性を検討したメタ解析 36 などでは，脱⽔の明らかなリスク増加 は認められなかったが，個々の患者では⽔分摂取の励⾏やシックデイ対策に関す る患者教育が重要である．シックデイ時には，SGLT2 阻害薬の休薬・中⽌を考慮 する． ４）急性腎障害 CREDENCE 試験 35 や DAPA-CKD 試験 1 においては，プラセボと⽐較し急性腎 障害 (AKI)のリスク上昇は認められていない．データベース研究でも AKI のリス ク低下(OR 0.47 [95% CI 0.27‒0.80])が報告されている 37．最近の SR では，糖尿 病，⾮糖尿病にかかわらず，AKI 発症リスクを低下させている(糖尿病：RR 0.79 [95% CI 0.72‒0.88]，⾮糖尿病：RR 0.66 [95% CI 0.54‒0.81]）14．EMPA-REG OUTCOME 試験のサブ解析では，利尿薬との併⽤は未使⽤群と⽐較して eGFR の initial dip は⼤きくなるが，eGFR の initial dip は，のちの腎イベント発症に影響 を与えなかったとされている 38．DAPA-CKD 試験のサブ解析においても，eGFR の initial dip はのちの腎機能低下には影響しなかった 39．しかしながら個々の症例 においては AKI に注意する必要がある．ループ利尿薬使⽤が AKI のリスク因⼦で あったとの報告もある 40．⾼齢者や⼼不全患者は⾎管内脱⽔に陥りやすく，全⾝の ⾎圧低下による腎⾎流低下から AKI を⽣じる可能性があり，過度な脱⽔を⽣じる 可能性がある際には投与を控えることも考慮する．脱⽔状態での RAS 阻害薬や⾮ ステロイド性消炎鎮痛薬等との併⽤は，過度な⽷球体内圧低下により AKI が発症 する恐れがあり注意を要する．腎⽣検が施⾏された症例で，近位尿細管における空 胞変性を伴う浸透圧腎症が報告されている 41, 42．特に肥満と過⾷があり，⾎糖コン トロールが極めて不良な患者では脱⽔を来しやすく，尿細管に流⼊する多量のブ ドウ糖が尿細管障害をもたらすと考えられており，注意が必要である． ５）サルコペニア・フレイル 従来，SGLT2 阻害薬によるサルコペニア・フレイルが懸念されている．SGLT2 阻 害薬は，尿糖排出からカタボリズム亢進状態となり，脂肪量のみならず筋⾁量も減 少するとされているが 43, 44，筋⾁量は変化しないとの報告もあり 45，明確な結論は 出ていない．HFrEF を呈する⼼不全患者にダパグリフロジンを投与した DAPA- 11 HF 試験のサブ解析では，フレイルスコアが⾼い患者ほど，ダパグリフロジン投与 によりフレイルが改善していたとの報告もある 46．しかしながら，痩せている⾼齢 ⽇本⼈においては，SGLT2 阻害薬によるカタボリズム亢進がサルコペニア・フレ イルを助⻑する可能性があり，患者のリスクとベネフィットを勘案し，SGLT2 阻 害薬の投与を検討する． ６）下肢切断・⾻折 CANVAS program では，カナグリフロジン群において下肢切断・⾻折発症が有意 に増加した（下肢切断: HR 1.97 [95%CI 1.41‒2.75];⾻折: HR 1.26 [95% CI 1.04‒ 1.52]) 47．しかしながら，腎機能が低下した患者が多く含まれる CREDENCE 試 験では，カナグリフロジン群において下肢切断の有意な増加は認めていない（HR 1.11 [95%CI 0.79‒1.56]）35．⼀⽅，EMPA-KIDNEY 試験を含む SR では，⼼⾎管 リスク・動脈硬化を有する糖尿病患者において，SGLT2 阻害薬により下肢切断の リスクが増加することが⽰され（RR 1.15 [95% CI 1.02‒1.30] ，全患者における検 討においても同様にリスクが増加している（RR 1.15 [95% CI 1.02‒1.30]）．⾻折に 関しては CANVAS program 以降は，SGLT2 阻害薬により有意にリスクが増加し たという報告はなく，糖尿病⾮合併 CKD 患者においても，糖尿病合併 CKD 患者 と⽐較して⾻折は増加していない 9．SR においてもプラセボと⽐較しリスクは有 意ではなかった（RR 1.07 [95% CI 0.99‒1.14]）．以上より，⼼⾎管リスクを有する 糖尿病患者への SGLT2 阻害薬投与時には，下肢切断リスクに留意する必要がある． ⾻折については，転倒リスクが⾼い⾼齢者に投与する場合は注意が必要であるも のの，リスク増加については定かではない． ７）尿路・性器感染症 尿路・性器感染症の発症・増悪には⼗分注意する必要がある．近年の SR では，尿 路感染症，性器（真菌）感染症ともにリスクが増加することが報告されている（尿 路感染症：RR 1.08 [95% CI 1.02‒1.15]，性器感染症 RR 3.57 [95% CI 3.14‒4.06]） 14．また，腎機能にかかわらず性器感染症を増加させる傾向にある(eGFR<60: IRD 13.66 [95%CI 9.20‒18.13]，eGFR≧60: IRD 16.01 [95%CI -1.04‒33.06]) 3．DAPA- CKD 試験において，糖尿病⾮合併 CKD 患者と⽐較し糖尿病合併 CKD 患者では 性器感染症の発症が多かった．糖尿病患者では尿糖排泄量が多いこと，易感染傾向 であることなどが誘因として考えられる 9．糖尿病⾮合併 CKD 患者においても， 投与前に尿路感染症を伴う場合には，SGLT2 阻害薬の投与を控えることも検討す る． ８）多発性嚢胞腎，ループス腎炎，ANCA 関連⾎管炎，免疫抑制療法中の患者 12 糖尿病⾮合併 CKD 患者を含む DAPA-CKD 試験では，多発性嚢胞腎(PKD)，ル ープス腎炎，ANCA 関連⾎管炎，免疫抑制療法中の患者は除外されているが 1， EMPA-KIDNEY 試験 では PKD や 3 ヶ⽉以内の免疫抑制薬静脈内投与，45mg/ ⽇以上のプレドニゾロン投与患者は除外されているものの，ループス腎炎や ANCA 関連⾎管炎患者は含まれている可能性がある 2．これら疾患への SGLT2 阻 害薬の有⽤性については現時点では不明であり，今後のさらなる解析が必要であ る．腎移植を含め免疫抑制薬を服⽤している患者に対する投与は，感染症の懸念か ら投与を慎重に検討する．PKD モデルにおける検討では，腎保護効果を⽰す⼀⽅ 48，悪化するとの報告もあり 49，⼀定の⾒解は得られていない．クリニカルエビデ ンスをもとに SGLT2 阻害薬の適応を慎重に考慮すべきである． 謝辞 本 recommendation の作成に際し，⽇本糖尿病学会の皆様より多⼤なるご助⾔を賜りまし た．⼼より感謝申し上げます． 13 引⽤⽂献 1
```
不自然な改行はなくなりましたが、スプリッドが機能せずに参照する文章が多くなってしまいました。

この問題の解決方法をChatGPTに問い合わせしたら以下のような返答が返ってきました。

```
PDFから抽出したテキストが長すぎるため、それを適切に分割することが問題となっているようです。

現在のコードでは、テキストはピリオド（'.'）で終わる文章ごとに分割されています。しかし、これは必ずしも適切な分割方法とは限りません。特に、PDFの内容が長いパラグラフやセクションで構成されている場合、またはピリオドが文章の途中で使用されている場合（例えば、"Dr."や"U.S."など）には、この方法では適切に分割できない可能性があります。

この問題を解決するためには、テキストの分割方法を調整する必要があります。具体的には、以下のような方法が考えられます：

センテンス分割: テキストをセンテンス（文）ごとに分割する方法です。これには、自然言語処理（NLP）ライブラリの一部であるセンテンス分割ツールを使用します。例えば、nltkライブラリのsent_tokenize関数などがあります。

パラグラフ分割: テキストをパラグラフごとに分割する方法です。これは、特にテキストが明確なパラグラフ構造を持つ場合に有効です。具体的には、連続する改行（\n\n）をパラグラフの区切りと見なすことが一般的です。

固定長分割: テキストを一定の長さ（例えば、100文字や200文字など）の部分文字列に分割する方法です。これは、テキストの内容や構造に関係なく、一定の長さのテキストを生成することが必要な場合に有効です。

これらの方法のどれが最適かは、具体的な要件やPDFの内容によります。また、これらの方法を組み合わせることも可能です。例えば、まずテキストをパラグラフに分割し、次に各パラグラフをセンテンスに分割する、というように。
```
ということで全てやってみましたが、、、、全然ダメでした。。。

### ③（救世主現る）テキストの修正もChatGPTにお願いする。

①～②を試行錯誤して何時間も経過。心折れそうになったので友人のプログラマーにメール。
```
ChatGPTにお願いしたら？
```
と返答してはっと我に返る。

ということでChatGPTにお願いすることに。

ただしChatGPTにはトークンの上限があり30000文字に及ぶテキストデーターを一気に整形できない。

そのため以下の流れで整形をお願いしました。

①test.pdfからテキスト抽出
②テキストを6000文字ごとに分割
③一つずつchatgptに整形してもらう
④最後に整形した文字を結合して一つのテキストデーターにする
⑤このテキストデーターを行ごとに分割する

そして、出来上がったのは以下のコードになります。

```
import fitz  # PyMuPDF
import openai

def extract_text_from_pdf(file_path):
    doc = fitz.open(file_path)
    text = ""
    for page in doc:
        text += page.get_text()

    return text

def format_text_with_gpt3(text):
    openai.api_key = ''  # OpenAIのAPIキーを設定します。

    # GPT-3.5-turbo-16kを使用してテキストを整形
    response = openai.ChatCompletion.create(
      model="gpt-3.5-turbo-16k",
      messages=[
            {"role": "system", "content": "PDFから抽出されたテキストデーターには不自然な改行や空白、文字化けがあるため整形して日本語で読みやすくしてください。"},
            {"role": "user", "content": text},
        ]
    )

    return response['choices'][0]['message']['content']

def split_and_format_text(text, chunk_size):
    # テキストを指定された文字数ごとに分割
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]

    # 各チャンクを整形
    formatted_chunks = [format_text_with_gpt3(chunk) for chunk in chunks]

    # 整形したチャンクを結合
    return ''.join(formatted_chunks)

file_path = '/content/test3.pdf'  # ここにPDFファイルのパスを指定します
text = extract_text_from_pdf(file_path)
pretext = split_and_format_text(text, 6000)
database = pretext.splitlines()
```


### 次回は

ChatGPTすげぇ。。。ということで苦悩の共有は終了にします。

次回はURLからの読み取りを試みようと思います。

では