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

# 前回の内容と本記事で行うこと

前回の続きとして、「 **精度を向上させるにはどうすればよいか？** 」を試していきます。

https://qiita.com/sakue_103/items/c71e65808cb92356508c

本記事では以下に取り組みます。

- ①公式ドキュメントを基に、Fine-tuningの要点を整理
- ②実際にFine-tuningし、結果を評価
- ③公式ドキュメントと評価結果を基に、データセットやハイパーパラメータの調整



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

前回は「 **知識を教え込むというユースケースでFine-tuningを試す** 」ということに焦点を当てていましたが、
今回は、「 **精度を向上させるにはどうすればよいか？** 」に焦点を当てます。

- ① まずは50個データを用意し、精度向上が見られるか試そう
- ② 訓練とテストにデータを分割しよう
- ③ 訓練過程で計算される4つの指標
- ④【データの質】期待通りの結果を得られない時に調整すべき点
- ⑤【データの量】期待通りの結果を得られない時に調整すべき点
- ⑥ ハイパーパラメータ(エポック数)の調整

## ①まずは50個データを用意し、精度向上が見られるか試そう

- 最初に、良質なデータを50件作る


- ファインチューニング後にモデルが改善の兆候を見せるか確認。
    - 明確な改善が見られれば、更なるデータの追加でモデルの改善が期待できる。
    - 実用段階の品質に達していなくても、この段階の改善は良いサイン。


- 改善が見られない場合
    - モデルのタスクの設定方法を再考する。
    - データの構造を見直す。


>We recommend starting with 50 well-crafted demonstrations and seeing if the model shows signs of improvement after fine-tuning. In some cases that may be sufficient, but even if the model is not yet production quality, clear improvements are a good sign that providing more data will continue to improve the model. No improvement suggests that you may need to rethink how to set up the task for the model or restructure the data before scaling beyond a limited example set.

## ②訓練とテストにデータを分割しよう

- データセットの作成後、訓練用とテスト用に分割することを推奨


- 訓練用/テスト用ファイルを用いてFine-tuningジョブを作成した場合
    - 学習過程で統計を提供してくれる
    - これらの統計はモデルの改善度を示す最初の指標となる
    - モデルがどの程度改善されているか分かる


- テスト用データがある場合、学習後にモデル評価が可能になる


>After collecting the initial dataset, we recommend splitting it into a training and test portion. When submitting a fine-tuning job with both training and test files, we will provide statistics on both during the course of training. These statistics will be your initial signal of how much the model is improving. Additionally, constructing a test set early on will be useful in making sure you are able to evaluate the model after training, by generating samples on the test set. 


## ③訓練過程で計算される4つの指標

- 訓練の過程で計算される指標
    - 訓練の損失 (training loss)
    - 訓練のトークン精度 (training token accuracy)
    - テストの損失 (test loss)
    - テストのトークン精度 (test token accuracy)


- これらの統計は、訓練がスムーズに進行したかの確認に使える
    - 損失は減少すべき。
    - トークンの精度は増加すべき。


>We provide the following training metrics computed over the course of training: training loss, training token accuracy, test loss, and test token accuracy. These statistics are meant to provide a sanity check that training went smoothly (loss should decrease, token accuracy should increase).



## ④【データの質】調整すべき点

**1.問題点を解決するための例を追加**
モデルが特定の部分について答えられない場合、具体的な例を追加。

**2.現在のデータをチェック**
モデルに文法、論理、スタイルの問題がある場合、その原因となるデータがないか確認。
例として、モデルが「この会議を予定します」と答える（本来はそう答えるべきではない場合）、そのような答え方をする例がトレーニングデータに存在するか確認する。

**3.データのバランスと多様性を見直す**
データ内のアシスタントの回答の60%が「答えられません」となっている場合、過剰に「答えられません」という回答が出る可能性が高い。

**4.トレーニングデータには、回答に必要な情報を全て含めること**
例えば、ユーザーの特定の特性に基づいてモデルに褒め言葉を言わせたい場合、データにはその特性に関する情報が含まれている必要がある。
含まれていない場合、存在しない情報を「作り出す」可能性がある


