GPT-3.5をFine-tuningして架空のスポーツを教え込んでみた①

# 本記事でやること

- 公式ドキュメントの要点を整理
- 独自のデータセットを作成し、実際にFine-tuningをしてみる
    - ※「知識を教え込む」といった内容で試しています。
    - Fine-tuningの推奨ユースケースではないと言われています。

第2回では、データセットの見直しと改善をテーマに色々とまとめました。

https://qiita.com/sakue_103/items/03d79d1d7c7c565830c6

# 公式ドキュメントの要点まとめ

まずは以下公式ドキュメントの中で気になった点をピックアップしてまとめておきます。

https://platform.openai.com/docs/guides/fine-tuning

https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates

## Fine-tuningの位置付け・イメージ

昔の公式ドキュメントにFine-tuningとRetrieval(RAGなど)の違いが紹介されていました。
（今はこのような記載を見つけれらませんでした、あくまでイメージとして捉えていただければと思います）

|手法|イメージ|
|--|--|
|Fine-tuning|試験勉強をした後に試験を受けるイメージ。忘れる/記憶違いを起こすこともある。|
|Retrieval(RAGなど)|カンニングペーパーを持ち込んで試験を受けるイメージ。正しい情報を参照できれば絶対に間違えない。|

現在のドキュメントには以下のように記載されています。

- EmbeddingsとRetrieval
    - 大量のドキュメントデータベースが必要な場合に適している。
    - 関連するコンテキストや情報を持つ場合が最適。

- OpenAIモデルのデフォルトの特性
    - 幅広いアシスタントとしての役割を持つ
    - 一般的なタスクに役立つように訓練されている。


- Fine-tuningの利用
    - モデルを狭く特定の焦点に絞ることが可能。
    - 特定の行動パターンを持つようにする。

- Retrieval戦略の利用
    - 応答を生成する前の関連するコンテキストを提供。
    - 新しい情報をモデルに利用可能にする。

- Retrieval戦略とFine-tuningの関係
    - RetrievalはFine-tuningの代替手段ではない。
    - RetrievalはFine-tuningを補完するものとして利用できる。


>Embeddings with retrieval is best suited for cases when you need to have a large database of documents with relevant context and information.
By default OpenAI’s models are trained to be helpful generalist assistants. Fine-tuning can be used to make a model which is narrowly focused, and exhibits specific ingrained behavior patterns. Retrieval strategies can be used to make new information available to a model by providing it with relevant context before generating its response. Retrieval strategies are not an alternative to fine-tuning and can in fact be complementary to it.


（※以下は筆者の感想・理解です。公式ドキュメント記載の情報ではありません。）
「 **企業内情報に特化した生成AIシステムを作ろう** 」となった場合、以下3つの要素が大切と捉えました。

- [1] 企業内情報を活用したRetriaval Augumented Generationシステムの構築
    - ユーザのクエリに応じて、適切かつ最新の情報を取得できるようにする
- [2] 企業内情報を基にFine-tuning
    - 適切かつ最新の情報をプロンプトに含めつつ、企業内情報に特化したモデルを使用する
- [3] Few-shotなどのプロンプトエンジニアリング
    - ユースケースに応じて出力形式やトーンなどを調整する

## Fine-tuningを行う際のポイント

- ①推奨ユースケースは「出力形式のカスタム」「トーンのカスタム」「言語のカスタム」など
- ②学習データは50~100個用意しよう
- ③学習データは他のトレーニングには使用されない
- ④学習データはGPT4により安全性チェックが行われる

### ①推奨ユースケース

- 向上したコントロール
    - 出力を簡潔にしたり、特定の言語での応答を向上。
    - 例: ドイツ語での応答の強化。
- 信頼性のある出力フォーマット
    - 一貫した応答フォーマットの向上。
    - 応用例: コード補完、API呼び出し、JSONスニペットの生成。
- カスタムトーン
    - モデルの出力のトーンや質を磨き上げ。
    - ビジネスのブランドの声に合わせる調整。

>In our private beta, fine-tuning customers have been able to meaningfully improve model performance across common use cases, such as:
Improved steerability: Fine-tuning allows businesses to make the model follow instructions better, such as making outputs terse or always responding in a given language. For instance, developers can use fine-tuning to ensure that the model always responds in German when prompted to use that language.
Reliable output formatting: Fine-tuning improves the model's ability to consistently format responses—a crucial aspect for applications demanding a specific response format, such as code completion or composing API calls. A developer can use fine-tuning to more reliably convert user prompts into high-quality JSON snippets that can be used with their own systems.
Custom tone: Fine-tuning is a great way to hone the qualitative feel of the model output, such as its tone, so it better fits the voice of businesses’ brands. A business with a recognizable brand voice can use fine-tuning for the model to be more consistent with their tone.

