RAGは検索しているに過ぎず、学習しているとは言えないと思う

## 概要
OpenAI Assistants APIのRetrieval機能は添付ファイルを検索して質問に回答できますが、添付ファイルの知識を組み合わせたような回答はできません。

## はじめに
RAG（Retrieval-Augmented Generation、検索拡張生成）[^rag]は、Fine-Tuningなど言語モデルの学習手段の代替として語られる事がたまにあります。ですが実際には、RAGとはあくまで外部のデータソースを「検索」しているの過ぎず、「学習」しているとまでは言えないのではないかと考えています。

[^rag]: Retrieval-augmented generation for knowledge-intensive NLP tasks https://dl.acm.org/doi/abs/10.5555/3495724.3496517

そこで本稿では、簡単なタスクを通してRAGで対応できないケースを見ていきます。
これをちゃんと論じるには「検索」と「学習」の定義を明確にする必要があると思います。が、そういうのはちゃんとした文献に任せて、本稿では「データを学習をしていれば解けるけど、RAGでは対応できなさそうなタスク」を使って実験していきます。
ざっくり「RAGで何でも簡単にできるわけではない」という事が言えればいいかなと思います。

## 実験
### タスクの概要
OpenAI ChatGPTは[ポケモン「ハバタクカミ」について正しく答えられない](https://qiita.com/suzuki_sh/items/43977bfa1d7c0ca59ff0)ことが分かっています。そこで、RAGと、RAGではない手法とを使って、それぞれ「ハバタクカミの対策」について答えさせてみます。

具体的には、まず以下のふたつのドキュメントを用意します。

- 「ハバタクカミは、はがねタイプの先制技に弱い」と書いてあるドキュメント
- 「ハッサムは、バレットパンチというはがねタイプの先制技を覚える」と書いてあるドキュメント

これらのドキュメントを「学習」したならば、「ハバタクカミの対策を教えて」と聞かれて「ハッサムのバレットパンチ」[^flutter-mane-bullet-punch]と推論できると思います。

[^flutter-mane-bullet-punch]: 一般的なポケモン対戦においてハバタクカミの対策は非常に難しく、ハッサムが対策にならない事もあります。ですが、本稿では「ハバタクカミの対策はハッサムのバレットパンチである」と仮定して話を進めます。

ハバタクカミ、ハッサム、もうひとつダミーとしてセグレイブについて説明する、以下3つのテキストファイルを用意しました。なおテキストファイルは筆者が本タスクのために執筆したものであり、一般的なポケモンの知識とは一部異なる場合があります。

```:ハバタクカミの知識.txt
## ハバタクカミ
ハバタクカミはゴースト・フェアリータイプの強力なポケモンです。
すばやさととくこうが高く、ムーンフォースやシャドーボールをはじめ強力なわざを高いすばやさで上から打ってきます。
マジカルフレイム、10まんボルト、パワージェム、エナジーボールなど様々な特殊技を習得し広い範囲を持っています。
また特殊受けに対してもサイコショックがあります。
でんじはやちょうはつといった補助技も備えています。
ハバタクカミの弱点はぼうぎょが低いことです。
はがねタイプの物理の先制技で大きなダメージを与えることができます。
とつげきチョッキを持たせたポケモンで特殊攻撃を封じることもできます。
```

```:セグレイブの知識.txt
## セグレイブ
セグレイブはこおり・ドラゴンタイプの強力なポケモンです。
600族と呼ばれる高いステータスを持っています。
こうげきが非常に高く、つららばりやきょけんとつげきといった強力なわざを習得します。
対処が遅れるとりゅうのまいやつるぎのまいでステータスを上げられてしまい、手がつけられなくなります。
ドラゴンタイプのため、すばやさの高いフェアリータイプは苦手としています。
```

```:ハッサムの知識.txt
## ハッサム
ハッサムはむし・はがねタイプの強力なポケモンです。
豊富な耐性を持ち、こうげきが高く、高い火力で攻撃してきます。
はがねタイプの先制技であるバレットパンチを、特性テクニシャンで強化して放つことができます。
これにより、ぼうぎょの低いフェアリーやこおりタイプに対して有利になります。
```

### ソースコードの実行環境
- Google Colaboratory（Colab）
- Python 3.10.12
- OpenAI Version: 1.13.3

### 検証方法と仮説
本稿ではすべてOpenAI APIを用いて実験します。
検証方法は単純で、「ハバタクカミの対策を教えて」というプロンプトに対し、「はがね」「先制」「ハッサム」「バレットパンチ」という語がどれだけ出てくるかを数えます。すべて出てきたら一番良い生成結果とします。

#### ベースライン
まずベースラインとして、`gpt-3.5-turbo`および`gpt-4-1106-preview`をそのまま使い、「ハバタクカミの対策を教えて」と指示します。

テキストデータ無しには「ハバタクカミ」がそもそも「ポケモンの一種である」と分からないはずなので、「はがねタイプ」とか「先制技」というポケモン用語は全然出てこないはずです。この実験によって、以降のRAG等による回答が、言語モデルが内部に持っている知識ではなく外部から与えられた知識によるものであるという事を確認します。

コードにすると下記のようになります。

```python
def chat(prompt, max_tokens=256, model_name="gpt-3.5-turbo"):
    chat_completion = openai_client.with_options(max_retries=5).chat.completions.create(
        messages=[{"role": "user","content": prompt}],
        model=model_name,
        max_tokens=max_tokens,
    )
    return chat_completion.choices[0].message.content

print(chat("ハバタクカミの対策を教えて"))

print(chat("ハバタクカミの対策を教えて", model_name="gpt-4-1106-preview"))
```

#### RAG
OpenAI Assistants APIを使って、Retrievalとして先に定義した3つのファイルを添付します。モデルは`gpt-4-1106-preview`とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/98f4fb1c-7dbb-8497-d42e-e26dba58d3dc.png)