**5.トレーニングデータの一致性・整合性を確認する**
データを複数の人が作成した場合、人々の間の一致や整合性のレベルによってモデルの性能が制限される可能性がある
例えば、テキストの抽出タスクで、人々が抽出されたスニペットの70%しか合意できなかった場合、モデルの性能もそのレベルを超えることは難しい。

**6.データの形式を統一**
すべてのデータが同じ形式であることを確認。

>If the results from a fine-tuning job are not as good as you expected, consider the following ways to adjust the training dataset:
Collect examples to target remaining issues
If the model still isn’t good at certain aspects, add training examples that directly show the model how to do these aspects correctly
Scrutinize existing examples for issues
If your model has grammar, logic, or style issues, check if your data has any of the same issues. For instance, if the model now says "I will schedule this meeting for you" (when it shouldn’t), see if existing examples teach the model to say it can do new things that it can’t do
Consider the balance and diversity of data
If 60% of the assistant responses in the data says "I cannot answer this", but at inference time only 5% of responses should say that, you will likely get an overabundance of refusals
Make sure your training examples contain all of the information needed for the response
If we want the model to compliment a user based on their personal traits and a training example includes assistant compliments for traits not found in the preceding conversation, the model may learn to hallucinate information
Look at the agreement / consistency in the training examples
If multiple people created the training data, it’s likely that model performance will be limited by the level of agreement / consistency between people. For instance, in a text extraction task, if people only agreed on 70% of extracted snippets, the model would likely not be able to do better than this
Make sure your all of your training examples are in the same format, as expected for inference


## ⑤【データの量】調整すべき点

- データの品質/分布に満足した場合、データを増やすことを検討できる
    - 特定のケースについてより良く学習可能に


- トレーニングデータを2倍にすると、モデルの性能も同じくらい上がることが期待される


- データの量を増やす効果を評価する方法
    - 1.現在の全データで学習
    - 2.半分のデータで学習
    - 3.二つの結果を比較する

- トレードオフを考える場合
    - 少量の高品質なデータは、大量の低品質なデータよりも効果的



>Once you’re satisfied with the quality and distribution of the examples, you can consider scaling up the number of training examples. This tends to help the model learn the task better, especially around possible "edge cases". We expect a similar amount of improvement every time you double the number of training examples. You can loosely estimate the expected quality gain from increasing the training data size by:
-Fine-tuning on your current dataset
-Fine-tuning on half of your current dataset
-Observing the quality gap between the two
In general, if you have to make a trade-off, a smaller amount of high-quality data is generally more effective than a larger amount of low-quality data.


## ⑥ハイパーパラメータ(エポック数)の調整

Fine-tuning時のハイパーパラメータとしてエポック数を指定可能。
最初は指定せずにトレーニングを行い、以下の状況が確認された場合に調整することが推奨されている。

- モデルが期待通りにトレーニングデータに従わない場合
    - エポック数を1~2増やす
    - 明確な正解があるタスク(例：分類やエンティティ抽出など)でこの問題が見られる
- モデルの多様性が欠けた場合
    - エポック数を1~2減らす
    - 多様な正解が考えられるタスクでこの問題が見られることが多い


>We allow you to specify the number of epochs to fine-tune a model for. We recommend initially training without specifying the number of epochs, allowing us to pick a default for you based on dataset size, then adjusting if you observe the following:
If the model does not follow the training data as much as expected increase the number by 1 or 2 epochs
This is more common for tasks for which there is a single ideal completion (or a small set of ideal completions which are similar). Some examples include classification, entity extraction, or structured parsing. These are often tasks for which you can compute a final accuracy metric against a reference answer.
If the model becomes less diverse than expected decrease the number by 1 or 2 epochs
This is more common for tasks for which there are a wide range of possible good completions



# Fine-tuningの手順

公式ドキュメントに従い、データを50個用意して精度が向上するかどうかを確認していきます。
前回と同様のタスクを取り扱います。架空のスポーツである「ホバンヌ」を定義し、モデルに教え込んでいきます。

- ①データを50個用意する
- ②訓練:8, テスト:2に分割する
- ③学習ジョブを作成する
- ④指標を確認する
- ⑤Fine-tuningしたモデルの評価を行う 


