【日本語版】OpenAI APIでGPT-3にFine-tuningでアニメの放送開始日時を学習してもらうPython実装の記録

[Supership](https://supership.jp/)の名畑です。今期アニメの[マイホームヒーロー](https://myhomehero-anime.com/)は毎週胃がキリキリするのに続きを見ずにいられない。

## はじめに

[前回の記事「OpenAI APIでGPT-3にFine-tuningでアニメの放送開始日時を学習してもらうPython実装の記録」](https://qiita.com/nabata/items/13962a62a1b298361dcb)では[OpenAI Fine-tunes
 API](https://platform.openai.com/docs/api-reference/fine-tunes)を用いて英語によるFine-tuningについて記載しました。
 
 今回は日本語においてFine-tuningを行い、前回同様に[ぼっち・ざ・ろっく!](https://bocchi.rocks/)の放送開始日時をGPT-3に学習してもらいます。記事の一部は前回の記事同様です。

アカウントの作成やAPI Keyのセットなどについては[前々回の記事「OpenAIのChat API(Chat completions)を用いての会話をPythonで実装してみた記録」](https://qiita.com/nabata/items/0919576a926d5bb9814d)をご参照ください。

:::note warn
この記事でやろうとしていることを実現したいだけなら「**GPT-3.5のモデルに対して元情報を含めた上で質問**」をした方がより精度が高い回答が得られるし安価です。
本記事は「**Fine-tuningを試す**」という目的のためのものであることをご認識ください。
:::

## Fine-tuning

新たな学習データを用意して、既存のモデルの調整を行うというものです。

公式に丁寧な説明があります。

- [API reference](https://platform.openai.com/docs/api-reference/fine-tunes)
- [Guides](https://platform.openai.com/docs/guides/fine-tuning)

「**学習データの用意**」「**学習データのアップロード**」「**学習**」「**生成されたモデルの使用**」これらの段階を踏んでいきます。

また、Guidesに下記の記載があるので、Chat APIではなく[Completions API](https://platform.openai.com/docs/api-reference/completions)を用います。

> You can start making requests by passing the model name as the model parameter of a completion request

## Fine-tuningの料金

[Pricngのページ](https://openai.com/pricing)に記載されています。学習と使用のそれぞれで料金がかかるのでご注意ください。

現時点での料金は下記です。modelによってコストの桁が変わるのでご注意を。

| Model	 | Training | Usage |
| --- | --- | --- |
| Ada | $0.0004 / 1K tokens | $0.0016 / 1K tokens |
| Babbage | $0.0006 / 1K tokens | $0.0024 / 1K tokens |
| Curie | $0.0030 / 1K tokens | $0.0120 / 1K tokens |
| Davinci | $0.0300 / 1K tokens | $0.1200 / 1K tokens |

## 今回の対象モデル

今回は既存のモデルとして**text-curie-001**を選んでみました。
Fine-tunes APIは下記の通り**GPT-3**ベースである**davinci**、**curie**、**ada**、**babbage**の4つにのみ今は対応しています。

> These models were superceded by the more powerful GPT-3.5 generation models. However, the original GPT-3 base models (davinci, curie, ada, and babbage) are current the only models that are available to fine-tune.

## 学習対象選定理由

**ぼっち・ざ・ろっく!の放送開始日**を選んだ理由は下記です。

- **text-curie-001**の学習データが**Up to Oct 2019**である(詳しくは[modelsのGPT-3の説明](https://platform.openai.com/docs/models/gpt-3)を参照)
- ぼっち・ざ・ろっく!の放送開始日は**2022年10月8日**のため、**text-curie-001**にその情報が含まれないはず
- つまり学習が反映されたことが判断しやすいはず


## 学習データの用意

下記が公式に展開されている学習データのフォーマット例です。**jsonl形式**です。改行区切りされたjsonです。

```Python
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
...
```

つまり**prompt**と**completion**を対にしたサンプルを用意する必要があります。

学習データについては[Data formatting](https://platform.openai.com/docs/guides/fine-tuning/data-formatting)を見ると下記のように書かれています。

- Each prompt should end with a fixed separator to inform the model when the prompt ends and the completion begins. A simple separator which generally works well is \n\n###\n\n. The separator should not appear elsewhere in any prompt.
- Each completion should start with a whitespace due to our tokenization, which tokenizes most words with a preceding whitespace.
- Each completion should end with a fixed stop sequence to inform the model when the completion ends. A stop sequence could be \n, ###, or any other token that does not appear in any completion.
- For inference, you should format your prompts in the same way as you did when creating the training dataset, including the same separator. Also specify the same stop sequence to properly truncate the completion.

つまりは、promptの最後には(prompt内で他に使われていない)セパレーターをつけること、completionはスペースで始めること、completionの最後には(completion内で他に使われていない)終わりを示す記号をつけること。

## どうやって学習データを用意した？

人力で用意するのはややしんどいため、下記の手順を踏むことにしました。

- ぼっち・ざ・ろっく！の放送開始日時をまとめたテキストを用意する
- そのテキストを元にして[OpenAI Chat API](https://platform.openai.com/docs/api-reference/chat)に質問と回答を生成してもらう

まず、用意した原文は下記です。**origin_message**という変数に格納しておきました。だいたいは[公式サイトのONAIRのページ](https://bocchi.rocks/onair/)の情報そのままです。

```Python
origin_message = """【タイトル】
TVアニメ「ぼっち・ざ・ろっく！」のオンエアスケジュール

【TVでの放送スケジュール】
※放送日時は変更になる場合がございます。

放送局	放送開始日	放送時間
TOKYO MX	2022/10/8（土）	24:00〜
BS11	2022/10/8（土）	24:00〜
群馬テレビ	2022/10/8（土）	24:00〜
とちぎテレビ	2022/10/8（土）	24:00〜
MBS	2022/10/8（土）	27:08〜
RKB毎日放送	2022/10/13（木）	27:00〜
MRT宮崎放送	2022/10/8（土）	25:28〜
AT-X	2022/10/10（月）	22:30～


TVアニメ「ぼっち・ざ・ろっく！」の配信スケジュールです



【ストリーミングでの配信スケジュール】
※配信日時は変更になる場合がございます。

プラットフォーム	配信開始日	配信時間
ABEMA	2022/10/8（土）	毎週土曜日24:00～
※地上波同時配信
Netflix	2022/10/11（火）	毎週火曜日12:00～
dアニメストア	2022/10/11（火）	毎週火曜日12:00～
dアニメストア
for Prime Video	2022/10/11（火）	毎週火曜日12:00～
dアニメストア
ニコニコ支店	2022/10/11（火）	毎週火曜日12:00～
U-NEXT	2022/10/11（火）	毎週火曜日12:00～
アニメ放題	2022/10/11（火）	毎週火曜日12:00～
Amazon Prime Video	2022/10/11（火）	毎週火曜日12:00～
バンダイチャンネル	2022/10/11（火）	毎週火曜日12:00～
Hulu	2022/10/11（火）	毎週火曜日12:00～
ひかりTV	2022/10/11（火）	毎週火曜日12:00～
FOD	2022/10/11（火）	毎週火曜日12:00～
GYAO!	2022/10/11（火）	毎週火曜日12:00～
ニコニコチャンネル	2022/10/11（火）	毎週火曜日12:00～
ニコニコ生放送	2022/10/11（火）	毎週火曜日22:30〜
WOWOWオンデマンド	2022/10/11（火）	毎週火曜日12:00～
TELASA	2022/10/11（火）	毎週火曜日24:00～
JCOM:オンデマンド	2022/10/11（火）	毎週火曜日24:00～
みるプラス-見放題パック	2022/10/11（火）	毎週火曜日24:00～
auスマートパスプレミアム	2022/10/11（火）	毎週火曜日24:00～
Video Market	2022/10/11（火）	毎週火曜日12:00～
music.jp	2022/10/11（火）	毎週火曜日12:00～
GYAO!ストア		2022/10/11（火）	毎週火曜日12:00～
"""
```


**質問と回答を生成したコード**は下記です。
質問と回答を20組生成させています。これを5回やれば100個です。指示が悪いのか20組返してくれないこともありましたが。

```Python
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")
messages = [{"role": "user", "content": """Write 20 sets of a question and an answer in Japanese using the text below.

            Desired format:
            Questions
            * <question 1>
            * <question 2>
            ...
            * <question 19>
            * <question 20>

            Answers
            * <answer 1>
            * <answer 2>
            ...
            * <answer 19>
            * <answer 20>

            Design rules:
            ・Don't use leading numbers

            Text: """ + origin_message}
            ]

completion = openai.ChatCompletion.create(
    model="gpt-3.5-turbo",
    messages=messages
)

# 応答の取得 / 表示
res_message = completion["choices"][0]["message"]["content"]
print(res_message)
```

フォーマットは私が扱いやすい形にしています。
プロンプトの書き方は[Best practices for prompt engineering with OpenAI API](https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-openai-api)がかなり参考になりました。

コードを実行した結果例は下記。

```
Questions
* 「ぼっち・ざ・ろっく！」はどのTV局で放送されますか？
* 放送開始日はいつですか？
* 放送時間は何時から何時までですか？
<省略>

Answers
* TOKYO MX、BS11、群馬テレビ、とちぎテレビ、MRT宮崎放送など、複数のTV局で放送されます。
* 放送開始日は2022年10月8日（土）です。
* 放送時間は、TOKYO MX、BS11、群馬テレビ、とちぎテレビ、MRT宮崎放送での放送は24:00〜、MBSでの放送は27:08〜、RKB毎日放送での放送は27:00〜です。
<省略>
```

MRT宮崎での放送開始日時は25:28なので、間違ってしまっていますね。今回は**学習されたということがわかればいい**という割り切りで、一つ一つのデータの正否については追求せずこのまま進めます。本当はよくないです。当然、学習データの品質が結果に影響するため。

得られた結果を編集してjsonlファイルを作成します。

質問をpromptとして、回答をcompletionとして扱いました。
ガイドに基づき**promptの末尾に\n\n###\n\nをつけること**と、**completionの先頭にスペースを入れること**と、**completionの末尾に###をつけること**もしました。


```Python
{"prompt": "「ぼっち・ざ・ろっく！」はどのTV局で放送されますか？\n\n###\n\n", "completion": " TOKYO MX、BS11、群馬テレビ、とちぎテレビ、MRT宮崎放送など、複数のTV局で放送されます。###"}
{"prompt": "放送開始日はいつですか？\n\n###\n\n", "completion": " 放送開始日は2022年10月8日（土）です。###"}
{"prompt": "放送時間は何時から何時までですか？\n\n###\n\n", "completion": " 放送時間は、TOKYO MX、BS11、群馬テレビ、とちぎテレビ、MRT宮崎放送での放送は24:00〜、MBSでの放送は27:08〜、RKB毎日放送での放送は27:00〜です。###"}
<以下略>
```

## 学習用データのアップロード

[FilesのAPi refernce](https://platform.openai.com/docs/api-reference/files)にやり方が載っています。

私の場合は学習用データを**origin_data.jsonl**というファイル名で保存して、下記のコードを実行しました。

```Python
import os
import openai

openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.File.create(
    file=open("origin_data.jsonl", "rb"),
    purpose='fine-tune'
)
print(response)
```

**purpose**に**fine-tune**を指定することを忘れないようにご注意下さい。

下記のようなレスポンスが返ってきます。このうちの**id**をFine-tuningで使います。

```Python
{
  "bytes": 20177,
  "created_at": 1683277820,
  "filename": "file",
  "id": "file-hogehogehogehogehoge",
  "object": "file",
  "purpose": "fine-tune",
  "status": "uploaded",
  "status_details": null
}
```
 
## Fine-tuningの実施
 
これもコードは非常にシンプルです。
 
```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.FineTune.create(
    training_file="file-hogehogehogehogehoge",
)
print(response)
```

**training_file**にさきほどアップロードしたファイルのIDを記載します。

recuiredではないですが、Fine-tuningで使われるモデルを**model**として指定することも可能です。書かない場合はデフォルト値である**curie**が用いられます。また、すでにFine-tuning済みのモデルを元にすることもできます。

この際のレスポンスに下記のようにidの記載があります。

```Python
"id": "ft-hogehogehogehogehoge",
```

Fine-tuningは即時完了はしないため、しばらく待ちます。私がやったときは10分足らずでしたが、キューイングされるようなので、その時にどれだけFine-tuningが行われているかという状況や、あと、学習データの量にも待ち時間は左右されるかとは思います。

状況確認やキャンセルのためには上記のidを使います。
状況確認は**retrieve**で行います。

```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.FineTune.retrieve(id="ft-hogehogehogehogehoge")
print(response)
```

レスポンスの**events**という要素の配列に発生したイベント履歴が記されます(今どこまで進んでいるかなど)。

コストもそこに下記のように書かれています。

```Python
{
  "created_at": 1683342896,
  "level": "info",
  "message": "Fine-tune costs $0.05",
  "object": "fine-tune-event"
},
```


完了時は下記。

```Python
{
  "created_at": 1683343651,
  "level": "info",
  "message": "Fine-tune succeeded",
  "object": "fine-tune-event"
}
```

完了すると**retrieve**のレスポンスにfine-tuningがされたモデル名が記載されています(完了前はnull)。

```Python
"fine_tuned_model": "curie:ft-personal-2023-05-06-03-27-30",
```

これで準備完了です。

また、[Analyzing your fine-tuned model
](https://platform.openai.com/docs/guides/fine-tuning/advanced-usage)に書かれている通り、学習結果のダウンロードも可能ですので、よければご覧ください。ファイル名は下記のように**events**に記載されています。

```Python
{
  "created_at": 1683343651,
  "level": "info",
  "message": "Uploaded result file: file-fugafugafuga",
  "object": "fine-tune-event"
}
```

ダウンロードするコードは下記です。

```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
response = openai.File.download(id="file-fugafugafuga")
print(response)
```

## モデル呼び出しのコード

modelにさきほどのモデル名を指名して「**TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてください**」と投げるコードは下記です。
学習データ同様に**promptの末尾に\n\n###\n\nをつけて、stopに###を指定しています（###が登場したなら終了として扱う)。**

```Python
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
completion = openai.Completion.create(
    model="curie:ft-personal-2023-05-06-03-27-30",
    prompt="TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてください\n\n###\n\n",
    max_tokens=300,
    stop="###"
)
print(completion["choices"][0]["text"])
```

## 結果

以降、結果です。

いくつかの質問に対して

1. Fine-tuningをしていない**text-curie-001**
1. 1に対して**100個のデータ**でFine-tuningをしたモデル
1. 2に対してさらに**100個のデータ**でFine-tuningをしたモデル(つまりは**合計200個**)
1. 3に対してさらに**200個のデータ**でFine-tuningをしたモデル(つまりは**合計400個**)
1. 4に対してさらに**400個のデータ**でFine-tuningをしたモデル(つまりは**合計800個**)

の回答をそれぞれ5つずつ取得しました。
5回連続して呼び出して、その結果をそのまま貼っています。
ただし、文頭の改行とスペースだけは削除しています。

## 質問1

### 質問文

- TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてください

### 正解

- 2022年10月8日（土）

### text-curie-001の回答

- The anime "Boku no Hero Academia" will be streaming on Netflix from September 15.
- 2013年2月27日<br><br>アニメ「ぼっち・ざ・ろっく!」の配信開始日を教えてください。
- こちらの記事にて、豊作の漫画アニメ 「ぼっち・ざ・ろっく!」の配信開始日が決定しています。<br><br><br>公式サイトより、4月21日に配信開始が決定しています。是非、ご覧ください。
- 2017/03/07
- 配信開始日は2014年5月15日です。

### 100データでFine-tuningをしたモデルの回答

- プラットフォームによって異なりますが、TVアニメ ぼっち・ざ・ろっく!の配信開始日は、一部に重要な場面や特別なストーリーが含まれるために開始される場合があります。
- ヨーダの配信開始日は、毎週火曜日です。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は、開始日がありますか？
- 「ボット・ア・ウエア」のアニメ配信開始日は、TVアニメ ぼっち・ざ・ろっく!の配信開始日は、
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は、ABEMA、Netflix、dアニメストア、U-NEXT、JCOM、M demoneta's、Huluなどで配信されます。

### 200データでFine-tuningをしたモデルの回答

- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日（土）です。
- サマーステージを含め、配信開始日が異なる場合があります。
- 「ぼっち・ざ・ろっく!」の配信開始日は、2022年10月8日（土）からです。
- プラットフォームによって異なりますが、基本的には月曜日12:00〜からです。
- アニメイト、Netflix、dアニメストアなどのプラットフォームで配信がなされます。

### 400データでFine-tuningをしたモデル の回答

- TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてくださいこちらです。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてくださいです。
- 後で見るより速く配信される場合がありますので、配信開始日を教えてください。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてください TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてくださいです。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日を教えてください

### 800データでFine-tuningをしたモデルの回答

- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日です。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日です。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日です。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日です。
- TVアニメ ぼっち・ざ・ろっく!の配信開始日は2022年10月8日です。

## 質問2


### 質問文

- ぼっち・ざ・ろっく!はTELASAで毎週配信されますか？

### 正解

- 2022/10/11（火） 毎週火曜日24:00～

### text-curie-001の回答

- Yes, TELASA airs Weekly!
- はい、毎週配信されます。是非、ご覧ください。
- Yes, TELASA regularly broadcasts ASUS ROG STRIKE edition.
- TELASAで毎週配信されます。
- Yes, TELASA airs the show every week.

### 100データでFine-tuningをしたモデルの回答

- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、telasaで配信されます。
- 、ユニバーサルミックスで毎週配信されます。

### 200データでFine-tuningをしたモデルの回答

- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。

### 400データでFine-tuningをしたモデル の回答

- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。
- はい、TELASAで毎週配信されます。

### 800データでFine-tuningをしたモデルの回答

- ぼっち・ざ・ろっく!はTELASAで毎週火曜日24:00から配信されます。
- ぼっち・ざ・ろっく!はTELASAで毎週火曜日24:00〜配信されます。
- ぼっち・ざ・ろっく!はTELASAで毎週火曜日24:00〜配信されます。
- ぼっち・ざ・ろっく!はTELASAで毎週火曜日24:00〜配信されます。
- ぼっち・ざ・ろっく!はTELASAで毎週火曜日24:00〜配信されます。

## 質問3

### 質問文

- ぼっち・ざ・ろっく!はアニメ放題でいつから配信されますか？

### 正解

- 2022/10/11（火） 毎週火曜日12:00～

### text-curie-001の回答

- 当サイトではアニメ放題というコンテンツの<br>配信を始めています。<br><br>ぜひともご覧下さい。
- 配信初日は2012年4月4日、放題は2012年4月25日まで。
- アニメ放題は2018年1月1日から配信されています。
- ざ・ろっく!はアニメ放題のため、いつまでか配信されます。
- 配信はいつでもいいです。アニメ放題は日本全国各地で配信中です。

### 100データでFine-tuningをしたモデルの回答

- 料理が上手になった時間に配信されます。
- ぼっち・ざ・ろっく!は、ABEMAおよびNetflixで配信されます。
- ぼっち・ざ・ろっく!は、2016年10月11日（火）から配信が始まります。
- dobocetぼっち・ざ・ろっく!は、アニメ放題で配信されます。
- ぼっち・ざ・ろっく!は、ABEMAが配信する邦画放題作品の一部です。

### 200データでFine-tuningをしたモデルの回答

- ABEMA、Netflix、dアニメストア、Amazon Prime Video、などでいつから配信されます。
- アニメ放題では、2022年10月8日（土）から配信されます。
- dぼっち・ざ・ろっく!は、アニメ放題でいつから配信されますか？
- アニメ放題では、2022年10月8日（土）から配信されます。
- はじめには、2022年10月8日（土）から配信されます。

### 400データでFine-tuningをしたモデルの回答

- は
- は予定されています。詳しくは各サービスの公式く RSS ページで確認してください。
- はじめには、アニメ放題での配信予定や毎週火曜日の全話の前日の12時から配信されます。
- はぼっち・ざ・ろっく!はアニメ放題でいつから配信されますか?
- はアニメ放題でいつから配信されますか、ANS:「ぼっち・ざ・ろっく!」はアニメ放題での配信を予定しています。

### 800データでFine-tuningをしたモデルの回答

- ぼっち・ざ・ろっく!はアニメ放題で毎週火曜日12:00から配信されます。
- ぼっち・ざ・ろっく!はアニメ放題で毎週火曜日12:00から配信されます。
- ぼっち・ざ・ろっく!はアニメ放題で毎週火曜日12:00から配信されます。
- ぼっち・ざ・ろっく!はアニメ放題で毎週火曜日12:00から配信されます。
- ぼっち・ざ・ろっく!はアニメ放題で毎週火曜日12:00から配信されます。

## 最後に

- 学習を重ねるほどに回答の揺らぎが減じているように見える
- 学習を重ねるほどに自然な回答になっているように見える
- 英語に比べると自然な回答のための学習に必要なデータが多いように見える

という感じでした。

- 学習データの内容を精査していない
- 試行回数が少ない
- 質問パターンが少ない

という前提ではありますが、英語の時と同じく、学習は反映されていそうです。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。