### ②Fine-tuningする際のデータ数

- モデルをファインチューニングするには、少なくとも10の例が必要。
- gpt-3.5-turboで50から100のトレーニング例にファインチューニングすると明確な改善が見られることが多い。
- 適切な例の数は、具体的な使用ケースによって大きく異なる。

>To fine-tune a model, you are required to provide at least 10 examples. We typically see clear improvements from fine-tuning on 50 to 100 training examples with gpt-3.5-turbo but the right number varies greatly based on the exact use case.


### ③効果とデータ利用ポリシー

- 初期テストの結果
    - ファインチューニングされたGPT-3.5 Turboのバージョンは、特定の狭いタスクにおいて、ベースのGPT-4レベルの能力と同等、またはそれを上回ることができる。
- データの所有権と利用に関するポリシー
    - ファインチューニングAPIへの入出力データは顧客が所有。
    - OpenAIや他の組織は、そのデータを他のモデルのトレーニングには使用しない。


>Fine-tuning for GPT-3.5 Turbo is now available, with fine-tuning for GPT-4 coming this fall. This update gives developers the ability to customize models that perform better for their use cases and run these custom models at scale. Early tests have shown a fine-tuned version of GPT-3.5 Turbo can match, or even outperform, base GPT-4-level capabilities on certain narrow tasks. As with all our APIs, data sent in and out of the fine-tuning API is owned by the customer and is not used by OpenAI, or any other organization, to train other models.

### ④安全性チェック

- ファインチューニングのトレーニングデータは、Moderation APIを経由して処理される。
- GPT-4で動作するモデレーションシステムを使用して、OpenAIの安全基準と矛盾する安全でないトレーニングデータを検出。

>It is very important to us that the deployment of fine-tuning is safe. To preserve the default model's safety features through the  fine-tuning process, fine-tuning training data is passed through our Moderation API and a GPT-4 powered moderation system to detect unsafe training data that conflict with our safety standards.




# Fine-tuningの手順

今回、「架空のスポーツを教え込む」というケースでFine-tuningを行います。
（知識を覚えさせるといったユースケースはFine-tuningが得意とするものではないといわれていますが、試さないと納得できない性分なので自分なりに試していきます。）

以下はFine-tuningのおおまかな手順です。

- ①データを用意する
- ②データをアップロードする
- ③学習ジョブの作成
- ④Fine-tuning済みモデルの実行


## ①データの用意

データはjsonl形式で用意します。データの中身は、OpenAI APIのChatCompletionを呼び出す時の形式です。
（userとassistantの学習への影響はなんとなく想像しやすいですが、systemはどのように影響するんですかね、、）

```json: 公式ドキュメントの例
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]}
```

今回は、ChatGPTに学習用のデータを作らせます。
架空のスポーツ「ホバンヌ」を題材に、質問と回答の組み合わせを作成します。

まずは、gpt-35-turboがホバンヌを知らないことを確認します。

【ChatGPTで聞いてみる】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/ce000bf4-0532-2aec-7553-cdc93bba55d3.png)

【OpenAI Playgroundで聞いてみる】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/2d4375cf-a4e0-a353-283f-2048c63c11d1.png)

それっぽい回答を生成するパターンもありました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/2c3ffaa7-13b5-8d6e-5e0b-fe87c89a50cf.png)


では、ホバンヌを定義します。
ChatGPTと会話しつつ定義したところ、以下のようなスポーツとなりました。

```:ホバンヌの概要
ホバンヌの概要（最終改訂版）:

ホバンヌは、3人のチームによって行われるスポーツで、手だけを使ってプレイされます。以下が最終改訂されたルールと概要です。

チームと選手: 各チームは3人の選手で構成されています。
コートのサイズ: ホバンヌのコートは、3人のチームに適したサイズで設計されており、長さ約20メートル、幅約10メートル程度です。
ボールとゴール: 試合では2つのボールが使用されます。各チームは相手のゴールにボールを投げ込むことで得点を獲得します。
得点: ボールを相手チームのゴールに投げ込むごとに1点が得点として加算されます。
試合時間: 試合は2つのハーフで構成され、各ハーフの時間は10分です。
開始と再開: 試合はセンターサークルから開始され、得点後やボールがアウトになった場合はゴールラインから再開されます。
アウトとターンオーバー: ボールがプレイエリア外に出た場合や、相手の選手にボールを奪われた場合はターンオーバーとなり、相手チームのボールとなります。
反則: 手だけを使うルールに基づき、手以外の部位を使用してボールを操作することは反則となります。また、ボールを持っている間は片足で立つ必要があります。
審判: 試合は審判によって管理され、ルールの適用や得点の確認などを行います。
```

上記の定義から、質問と回答の組み合わせを生成します。

>ホバンヌの概要から質問と回答の組み合わせを15個作って、markdownのメモ帳に表形式で張り付けられるようにして。