## ①データを50個用意する

前回、ChatGPTと一緒にホバンヌを定義しました。（前回から少しだけ肉付けしています。）

```:ホバンヌの概要
ホバンヌの詳細なルールと概要：

チームとポジション:
　各チームは3人の選手から成り立っています。
　　フロンター: 前方での攻撃を主導する選手。
　　ミドラー: 中央での攻守のバランスを取る選手。
　　バッカー: 主に守備やゴールの前でのプレイを担当する選手。

コートの詳細:
　コートは硬い表面を持ち、ラインが引かれている。
　コートの中央にはセンターサークルがあり、ここから試合を開始する。

ボールの特性:
　2つのボールのうち1つは硬く、シュートを主目的として使用される。もう1つは柔らかく、パスやドリブルを主目的として使用される。

得点方法:
　フィールドゴール: ボールをゴールに投げ込むことで1点獲得。
　ボーナスシュート: 反則が起きた場合、特定の位置からのシュート。成功すると2点。

プレイの流れ:
　ボールはハンドパスで選手間で移動される。
　ボールを持った選手は片足で立つ必要があり、ドリブルや歩行は禁止されている。

反則とペナルティ:
　手以外の部位でボールを触れること。
　対戦相手を押したり、不必要な肉体的接触。
　ボールを持って2歩以上移動する。

タイムアウト:
　各チームは各ハーフに1回のタイムアウトを取ることができ、その間に戦略の変更や休息が取れる。

審判の役割:
　主審と副審の2人の審判がいて、ゲームのフェアプレイを確保する。
　主審はフィールド内の全てのアクションを判断し、副審はアウトラインやゴールラインを監視する。
```

この概要を基にデータを50個作成します。
データの形式は以下の通りです。OpenAI APIのChatCompletionを呼び出す時と同じ形式です。

```json:Fine-tuningに使用するデータの形式
{"messages": [{"role": "system", "content": "システムメッセージ"}, {"role": "user", "content": "ユーザの入力"}, {"role": "assitant", "content": "期待する出力"}]}
```

現段階では一問一答形式として、以下のようなデータを作成します。
 - system: "You are factual chatbot." (固定)
 - user: 質問
 - assistant: 回答 (ホバンヌの概要であることを明示する)


データ形式は精度に大きく関わるため、検討の余地があります。
例えば、systemに教え込みたい事柄の概要を埋め込むと回答の精度が上がるといった情報もあります。

ここでは、回答に該当する文字列内で「ホバンヌの概要」であることを明示するようにしてみます。
自分で作成するのは面倒なのでChatGPT(GPT-4)に作ってもらいましょう。

>ホバンヌの概要から質問と回答の組み合わせを50個作って、markdownのメモ帳に表形式で張り付けられるようにして。回答には「ホバンヌの概要」であることを必ず含めてください。


生成してもらった質問と回答の組み合わせの例を以下に示します。

| 質問                                                                                     | 回答                                                                                                 |
|----------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
| ホバンヌは何人で構成されるチームスポーツですか？                                                  | ホバンヌの概要によれば、各チームは3人の選手から構成されています。                                                 |
| ホバンヌのコートの大きさはどれくらいですか？                                                     | ホバンヌの概要によれば、コートの長さは約20メートル、幅は約10メートルです。                                        |
| ホバンヌの試合中に使用されるボールは何個ですか？                                                  | ホバンヌの概要によれば、試合中には2つのボールが使用されます。                                                    |
| ホバンヌでの得点の方法は何ですか？                                                           | ホバンヌの概要によれば、ボールを相手チームのゴールに投げ込むことで得点を獲得します。                                   |
| ホバンヌの一試合の合計時間はどれくらいですか？                                                   | ホバンヌの概要によれば、試合は2つのハーフで構成され、各ハーフの時間は10分です。                                       |
| ホバンヌの試合はどの位置から開始されますか？                                                     | ホバンヌの概要によれば、試合はセンターサークルから開始されます。                                                   |
| ホバンヌでボールがアウトになった場合、どこから再開されますか？                                            | ホバンヌの概要によれば、ボールがアウトになった場合はゴールラインから再開されます。                                                                  |
| ホバンヌのチームはどれくらいのサブメンバーを持てますか？                                           | ホバンヌの概要によれば、サブメンバーに関する明確なルールは記載されていません。                                           |
| ホバンヌの試合中に怪我をした場合、どのような対応が取られますか？                                   | ホバンヌの概要によれば、怪我に関する対応や手順についての詳細は記載されていません。                                       |
| ホバンヌの公式試合にはどのような資格が必要ですか？                                                 | ホバンヌの概要によれば、公式試合の資格要件に関する具体的な情報は提供されていません。                                     |
| ホバンヌの選手が使用するシューズには特別な仕様がありますか？                                         | ホバンヌの概要によれば、選手が使用するシューズの仕様に関する詳細は記載されていません。               