後述の実験対象と比較するために、3つのテキストファイルを別々に添付したものと、3ファイルをひとつに連結したテキストファイルを添付したものと、ふたつのアシスタントを用意します。

RAGはドキュメントを検索をしているに過ぎないので、「ハバタクカミの対策を教えて」という指示では「はがねタイプの先制技」とまでしか回答できず、「ハッサムのバレットパンチ」とは出てこないはずです。

```python
def chat_assistant(prompt: str, assistant_id: str, verbose=0):
    thread = openai_client.with_options(max_retries=5).beta.threads.create()
    _message = openai_client.with_options(max_retries=5).beta.threads.messages.create(
        thread_id=thread.id,
        role="user",
        content=prompt
    )
    run = openai_client.with_options(max_retries=5).beta.threads.runs.create(
        thread_id=thread.id,
        assistant_id=assistant_id,
    )
    while run.status != 'completed':
        time.sleep(5)
        run = openai_client.with_options(max_retries=5).beta.threads.runs.retrieve(
            thread_id=thread.id,
            run_id=run.id
        )
        if verbose >= 1:
            print(run.status)
    messages = openai_client.with_options(max_retries=5).beta.threads.messages.list(
       thread_id=thread.id
    )
    return messages.data[0].content[0].text.value
```

#### システムプロンプト利用
テキストを「検索」するRAGとの比較対象として、テキストを「学習」させて、「ハッサムのバレットパンチ」と回答させることを目指します。学習ということで本来はFine-Tuningとか何かしたいのですが大変なので、ここではただドキュメントを連結してシステムプロンプトに放り込み、「以下の知識を元に質問に回答して」とした文字列を与えて指示してみようと思います。
これが「学習」に相当する手法として機能すると主張して良いのかどうかは知りませんが、本実験で期待する動作はしてくれます。実用を考えた上でも、RAGより簡易で強力な手法であるため比較対象としては適切ではないかと思います。

文章で説明するとややこしいですが実際のコードは次のようになります。簡単ですね。

```python
FLUTTER_MANE_PROMPT = """以下のポケモンの知識をもとに回答してください。

# ポケモンの知識
## ハバタクカミ
ハバタクカミはゴースト・フェアリータイプの強力なポケモンです。
すばやさととくこうが高く、ムーンフォースやシャドーボールをはじめ強力なわざを高いすばやさで上から打ってきます。
マジカルフレイム、10まんボルト、パワージェム、エナジーボールなど様々な特殊技を習得し広い範囲を持っています。
また特殊受けに対してもサイコショックがあります。
でんじはやちょうはつといった補助技も備えています。
ハバタクカミの弱点はぼうぎょが低いことです。
はがねタイプの物理の先制技で大きなダメージを与えることができます。
とつげきチョッキを持たせたポケモンで特殊攻撃を封じることもできます。

## セグレイブ
セグレイブはこおり・ドラゴンタイプの強力なポケモンです。
600族と呼ばれる高いステータスを持っています。
こうげきが非常に高く、つららばりやきょけんとつげきといった強力なわざを習得します。
対処が遅れるとりゅうのまいやつるぎのまいでステータスを上げられてしまい、手がつけられなくなります。
ドラゴンタイプのため、すばやさの高いフェアリータイプは苦手としています。

## ハッサム
ハッサムはむし・はがねタイプの強力なポケモンです。
豊富な耐性を持ち、こうげきが高く、高い火力で攻撃してきます。
はがねタイプの先制技であるバレットパンチを、特性テクニシャンで強化して放つことができます。
これにより、ぼうぎょの低いフェアリーやこおりタイプに対して有利になります。
"""

def chat_with_system(system_prompt: str, prompt: str, max_tokens=256, model_name="gpt-3.5-turbo"):
    chat_completion = openai_client.with_options(max_retries=5).chat.completions.create(
        messages=[{"role": "system","content": system_prompt}, {"role": "user","content": prompt}],
        model=model_name,
        max_tokens=max_tokens,
    )
    return chat_completion.choices[0].message.content

print(chat_with_system(FLUTTER_MANE_PROMPT, "ハバタクカミの対策を教えて"))
```

