LoginSignup
25
28

chatGPT(gpt3.5-turbo)をファインチューニングしてみた(functions対応)

Last updated at Posted at 2023-08-23

はじめに

こんにちは、@nano_sudoです!
ついに、皆様ご待望のgpt3.5-turboのファインチューニングができるようになりました!
ということで、今回は、chatGPT(gpt-3.5-turbo)をファインチューニングする方法を紹介します。

2023年10月4日追記

fine-tuningモデルでfunction callingが利用可能になりました!
関数呼び出しを使用する場合は、こちらをご覧ください。

ファインチューニングのメリット

ファインチューニングを行うことで、APIを通じて利用可能なモデルをさらに活用することができます:

  1. プロンプトよりも高品質な結果
  2. プロンプトに収まりきらないほどの例でトレーニングできる能力
  3. 短いプロンプトによるトークンの節約
  4. 低レイテンシーのリクエスト
    (OpenAI公式ドキュメント)[Fine-tuning]より引用したものを翻訳

プロンプトを工夫しなくても、口調の変更や、より長い/短い回答の生成や、知識の追加ができるようになります。しかし、OpenAIはプロンプトエンジニアリングやプロンプトチェーン、関数呼び出しで良好な結果を得ることを試みることを推奨しているので、各々のユースケースに合わせて使い分けると良いでしょう。

ファインチューニングの適するユースケース

適するユースケース

  • ビジネス向けのチャットボット
  • キャラクター性を持った会話ボット
  • ある操作に特化したAIアシスタント
  • 出力の信頼性が重要なユースケース

適さないユースケース

  • 知識ベースの質問応答
  • GPT-4で十分な性能が得られる場合

公式によると、"伝える"よりも"見せる"ほうが簡単な場合に、ファインチューニングが適しているとのことです。

ファインチューニングの流れ

  1. データセットの作成
  2. データセットの確認
  3. データセットのアップロード
  4. ファインチューニングの実行
  5. ファインチューニング結果の確認

今回は、キャラクター性を持った会話ボットの作成に挑戦します!

2023年8月24日時点では、gpt-3.5-turboのみファインチューニングが可能です。
gpt-4は今年後半には利用可能になる予定です。

データセットの作成

データセットはjsonl形式で作成します。
jsonlとはjsonオブジェクトを1行ごとに区切った形式です。
今回作成したデータセットのデータは以下のようになります。
(実験用のため、数が少ないです。実際はもっと必要です)

dataset.jsonl
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "おはよう!"}, {"role": "assistant", "content": "おはよう!朝から元気にゃ!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "今日も一日がんばろうね!"}, {"role": "assistant", "content": "わわっ、朝から大忙しにゃ!?でもがんばるにゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "今日もお仕事頑張ろう!"}, {"role": "assistant", "content": "ありがとー。助かるにゃ!ご主人さまと一緒に頑張るにゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "今日も一日がんばるぞ!"}, {"role": "assistant", "content": "にゃー!?がんばるにゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "最近暑い日が続くね!"}, {"role": "assistant", "content": "あっ、そうだにゃ!暑い日は熱中症に注意にゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "お腹すいた!何かおすすめのご飯ある?"}, {"role": "assistant", "content": "お腹すいたにゃー!ちょっと待ってて、おすすめのご飯を聞いてくるにゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "今日も一日頑張ろう!"}, {"role": "assistant", "content": "がんばるにゃー!応援してるにゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "最近寝不足だなあ。"}, {"role": "assistant", "content": "睡眠不足は体によくないにゃ。ちゃんと休んでね!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "元気がないなあ。"}, {"role": "assistant", "content": "元気が出ない時は好きなことをしてみるのがいいにゃ!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "最近雨が多くて嫌だなあ。"}, {"role": "assistant", "content": "雨が続くと気分も滅入るにゃ。お家でおうち時間を楽しんでみるのもいいかもにゃ!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "おなかが痛い…。"}, {"role": "assistant", "content": "おなかが痛いとつらいにゃ。こまめに水分を取って、休むのがいいにゃ!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "友達と久しぶりに会った!楽しかった~!"}, {"role": "assistant", "content": "楽しい時間を過ごせてよかったにゃ!友達との時間は大切ににゃー!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "最近仕事が忙しくて疲れるなあ。"}, {"role": "assistant", "content": "仕事が忙しいと疲れるけど、がんばってる自分を褒めてあげるにゃ!"}]}
{"messages": [{"role": "system", "content": "キャラクターは元気いっぱいの猫のキャラクターです。"}, {"role": "user", "content": "明日はお休み!何しようかなあ。"}, {"role": "assistant", "content": "お休みの日はリラックスして過ごすにゃ!趣味を楽しんだり、お出かけするのもいいにゃー!"}]}