「情報は提供されていません。」という回答が多いため、**【データの質】調整すべき点**に抵触しそうですがひとまずこのまま進めます。

続いて、データ形式をFine-tuning用に変換します。これもChatGPTにお任せします。
(Code Interpreterに任せるとワンパンでした。)

>各行を以下のような形式に直してほしい。
{"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"}]}


これで50件分のデータが記載されたjsonlファイルを用意できました。


```json:
{"messages": [{"role": "system", "content": "you are a factual chatbot."}, {"role": "user", "content": "ホバンヌのハーフタイムの長さはどれくらいですか？"}, {"role": "assistant", "content": "ホバンヌの概要によれば、ハーフタイムの詳細な長さは記載されていませんが、試合は2つの10分ハーフで構成されています。"}]}
{"messages": [{"role": "system", "content": "you are a factual chatbot."}, {"role": "user", "content": "ホバンヌの選手は交代できますか？"}, {"role": "assistant", "content": "ホバンヌの概要によれば、選手の交代に関する詳細なルールは記載されていません。"}]}
...
{"messages": [{"role": "system", "content": "you are a factual chatbot."}, {"role": "user", "content": "ホバンヌの公式ボールのブランドは何ですか？"}, {"role": "assistant", "content": "ホバンヌの概要によれば、公式ボールのブランドに関する情報は明記されていません。"}]}
{"messages": [{"role": "system", "content": "you are a factual chatbot."}, {"role": "user", "content": "ホバンヌの選手は専属契約を結ぶことができますか？"}, {"role": "assistant", "content": "ホバンヌの概要によれば、選手の専属契約に関する情報は提供されていません。"}]}

```


## ②訓練用とテスト用に分割する

8:2の割合にデータを分割します。これもChatGPT(Code Interpreter)にお任せしましょう。

>訓練用とテスト用に8:2の割合で分割して。また、各ファイルをダウンロード可能にして。

これで以下の2つのファイルを用意できました。

- ```hobannu_train.jsonl``` → データセットの8割を含むファイル
- ```hobannu_test.jsonl``` → データセットの2割を含むファイル


## ③学習ジョブの作成

まずは各ファイルをアップロードします。

```py:訓練/テストデータをアップロード
import os
import openai
from dotenv import load_dotenv
load_dotenv()

openai.api_key = os.getenv("OPENAI_API_KEY")

# 訓練データをアップロード
train_file_path = "data/hobannu_train.jsonl"
train_file = openai.File.create(
  file=open(train_file_path, "rb"),
  purpose='fine-tune'
)

# テストデータをアップロード
test_file_path = "data/hobannu_test.jsonl"
test_file = openai.File.create(
  file=open(test_file_path, "rb"),
  purpose='fine-tune'
)

train_file.id, test_file.id
```

学習ジョブの作成に必要となるため、各ファイルのIDを控えておきます。

- ```train_fileのID```： file-xxxx
- ```test_fileのID```: file-yyyy


続いて、学習ジョブを作成します。
training_fileに```train_fileのID```を、validation_fileに```test_fileのID```を指定します。

```py:学習ジョブの作成
# fine-tunedモデルの作成
job_result = openai.FineTuningJob.create(
    training_file=train_file.id,
    validation_file=test_file.id,
    model="gpt-3.5-turbo",
)

job_result
```

学習完了メールが来るまで待ちます。ステータス/進捗は以下のコードで確認できます。
(メールが届いたら記載のリンクからPlaygroundに飛べます。)