以下のようなQAリストを生成できました。

| 質問                                                    | 回答                                                                 |
|-------------------------------------------------------|----------------------------------------------------------------------|
| ホバンヌは何人の選手で行われるスポーツですか？              | ホバンヌは3人のチームによって行われるスポーツです。                                        |
| ホバンヌのコートのサイズはどのくらいですか？                | ホバンヌのコートは、長さ約20メートル、幅約10メートル程度です。                          |
| 一つの試合で何個のボールが使用されますか？                 | 試合では2つのボールが使用されます。                                                    |
| ホバンヌでの得点の方法は何ですか？                         | ボールを相手チームのゴールに投げ込むごとに1点が得点として加算されます。            |
| 一つの試合の時間はどのくらいですか？                       | 試合は2つのハーフで構成され、各ハーフの時間は10分です。                                |
| 試合の開始と再開はどの位置から行われますか？                | 試合はセンターサークルから開始され、得点後やボールがアウトになった場合はゴールラインから再開されます。|
| ボールがアウトになった場合、どのチームがボールを持つことになりますか？ | ターンオーバーとなり、相手チームのボールとなります。                                       |
| ホバンヌの反則にはどのようなものがありますか？              | 手以外の部位を使用してボールを操作することや、ボールを持っている間は片足で立たなければならないことなどが反則となります。|
| 試合はどのように管理されますか？                           | 試合は審判によって管理され、ルールの適用や得点の確認などを行います。                         |
| ホバンヌでボールを持つときの注意点は何ですか？              | ボールを持っている間は片足で立つ必要があります。                                          |
| ホバンヌの試合で反則を犯すとどうなりますか？                | 反則を犯すと、相手チームにペナルティやボールの権利が与えられることが考えられます（具体的なペナルティの詳細は文中に記載されていない）。|
| ホバンヌでの得点はどのくらいの頻度で行われますか？          | 得点はボールを相手チームのゴールに投げ込むごとに行われます。                              |
| ホバンヌのプレイエリア外にボールが出た場合、どうなりますか？   | ボールがプレイエリア外に出た場合はターンオーバーとなります。                               |
| ホバンヌでボールを操作する際の基本的なルールは何ですか？     | ボールは手だけを使って操作することが基本的なルールです。                                   |
| ホバンヌの選手は試合中にどのような動きを取ることが多いですか？  | ボールを持つ、投げる、守る、相手のボールを奪うなどの動きが考えられます（具体的な動きの詳細は文中に記載されていない）。|




このQAリストをjsonl形式に変換します。これもChatGPTにお願いしましょう。

>各行を以下のような形式に直してほしい。
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "質問1"}, {"role": "assitant", "content": "回答1"}]}
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "質問2"}, {"role": "assitant", "content": "回答2"}]}

最終的に以下のようなjsonl形式のファイルを生成することができました。（間は省略しています）

```json:
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "ホバンヌは何人の選手で行われるスポーツですか？"}, {"role": "assistant", "content": "ホバンヌは3人のチームによって行われるスポーツです。"}]}
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "ホバンヌのコートのサイズはどのくらいですか？"}, {"role": "assistant", "content": "ホバンヌのコートは、長さ約20メートル、幅約10メートル程度です。"}]}
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "一つの試合で何個のボールが使用されますか？"}, {"role": "assistant", "content": "試合では2つのボールが使用されます。"}]}
...
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "ホバンヌでボールを操作する際の基本的なルールは何ですか？"}, {"role": "assistant", "content": "ボールは手だけを使って操作することが基本的なルールです。"}]}
{"messages": [{"role": "system", "content": "you are factual chatbot."}, {"role": "user", "content": "ホバンヌの選手は試合中にどのような動きを取ることが多いですか？"}, {"role": "assistant", "content": "ボールを持つ、投げる、守る、相手のボールを奪うなどの動きが考えられます（具体的な動きの詳細は文中に記載されていない）。"}]}

```

これでデータの準備は完了です。


## ②データのアップロード

Pythonライブラリ```openai```を使用して、さきほど作成したjsonlファイルをアップロードします。

```py:ファイルのアップロード
# 学習用データのアップロード
file_path = "hobannu.jsonl"
openai.File.create(
  file=open(file_path, "rb"),
  purpose='fine-tune'
)
```

色々と情報が出力されます。uploadedとなっているのでよさそうです。
idの値(file-***)は以降の処理で使用するので控えておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/abaf0729-d0cf-64a4-ae61-ee30a30c3c2a.png)



## ③学習ジョブの作成

アップロードしたファイルを使って学習を行うためのジョブを作成します。

```py:
openai.FineTuningJob.create(
    training_file="file-***",
    model="gpt-3.5-turbo",
)
```


