チャットボットを作る前に、ML-Readableな文書をつくらないか？

## 結論（RAGするならテキストはこうしておきましょう）

機械学習（特にテキスト処理モデル）での解釈が容易な文書をここではML-Readableな文書と呼ぶことにし、それの特徴は次のとおりだと考えました。

- 文章がきれいに取得できること
- 余計な文字が入っていないこと
- 上から下に自然に読めること
- 文字で完全に説明されていること
- 誤った情報が部分的に見ても一切記載されていない

これらは文章同士の意味を比較して検索する手法（Embedding Modelとか）を想定しています。

本気でDX化を考えるのであれば、文書はMarkdownで保管、それをもとにPPTX、PDF作成が一番いいんじゃないかなぁ。


## まえおき
皆さんルンバ使っていますか？僕は使っていないです。
ルンバを動かすにはある程度の床の綺麗さが必要で、これがRoombabilityと呼ばれているとかいないとか。
つまりは、自動化するためには機械にお膳立てをする必要があるということです。AIに限った話ではないですね。
これはAGI候補のLLMでも同じことです。

## LLMが文書を読む時代
LLMは高度なテキスト処理が可能です。AI活用得意ニキは長ったらしいテキストをLLMに投入して、咀嚼してもらった情報を美味しく頂いていることでしょう。
この能力はin-context learningと呼ばれるLLMの特性を利用しています。
LLMがもともと持っていない情報でも、入力プロンプトに含まれている情報を素早く解釈しそれっぽい回答をしてくれるのです。
しかしこれには制限があり、LLMはコンテキスト窓を超える入出力を行えません。
例えば、gpt-3.5-turboは入出力合わせて4096トークンが上限です。
つまり、超長文が読めない子なのです。そのため、データベースと連携した手法が流行しています。

https://platform.openai.com/docs/models

## RAG
LLMのテクニックとしてRAG(retrieval augmented generation)があります。
RAGの詳しい説明は別の情報ソースに委ねるとして、ざっくりいえば質問内容と関係のあるデータを持ってきて回答に利用する手法です。これなら5000兆文字...は流石に無理でも、コンテキスト窓を超える文書量にアクセスできます。

https://speakerdeck.com/smiyawaki0820/retrieval-based-lm-rag-system-zatukurili-jie-suru


```mermaid
sequenceDiagram
    participant User as 人間
    participant Chatbot as チャットボット
    participant DB as 文書DB
    participant LLM as LLM

    User->>Chatbot: 質問する
    Chatbot->>DB: 関連文書を検索
    DB-->>Chatbot: ヒットしたテキストを返す
    Chatbot->>LLM: プロンプトによる質問生成
    LLM-->>Chatbot: 回答を生成
    Chatbot-->>User: 回答をレスポンス

```

LLMは検索によって渡された情報をもとにしていい感じの回答を生成すればいいです。
つまり、このシステムは検索結果と取得できたドキュメントの品質にかかっています。

## RAGのR
ぶっちゃけなんでもいいですが、Embedding Modelの出力で検索する方法が人気です。
深層学習で得られた符号化器は文章を意味的に捉えた出力をします。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1977360/0a606ade-a997-f369-8d53-97c6e2e657d3.png)

https://openai.com/blog/new-and-improved-embedding-model

つまり、検索するための前処理で要求されるものは「文書」、ではなく「文章」です。

クエリについても文章である必要があります。DBに対して文章でクエリ（正確には文章の埋め込み表現）する必要があります。その際、DBの内容と質問の関連が薄い場合があります。
最良のパフォーマンスを出すにはDBに入れる文章を精査すること、どうしても手作業が難しければLLMの力を借りることになるでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1977360/90f20c89-d367-fe86-2a08-b03917a4c9c3.png)

https://dev.classmethod.jp/articles/problem-and-improve-methods-of-vector-search/

ベクトルDBでは多くの場合メタデータを登録できます。
なので、文章が検索できればその文章が存在するファイル、位置、その他付随する情報は自由に付与できます。
実際には埋め込みに使用した文章をLLMに使用するのではなく、周辺の文章を使用することが多いと思います。

## RAGとRAGのAG
LLMに丸投げすればOK


## LLMにやさしくない現実を見てみよう

こちらの記事より

https://zenn.dev/sompojapan_dx/articles/eb755a18e893ce

> 当社においては保険商品の規約等がハンドブックとしてPDFで作成されています。下はその一例ですが、テキストだけでなく図や表、注釈等もふんだんに使われておりどのようにLLMにドキュメントを与えるかは工夫の余地があることが分かるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1977360/b53aba97-d60a-810e-3b03-31cb1e0bce5e.png)

これこそML-Readabilityが低いPDFです。実際にテキスト抽出をやってみたくはあるのですが、元データが手に入らないのでqiitaのPDFで代用します。