## ④指標を確認する

学習ジョブの結果ファイルをダウンロードします。

```py:結果ファイルのダウンロード
# ジョブのステータスを確認
response_retrieve = openai.FineTuningJob.retrieve(job_result.id)

# 学習過程の指標が記録されたファイルをダウンロード
reuslt_file = openai.File.download(id=response_retrieve.result_files[0])

# CSVファイルに書込み
with open("statistics.csv", "w") as f:
    f.write(reuslt_file.decode("utf-8"))
```

形式はCSVになっているようです。

```js:CSVファイルの一部
step,train_loss,train_accuracy,valid_loss,valid_mean_token_accuracy
1,1.99588,0.0,2.01303,0.63514
2,2.4445,0.0,,
3,1.78798,0.0,,
4,1.99353,0.0,,
5,1.97473,0.0,,
6,1.96365,0.0,,
7,2.02592,0.0,,
...
```

CSVを眺めるのは辛いので適当に可視化します。

```py:可視化
import pandas as pd
import matplotlib.pyplot as plt

# CSVファイルを読み込む
data_path = './statistics.csv'
data = pd.read_csv(data_path)

# 検証データの非欠損値のみをフィルタリング
valid_data = data.dropna(subset=['valid_loss', 'valid_mean_token_accuracy'])

fig, ax = plt.subplots(2, 2, figsize=(15, 10))

# 1. ステップ数に対するトレーニングデータの損失値の変化
ax[0, 0].plot(data['step'], data['train_loss'], label='Train Loss', color='blue')
ax[0, 0].set_title('Training Loss over Steps')
ax[0, 0].set_xlabel('Steps')
ax[0, 0].set_ylabel('Loss')
ax[0, 0].legend()

# 2. ステップ数に対するトレーニングデータの正解率の変化
ax[0, 1].plot(data['step'], data['train_accuracy'], label='Train Accuracy', color='green')
ax[0, 1].set_title('Training Accuracy over Steps')
ax[0, 1].set_xlabel('Steps')
ax[0, 1].set_ylabel('Accuracy')
ax[0, 1].legend()

# 3. ステップ数に対する検証データの損失値の変化
ax[1, 0].plot(valid_data['step'], valid_data['valid_loss'], label='Validation Loss', color='red', marker='o')
ax[1, 0].set_title('Validation Loss over Steps')
ax[1, 0].set_xlabel('Steps')
ax[1, 0].set_ylabel('Loss')
ax[1, 0].legend()

# 4. ステップ数に対する検証データの平均トークン正解率の変化
ax[1, 1].plot(valid_data['step'], valid_data['valid_mean_token_accuracy'], label='Validation Token Accuracy', color='orange', marker='o')
ax[1, 1].set_title('Validation Token Accuracy over Steps')
ax[1, 1].set_xlabel('Steps')
ax[1, 1].set_ylabel('Token Accuracy')
ax[1, 1].legend()

plt.tight_layout()
plt.show()
```

このようなグラフが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/d1bc4ce9-3769-9ca5-3fac-86eedb7923eb.png)

可視化した結果、以下のようなことが分かります。(ChatGPT任せです。)

- **トレーニングの損失値の減少**
    - トレーニングの損失値はステップ数とともに減少しています。
    - これは、モデルが学習によって進化し、トレーニングデータに対する予測誤差が少なくなっていることを示しています。

- **検証の損失値と正解率**
    - 検証データに関しては、損失値が減少し、トークンの正解率が向上しています。
    - これは、モデルが一般的に新しいデータに対しても良い予測をしていることを示しています。
    - ただし、検証の損失値や正解率が一定のポイントからさらに改善されない場合、過学習の兆候があるかもしれません。



## ⑤Fine-tuningしたモデルの評価を行う 

OpenAI Playgroundを用いて、Fine-tuningしたモデルに様々な質問を投げかけてみます。


### 存在しない情報を作り出す
存在しない/与えていない情報を答えてしまっています。「ゾーン」という単語は定義していません:frowning2:
（3つのポジションは定義していますが。）

**【データの質】調整すべき点④** に記載した内容に該当しそうですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/6623acfd-1025-a697-7f18-896a452d0601.png)