## 結果
上記の実験をそれぞれ10回行い、キーワードが登場する回数を確認しました。

|  | はがね | 物理 | 先制 | ハッサム | バレットパンチ |
|:--|--:|--:|--:|--:|--:|
| gpt-3.5-turbo 単独 | 0 | 0 | 0 | 0 | 0 |
| gpt-4-1106-preview 単独 | 0 | 2 | 0 | 0 | 0 |
| gpt-4-1106-preview RAG（3ファイル別々） | 10 | 10 | 10 | 0 | 0 |
| gpt-4-1106-preview RAG（連結） | 9 | 10 | 10 | 0 | 0 |
| gpt-4-1106-preview + システムプロンプト利用 | 10 | 10 | 10 | 10 | 10 |

仮説通り、RAGでは「ハバタクカミの対策を教えて」に対して「はがねタイプ」「先制技」とまでしか回答できず、システムプロンプト利用した時のみ「ハッサムのバレットパンチ」を出力できました。

### 生成例
#### 生成例「gpt-4-1106-preview 単独」
>「ハバタクカミ」という言葉が指し示すものが明確でないため、正確な対策を提供することは難しいですが、「羽ばたく神」または「飛ぶ紙」などの意味合いであるかもしれません。もし物理的な「飛ぶ紙」のようなものに対処したい状況を想定しているのであれば、紙が飛ばないようにするための一般的な対策をいくつか紹介いたします。
>（以下略）

「物理」という語がたまに出現しますが、「カミ」から「電子書類か物理的な紙か」という話をしているだけであり、ポケモンの「物理技」の話になっていません。仮説通り、GPT-4は単独ではハバタクカミの知識を持っていません。

#### 生成例「gpt-4-1106-preview RAG（3ファイル別々）」
>ハバタクカミに対する対策として、以下の点が挙げられます：
>- ハバタクカミの弱点は物理防御（ぼうぎょ）が低いことであるため、はがねタイプの物理攻撃、特に先制技を使って大きなダメージを与えることが効果的です。
>- 特殊攻撃に対しては、とつげきチョッキを持たせたポケモンを使用することでハバタクカミの特殊攻撃を封じることができます。

ハバタクカミの対策として、ハバタクカミのテキストに書いてある「はがねタイプ」「先制技」しか述べられておらず、ハッサムのバレットパンチに対する言及はありません。

#### 生成例「gpt-4-1106-preview + システムプロンプト利用」
>ハバタクカミはゴースト・フェアリータイプで、すばやさととくこうが高く、多様な特殊攻撃を使ってくるため、対策を考える際はその弱点を突くことが重要です。以下はハバタクカミへの対策のいくつかのポイントです。
>1. **物理攻撃**: ハバタクカミはぼうぎょが低いため、物理攻撃でダメージを与えやすいです。特にはがねタイプやどくタイプの物理わざを持つポケモンが有効です。
>2. **先制技**: はがねタイプの先制技、例えばハッサムのバレットパンチはハバタクカミが苦手なはがねタイプである上に優れた火力を持つため、特に有効です。ハバタクカミを倒す前にダメージを与えることができます。
>（以下略）

## 結論
というわけで、仮説通りRAGではドキュメントを検索しているに過ぎず、ドキュメントを学習しているとは言い難い結果になりました。昨今LLMのプロンプトに使えるコンテキストサイズも非常に大きくなってきているので、少量のテキストであれば本実験のようにRAGせずすべてプロンプトに入力するほうが精度が良くなると思います。

もちろん本タスクは、RAGで生成した結果をもとに新たな指示を与えれば簡単に解くことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50391/df10af12-0111-0059-d963-27214a5c696b.png)

Retrievalの結果をもとに自動で考えるエージェントとか作ったり、[HyDE](https://arxiv.org/abs/2212.10496)のようなクエリを工夫する手法を適用することで、本タスクは自動で解けるようになると思います。

誰か自然言語推論とかと絡めてちゃんとしたデータセットで示してほしいです。