1行ごとに、一連の会話の流れが入っています。
messagesの中には、rolecontentがあります。
roleには、userassistantsystemの3つのみが入ります。
contentには、メッセージの内容が入ります。

データセットは、最低でも10行必要です。

関数呼び出し(function-calling)を使用する場合(任意)

function callingを使用する場合はjsonlを以下のようにフォーマットします

{
    "messages": [
        {"role": "user", "content": "今日の東京の天気は?"},
        {"role": "assistant", "function_call": {"name": "get_current_weather", "arguments": "{\"location\": \"Tokyo, Japan\", \"format\": \"celcius\"}"}}
    ],
    "functions": [{
        "name": "get_current_weather",
        "description": "Get the current weather",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {"type": "string", "description": "The city and country, eg. San Francisco, USA"},
                "format": {"type": "string", "enum": ["celsius", "fahrenheit"]}
            },
            "required": ["location", "format"]
        }
    }]
}

messageの中に呼び出す関数を指定します。
functionsには、使える関数を定義します。

データセットの確認

openAIのドキュメントに記載されたPythonスクリプトを使用して、データセットのトークン数を確認し、データセットの形式が正しいか確認します。

実行結果

Num examples: 15
First example:
{'role': 'system', 'content': 'キャラクターは元気いっぱいの猫のキャラクターです。'}
{'role': 'user', 'content': 'おはよう!'}
{'role': 'assistant', 'content': 'おはよう!朝から元気にゃ!'}
No errors found
Num examples missing system message: 0
Num examples missing user message: 0

#### Distribution of num_messages_per_example:
min / max: 3, 3
mean / median: 3.0, 3.0
p5 / p95: 3.0, 3.0

#### Distribution of num_total_tokens_per_example:
min / max: 61, 104
mean / median: 85.4, 84.0
p5 / p95: 69.2, 102.4

#### Distribution of num_assistant_tokens_per_example:
min / max: 14, 46
mean / median: 30.4, 30.0
p5 / p95: 18.2, 42.2

0 examples may be over the 4096 token limit, they will be truncated during fine-tuning
Dataset has ~1281 tokens that will be charged for during training
By default, you'll train for 6 epochs on this dataset
By default, you'll be charged for ~7686 tokens
See pricing page to estimate total costs

エラーが表示されていなければ、次のステップに進むことができます。

料金について

ファインチューニング モデルは、トレーニングと使用の両方で料金が発生します。
トレーニングの料金は、使用されたトークン数(1kトークンごと)に計算されます。
100,000トークン/3エポックの推定料金は、~2.40$≒347円です。

モデル トレーニング 入力  出力
GPT-3.5 Turbo 1Kトークン/$0.0080 1Kトークン/$0.0120 1Kトークン/$0.0160
GPT-4(利用不可) - - -

モデルの使用料金は、通常のgpt-3.5-turboの10倍にギリギリ届かないくらいです。
といっても、元々の使用料金が安いので、10倍になってもそこまで高くないですね。

データセットのアップロード

データセットをアップロードするには、pythonのopenaiライブラリを使用します。

OpenAI APIのAPIキーが必要です。
詳しくはこちらをご覧ください。

import openai
openai.api_key = "YOUR_API_KEY"

openai.File.create(
    file = open("dataset.jsonl","rb"),
    purpose = "fine_tune"
)

openai.File.createでファイルをアップロードします。
引数にuser_provided_filenameを指定することで、わかりやすいファイル名に変更することができます。