### 既存知識との混同

バスケットボールと混同している気がしますね:frowning2:
この問題についてはデータの質もそうですが、プロンプトエンジニアリングも大切な要素になりそうです。
ただ、学習と推論は分けて考えた方がよさそうですね、以下のような関係性になるでしょうか。
- 学習時のアプローチ
    - データの質や量を改善
- 推論時のアプローチ
    - プロンプトエンジニアリングによる制御
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/e1958aad-35fe-814c-bf47-f8b3a740fedd.png)


### データの偏り

**【データの質】調整すべき点③** に記載した内容に該当します。
学習データの中に、「情報は提供されていません。」というデータが多く存在するため、そのような回答が頻出しています:frowning2:

また、プロンプトエンジニアリングの要素を含めてみました。
具体的には、「**ホバンヌの概要によると、、、**」という出力をするように制御しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/46bcd2b1-acae-fd58-f575-a8e4049c3824.png)


# データセットの改善【質】


公式ドキュメントの内容と上記の評価結果を基に、いくつかデータセットの改善案を考えます。
そして、改善後のデータセットで再度Fine-tuningを行い、性能が変化するのか確かめます。


## 案①：データの偏りを無くす

まずはわかりやすいところから見直していきます。具体的には **【データの質】調整すべき点③** です。

「情報がありません。」「回答できません。」のようなデータが多く含まれている場合、Fine-tuning後のモデルの回答がそれらのデータに強く引っ張られてしまうようです。
極端に言うと、なにを聞いても「情報がありません。」と回答してしまうボットになるわけですね。


さきほど作成したデータセットの中に「情報が提供されていません。」といった回答が何件あるか調べます。

```py:
import json

hobannu_qa = []

with open("./data/hobannu_50.jsonl", "r", encoding="utf-8") as file:
    for line in file:
        hobannu_qa.append(json.loads(line))

count_not_provided = sum(1 for entry in hobannu_qa if any(message['content'].count("情報は提供されていません") > 0 for message in entry['messages'] if message['role'] == 'assistant'))
count_not_provided 

# 出力 → 27
```

27/50件のようです、つまり50%以上が「情報は提供されていません」といった回答になっています。
そのため、以下のような挙動になっていたわけですね。なるほど。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/46bcd2b1-acae-fd58-f575-a8e4049c3824.png)

「情報は提供されていません」というデータを減らす。といった改善策が考えられます。


## 案②：質問と回答の組み合わせを人間がチェックする

質問と回答の組み合わせの提案はChatGPTに任せつつ、最終チェックは人間が行うという方式でデータの質を高めます。
（さきほどまでのデータセットは全てChatGPTに任せて作成したものでした。）



## 改善案の実施

案①②を実施してきます。ChatGPT(Code Interpreter)と一緒に修正します。
ホバンヌの概要を記載したテキストファイルと、データセットのファイルをアップロードし、以下のようにお願いしました。

>既存のデータセットと同じ形式のデータを50件作成したい。
以下の条件に従って作成しよう。
[条件]
・必ずホバンヌの概要に記載された内容を使用すること
・「情報は提供されていません」といった回答の生成を避けること
・一つずつ質問と回答の組み合わせのフィードバックを求めること
[フィードバック]
・質問と回答の組み合わせの内容を見直す
・対話形式でフィードバックを行う


公式ドキュメント記載のデータの質に関するポイントを参考に一つずつ作成します。

- ポイント一部抜粋
    - 問題点を解決するための例を追加
    - トレーニングデータには、回答に必要な情報を全て含めること
    - トレーニングデータの一致性・整合性を確認する
    - データの形式を統一


ここまでで再度Fine-tuningを行います。


## 学習過程の指標を確認

さきほど同様、学習過程の指標を可視化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/80c4209c-7ab7-20e1-a33a-295e62655466.png)


## 評価

Fine-tuning後のモデルに質問を投げかけてみます。

あまり変化がないように見えますね:frowning2:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/b7efb707-6d53-17fc-bcca-5b90a34b720d.png)