以下のような出力がされます。ステータスを確認するとcreatedとなっているのでよさそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/14f112a5-132e-bf9c-92d5-a7f532c47af3.png)


Fine-tuningが終わると、OpenAIからメールが届きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/742945d5-5de3-82fa-5b98-38d9878d4128.png)


## ④Fine-tuning済みモデルの実行

メールに記載されているリンクからPlaygroundにアクセスします。
画面右側をみると、Fine-tuningされたモデルが選択されていることが分かります。(```ft:<base_model>:<account_type>::<id>```)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/a4be6a06-5a0a-5579-c329-5e83975fee6e.png)

Playgroundから、質問を投げかけてみます。
（以降はどちらかと言うと期待に近い生成をしてくれた例です。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/8ccdf1c5-f951-4797-cbcf-0384704eff27.png)
一応間違えてはいません。ただ、GPTお得意の当たり障りのない回答な気がするので色々と追加で質問してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/5643ae59-6cdc-a229-cfd0-7617343ecfd6.png)
おお、合ってそうですね。もう少し深掘り質問します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e8fd63a2-61bc-63c1-ff55-b75fe37c5465.png)
うーん、60点くらいな回答ですね。少しニュアンスが違う感じがします。
さらに深掘り質問をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/46c021ab-a860-1a4f-8e93-c64de352b215.png)
これは10点くらいですね。ホバンヌとはかけ離れた回答も含まれています。
（ホバンヌはボールを手で持ちます。）

Pythonライブラリから実行する場合は以下のようなコードで実行できます。

```py:
# メールに記載のモデルIDを指定
model_param = {
    "model": "ft:gpt-3.5-turbo-0613:personal::aaaAA111"
}

# メッセージを初期化
messages = [
    {"role": "system", "content": "You are factual chatbot."},
    {"role": "user", "content": "ホバンヌってどんなスポーツ？"},
]

# チャットの実行
response = openai.ChatCompletion.create(
    messages=messages,
    temperature=0,
    top_p=1,
    max_tokens=200,
    **model_param,
)

response["choices"][0]["message"]["content"]
```

以上がFine-tuningの手順です。単純にFine-tuningするだけであればとても簡単な手順ですよね。
ただ現状、学習データの質や量、情報の与え方などベストプラクティスが不明です。そのため、次回以降では色々と試して精度がどのように変わるのかを見てみます。


# Fine-tuningしたモデルへの質問と回答まとめ

色々と質問を投げかけてみた感触としては「 **精度は悪い** 」ですね。
精度向上のためのプラクティスがいくつか公式ドキュメントに記載されていたため、次回はそれを試そうと思います。

:frowning2: (ボールを2つ使うチームスポーツです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e723f613-9214-cecd-0f6d-57aa37d5d5c3.png)


:frowning2: (ボールの名前は定義していません)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/56338d4f-3a43-ac59-e6e0-61f1e5180cfe.png)

:ok_hand: (定義通りです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/21847205-1f21-7647-0378-1d5ff5c7e596.png)


:frowning2: (ボールを2つ使うチームスポーツです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/d151afe1-3002-4f2e-1650-914934788010.png)

:frowning2: (20×10です)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/8c015a9e-e89a-8a31-8d62-8d27011bb41d.png)

:frowning2: (ホバンヌ含まれず)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/6596e56e-bf0b-f6bb-3b35-e96c1b91b7fb.png)


# 所感・まとめ

「Fine-tuningは知識を教え込むのには向いていない」という見解が多いですがどうなんでしょうね？
データの作り方やプロンプトによってはいけそうな気もしますが、この辺りの検証・研究の情報を収集していこうと思います。


以下は今回手を動かしてなんとなく感じたことです。

- Temperature=0, top_p=1であっても、評価するときは何度か同じデータ(入力)で試した方がよさそう。
    - 入力が同じでも全く同じ回答をしてくれるわけではないため。


- 以下のようなリスクがあるが、Fine-tuningで知識を教え込むのはいけそう？
    - 一般化の問題:
        - 過度に特定の情報を埋め込むと、その情報にのみ過度に依存するモデルができ上がる恐れがあります。その結果、他の情報やコンテキストでの適切な回答が難しくなる可能性があります。
    - 柔軟性の欠如:
        - 一度埋め込んだ情報を変更や更新するのが難しくなる場合があります。


「ドメイン特化のFine-tuningモデルを複数用意して、どのモデルに聞いたらいいのかをFunction-callingなどで判断する」といった仕組みもありかなと思いました。次々回以降実装してみよう。

次回は、公式ドキュメントに記載のあるプラクティスに従ってFine-tuningを何通りか試し、精度をPrompt Flowで評価しようと思います。
学習データを変更するなどした時に、精度がどのように変わるのか？を検証していきます。

OpenAIが開発している評価ツールを使うのもよいかもしれません。

https://github.com/openai/evals