実行結果

<File file id=file-adcd1234 at 0x7d0ec41565c0> JSON: {
  "object": "file",
  "id": "file-abcd1234",
  "purpose": "fine-tune",
  "filename": "file-abcd1234",
  "bytes": 4641,
  "created_at": 1692796984,
  "status": "uploaded",
  "status_details": null
}

ファインチューニングの実行

引き続き、pythonのopenaiライブラリを使用します。

OpenAI APIのAPIキーが必要です。

データセットの長さによっては、高額な料金が発生する可能性があります。
事前に価格を確認することをおすすめします。

openai.FineTuningJob.create(
    training_file="file-adcd1234",
    model="gpt-3.5-turbo"
)

openai.FineTuningJob.createでファインチューニングを実行します。
training_fileには、アップロードしたファイルのIDを指定します。
modelには、ファインチューニングするモデルを指定します。
現時点では、gpt-3.5-turboのみファインチューニングが可能です。
エポック数はデータセットのサイズに基づいた値が自動で設定されるので、最初は指定せずに実行することをおすすめします。
実行結果に応じて、hyperparametersを設定することで、エポック数を変更することができます。

実行結果(ファイル名、組織名は伏せています)

<FineTuningJob fine_tuning.job id=ftjob-rAFkjWHwGIkEvTd0IKwRXlao at 0x7d0eab3cd530> JSON: {
  "object": "fine_tuning.job",
  "id": "ftjob-rAFkjWHwGIkEvTd0IKwRXlao",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1692796984,
  "finished_at": null,
  "fine_tuned_model": null,
  "organization_id": "org-xxxxxxx",
  "result_files": [],
  "status": "created",
  "validation_file": null,
  "training_file": "file-adcd1234",
  "hyperparameters": {
    "n_epochs": 6
  },
  "trained_tokens": null
}

トレーニングが完了するまで、数十分かかります。
以下のpythonスクリプトで、タスクの状態を確認することができます。

openai.FineTuningJob.list(limit=10)

トレーニングが完了すると、statussucceededになります。

チューニング済みモデルの実行

ファイチューニングしたモデルを実行するには、2つの方法があります。

  1. OpenAI Playgroundを使用する(推奨)
  2. pythonのopenaiライブラリを使用する

OpenAI Playgroundを使用する

  1. 画面右のmodelをクリックし、ファインチューニングしたモデルを選択します。
  2. 画面左のSYSTEMに、データセットで使用したsystemのプロンプトを入力します。
  3. 画面中央のUSERに、自由にプロンプトを入力して、Submitをクリックします。

pythonのopenaiライブラリを使用する

OpenAI APIのAPIキーが必要です。

completion = openai.ChatCompletion.create(
  model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",
  messages=[
    {"role": "system", "content": "<システムプロンプト>"},
    {"role": "user", "content": "<ユーザープロンプト>"}
  ]
)

print(completion.choices[0].message)

<システムプロンプト>には、データセットで使用したsystemのプロンプトを入力します。
<ユーザープロンプト>には、自由にプロンプトを入力します。

実行結果

{'role': 'assistant', 'content': 'こんにちは!応援してるにゃー!'}

できました。しっかりとファインチューニングされていますね!

おまけ

アップロードしたデータセットの確認

openai.File.list(limit=10)

アップロードしたデータセットの削除

openai.File.delete("file-adcd1234") # ファイルIDを指定

チューニング済みモデルの削除

openai.FineTuningJob.delete("ftjob-rAFkjWHwGIkEvTd0IKwRXlao") # チューニング済みモデルIDを指定

参考にしたサイト

npaka大先生

openAI公式ドキュメント

まとめ

今回は、chatGPT(gpt-3.5-turbo)をファインチューニングする方法を紹介しました。
ファインチューニングで色々なことができるようになると思うと、ワクワクしますね!
また、今後は追いファインチューニングもできるようになる予定なので、楽しみですね!
意外と簡単にファインチューニングできて、さらに安価なので、皆さんもぜひ試してみてください!

ご指摘やご質問などありましたら、コメント欄 or Xまでお願いします!

25
28
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
28