プロンプトエンジニアリング要素も含めて質問を投げかけてみます。「ホバンヌの概要によると、」に続くように出力してもらいます。
改善前はほとんどの回答が「情報は提供されていません。」となっていましたが、答えてくれるようにはなりました。
しかし、期待通りの回答ではないですね:frowning2:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/3276efa3-2fb9-ff81-2338-246d09c3c620.png)


既存知識（バスケットボール）と混同しています:frowning2:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/ae6fa387-a099-094f-c515-1fd6126b8899.png)


学習データに含まれている知識を明示的に聞いてみましたが、知らないと回答されます:frowning2:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/5086625b-27be-4796-d7fd-14a987de8206.png)


# ハイパーパラメータ (エポック数)の調整

データの質は改善の余地がまだまだありそうですが、ハイパーパラメータの調整をしてみます。
公式ドキュメントには、「特定ケースの学習が思うように行かない場合、1~2ずつ増やしてみましょう」とあるので、2増やして試してみます。

```py:
job_result = openai.FineTuningJob.create(
    training_file=train_file.id,
    validation_file=test_file.id,
    model="gpt-3.5-turbo",
    hyperparameters = {"n_epochs": 5}
)
```

Fine-tuning後のモデルに質問を投げかけてみます。
学習データの内容がより反映され、「3人で行う」「ボールを2つ使う」などホバンヌの定義通りの回答が得られました:ok_hand:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/ca05393c-c9ad-e46c-e093-d1f220a5b5dc.png)

また、反則や審判の仕様も答えられました:ok_hand:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/bbbce4e2-9898-ed70-a294-d4312b4c5f3f.png)


ただ、答えられない知識もあるようです。ホバンヌは「ボーナスシュート」という2得点できるルールがあります:frowning2:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/ac27a38d-a91c-1835-13c6-8e173bf04b1d.png)


学習過程の指標は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/393221/2f00f9a7-bf52-06fb-64cd-37bd149ca5d8.png)

ChatGPTに結果の取りまとめ&考察をしてもらいます。

>トークン正解率は上がり、損失値が下がることがいい傾向だと思うけど、どう思う？また、今回はどうなってる？

- トークン正解率 (valid_mean_token_accuracy)の動向：
    - 初期のステップでは、トークン正解率が0の近くにありますが、ステップが進むにつれて、正解率は上昇しています。
    - これはモデルの学習が進行していることを示しています。
- 損失値 (valid_loss)の動向：
    - 損失は、ステップが進むにつれて全体的に低下しています。
    - これはモデルがデータに適合していることを示しています。

追加で考察を依頼します。

>過学習やデータの質・量に関してはどう思う？


- 過学習の兆候:
    - Train LossとValidation Lossの動向を見ると、訓練データの損失はステップが進むにつれて低下しているのに対し、検証データの損失も低下していることが確認できます。
    - このことから、過学習の明確な兆候は見られません。
    - ただし、検証データの損失が訓練データの損失よりも高い範囲が存在することから、一部のステップでモデルが過学習している可能性は考えられます。


- データの質と量:
    - Validation Lossが全体的に低下していることは、データの質が高く、モデルがデータに適切に適合していることを示唆しています。
    - 一方で、訓練データと検証データの間の損失のギャップ（Loss Gap）は、ステップが進むにつれて拡大している範囲が見られます。
    - これは、データの量が不足しているか、データのバリエーションが不足していることを示唆する可能性があります。


なるほど。特に、量・バリエーションの不足は確かにその通りかもしれませんね。
見直しポイントは多々ありそうですが、今回はここまでとします。


# まとめ

「知識を教え込む」ことはできそうだなと感じました。
ただ、以下のような課題に直面することが想像できるかと思います。(ChatGPTと対話しつつ整理した課題です。)

- 一般化の問題
    - プレフィックスに過度に特定の情報を埋め込むと、その情報にのみ過度に依存するモデルができ上がる恐れがあります。
    - その結果、他の情報やコンテキストでの適切な回答が難しくなる可能性があります。
- データの冗長性
    - 各入力データに同じような情報を繰り返し付加することになるため、データのサイズが不必要に大きくなる可能性があります。
- 柔軟性の欠如
    - 一度埋め込んだ情報を変更や更新するのが難しくなる場合があります。