https://cdn.qiita.com/assets/public/ads/qiita_ads_201908-10_v2-180ad74a5e02dbdf69d00d607ae87f50.pdf

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1977360/d1eade17-3012-c212-80d7-fee66b408391.png)

このPDFをテキスト化してみます。

```py
# code by GPT-4
import fitz

def pdf_to_text(pdf_path):

    doc = fitz.open(pdf_path)
    text_content = []

    for page_num in range(len(doc)):
        page = doc.load_page(page_num)

        # テキストの抽出
        text = page.get_text()
        text_content.append(text)

    doc.close()
    return "\n".join(text_content)


pdf_path = 'qiita.pdf'
text_and_images = pdf_to_text(pdf_path)
print(text_and_images)
```
```bash
python3 main.py > out.md
```

```out.md
 
Qiita Media Guide 
2019.8-10 
Increments株式会社 
 

Qiita 媒体概要 
2

©2019 Increments Inc.
Qiitaとは 
3
●
プログラミングに関する知識を記録・共有するためのサービス 
 
※2019年8月時点 
440,000件を超える記事が蓄積 

©2019 Increments Inc.
メディア規模 
●
国内プログラマほぼ100％が利用するサービスに成長 
 
4
712万UU/5563万PV 2019年6月（月間） 

©2019 Increments Inc.
流入経路 
●
行き詰った時に解決方法を検索/Qiitaの記事にたどり着き、参照 
5
約90％が検索での流入 

©2019 Increments Inc.
ユーザー属性（全体） 
●
プログラマ以外のプログラミング利用者にもリーチ可能 
```
単純にテキストを抽出すると
- 句点で区切られていないので文章を取得できない
- 改行混入しまくり
- 不要な文字混ざりまくり

そして、この比較的きれいな表がどうなったかというと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1977360/342591ea-dc08-ec9e-ab7a-ffad2820d882.png)

```out.md
セグメント指定 
19
トライアル価格となります。  
在庫が足りない場合は関連タグへの配信をする場合がございますことをご了承ください。
 
 
例）非ログインユーザー向け広告（PC）①に、記事タグを指定した場合のimp単価：1円+0.2円=1.2円　
 
名称 
面/人 
ログイン/ 
非ログイン 
詳細 
値 
imp 
価格 
記事タグ 
面 
両面 
閲覧者が閲覧している記事についてい
るタグ 
自由（qiitaに存在するタグ全て使用可。複数組
み合わせも可） 
0.2円 
記事投稿数 
人 
ログインのみ 
閲覧者の記事投稿数 
0,1-5,6-10,11以上 
0.5円 
Contribution数 
人 
ログインのみ 
閲覧者のcontribution数 
0,1-10,11-100,101-1000,1001以上 
0.5円 
フォロータグ 
人 
ログインのみ 
閲覧者がフォローしているタグ 
自由（qiitaに存在するタグ全て使用可。複数組
み合わせも可） 
0.4円 
GitHub 
人 
ログインのみ 
閲覧者がGitHubの認証を行なっている
かどうか 
行っている,行っていない 
0.2円 
Organization 
人 
ログインのみ 
閲覧者が所属するorg 
自由（qiitaに存在するorg全て使用可。複数組み
合わせも可） 
1円 
投稿タグ 
人 
ログインのみ 
閲覧者が投稿した記事についているタ
グ 
自由（qiitaに存在するタグ全て使用可。複数組
み合わせも可） 
0.7円 
アカウント登録期間 
人 
ログインのみ 
閲覧者がQiitaアカウントを登録してから
の期間 
半年未満, 半年以上1年未満, 1年以上3年未満, 
3年以上5年未満, 5年以上 
0.5円 

```

スライドタイトル -> 表の下の文章 -> 表の内容
となり、順番がめちゃくちゃ、表の体裁を保たないという最悪な状況。

スライドはシンプルで見やすいのに...どうして...（なのでエンジニアとそれ以外で喧嘩が発生する）


## じゃあどうなってればいいんだ

ベクトルDBには文章（チャンク）で、LLMにはチャンク周辺のテキストが使用される前提であれば、次のことが言えると思います。


- 文章がきれいに取得できること
    - 句点とか、改行で区切れると嬉しいね
- 余計な文字が入っていないこと
    - ノイズになるね
- 上から下に自然に読めること
    - 検索後にLLMが困ってしまう
- 文字で完全に説明されていること
    - 表であればマークダウンとかHTMLとかで代替。図はaltを活用
- 誤った情報が一切記載されていない
    - LLMはコンテキスト窓が決まっている+Embeddingする場合はチャンクで切られるので、前後をカットされても意味が矛盾してはならない



## おまけ

霞が関パワポは人間にも優しくない。

https://www.env.go.jp/content/900513416.pdf


https://note.com/kayaka08/n/n5516e5d47235