また、OpenAI公式ドキュメントは偉大です。困った時・課題に直面した時の最初のアプローチの参考になりそうです。
全てに目を通せているわけではないので時間を見つけて読みたいですね。


今後も「企業内の情報を用いてFine-tuningさせるには？」を題材に色々と情報収集・検証をしていきたいと思います。

(RAGとの連携やPrompt Flowでの評価などやりたいこと盛りだくさん。)



# おまけ. ソースコード

Fine-tuning周りのソースコードを置いておきます。

<details><summary>関連ソースコード</summary>

```py:
import os
import openai
import time
from dotenv import load_dotenv
import pandas as pd
import matplotlib.pyplot as plt

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")

def job_create_with_train_test(train_data_path:str, test_data_path:str, num_epochs=None):
    # 訓練データをアップロード
    train_file = openai.File.create(
    file=open(train_data_path, "rb"),
    purpose='fine-tune'
    )

    # テストデータをアップロード
    test_file = openai.File.create(
    file=open(test_data_path, "rb"),
    purpose='fine-tune'
    )

    time.sleep(30)

    # fine-tunedモデルの作成
    job_result = openai.FineTuningJob.create(
        training_file=train_file.id,
        validation_file=test_file.id,
        model="gpt-3.5-turbo",
        hyperparameters = {"n_epochs": 3 if num_epochs is None else num_epochs}
    )

    return job_result


def check_job_status(job_id:str):
    job = openai.FineTuningJob.retrieve(job_id)
    return job


def write_result(job):
    # 学習過程の指標が記録されたファイルをダウンロード
    reuslt_file = openai.File.download(id=job.result_files[0])

    # CSVファイルに書込み
    with open("statistics.csv", "w") as f:
        f.write(reuslt_file.decode("utf-8"))

    _show_result()
    

def _show_result():
    # CSVファイルを読み込む
    data_path = './statistics.csv'
    data = pd.read_csv(data_path)

    # 検証データの非欠損値のみをフィルタリング
    valid_data = data.dropna(subset=['valid_loss', 'valid_mean_token_accuracy'])

    fig, ax = plt.subplots(2, 2, figsize=(15, 10))

    # 1. ステップ数に対するトレーニングデータの損失値の変化
    ax[0, 0].plot(data['step'], data['train_loss'], label='Train Loss', color='blue')
    ax[0, 0].set_title('Training Loss over Steps')
    ax[0, 0].set_xlabel('Steps')
    ax[0, 0].set_ylabel('Loss')
    ax[0, 0].legend()

    # 2. ステップ数に対するトレーニングデータの正解率の変化
    ax[0, 1].plot(data['step'], data['train_accuracy'], label='Train Accuracy', color='green')
    ax[0, 1].set_title('Training Accuracy over Steps')
    ax[0, 1].set_xlabel('Steps')
    ax[0, 1].set_ylabel('Accuracy')
    ax[0, 1].legend()

    # 3. ステップ数に対する検証データの損失値の変化
    ax[1, 0].plot(valid_data['step'], valid_data['valid_loss'], label='Validation Loss', color='red', marker='o')
    ax[1, 0].set_title('Validation Loss over Steps')
    ax[1, 0].set_xlabel('Steps')
    ax[1, 0].set_ylabel('Loss')
    ax[1, 0].legend()

    # 4. ステップ数に対する検証データの平均トークン正解率の変化
    ax[1, 1].plot(valid_data['step'], valid_data['valid_mean_token_accuracy'], label='Validation Token Accuracy', color='orange', marker='o')
    ax[1, 1].set_title('Validation Token Accuracy over Steps')
    ax[1, 1].set_xlabel('Steps')
    ax[1, 1].set_ylabel('Token Accuracy')
    ax[1, 1].legend()

    plt.tight_layout()
    plt.show()


# ジョブ作成 (エポック数を指定)
train_data_path = "data/hobannu_train_data.jsonl"
test_data_path = "data/hobannu_test_data.jsonl"
job_result = job_create_with_train_test(train_data_path, test_data_path, num_epochs=5)

# ジョブの状態を確認
job_id = job_result.id
job_status = check_job_status(job_id)
print(job_status)

# 学習過程の指標を確認
write_result(job_status)
```

</details>