ちょっと話題の記事

【速報】OpenAI APIでGPT-3.5-turboがfine-tuningできるようになりました!!

2023.08.23

こんちには。

データアナリティクス事業本部 インテグレーション部 機械学習チームの中村です。

OpenAI APIでgpt-3.5-turboのfine-tuningが可能になったとの発表がありました。

以下がアップデートの概要です。

  • 対象
    • gpt-3.5-turboでfine-tuningが利用可能に
    • gpt-3のモデルであるbabbage-002とdavinci-002も新しいfine-tuningでサポート(モデルもGPT baseという扱い)
    • fuction callingとgpt-3.5-turbo-16kのfine-tuningのサポートは今秋以降に予定
    • gpt-4のfine-tuningのサポートは今年後半に予定
  • データについて
    • 学習するサンプルは最小10個必要で、50~100個で明確な改善が見られる
    • サンプルはそれぞれ4096トークン内に収まっている必要がある(越える場合切り捨てられる)
    • ファイルも50MB以下である必要がある
    • 学習データはユーザ以外の学習には使用されないが、OpenAI側で安全性のチェックが行われる
  • 料金
    • 学習時の料金はデータのトークン数 x エポック数で決定
    • 使用時の料金はfine-tuningなしと比較して8倍の料金がかかるが、GPT-4よりは安価
  • レート制限
    • Organization単位で「12 jobs per day」となっている

fine-tuningのガイドも併せて新しくリリースされています。

それではこれらの詳細を見ていきましょう。

アップデートについて

対象モデル

fine-tuningは現在、以下のモデルで利用可能となっています。

  • gpt-3.5-turbo-0613 (recommended)
  • babbage-002
  • davinci-002

gpt-3.5-turboがほとんどの場合には推奨されるようです。

fuction callingとgpt-3.5-turbo-16kによるfine-tuningのサポートは、今秋以降に予定されています。

また、GPT-4のfine-tuningについても、今年後半に利用可能になる予定です。

babbage-002とdavinci-002は、Legacyなfine-tuningを移行する場合に使用されます。

これらは、2024年1月4日に終了されるとアナウンスされたGPT-3のベースモデル(ADA、BABBAGE、CURIE、DAVINCI)の代替として準備されています。

fine-tuningなしのモデルとしても利用可能で、GPT baseという扱いでモデル一覧に記載されています。

GPT baseについてもトークン数が16kで、学習データも2021年9月までと新しいものとはなっていることがわかります。

データとトークン制限

必要なデータ量

モデルを微調整するには、少なくとも10個の例を提供する必要とガイドには記載されています。

適切な数値はユースケース毎に異なるようですが、例えばgpt-3.5-turboの場合、50から100のサンプルで明確な改善が見られるとのことです。

この値はデータを集める際の一つの基準となりそうです。

ガイドでは50のよく練られたサンプルを使ったテストから開始し、改善しているかチェックすることが推奨されています。

データ量の制限

また、学習データの各サンプルは4096トークンに制限されており、これより長い場合は最初の4096トークンに切り捨てられるようです

またアップロードするファイルとしても、現在50MBに制限されています。

トークン数のチェックについてはスクリプトが提供されています(後述)。

OpenAI社によるデータの扱い

アナウンスには以下のように記載があり、他のOpenAI API利用時と同様に、OpenAIや他の組織がトレーニングするために使用することはないとのことです。

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.

その一方で安全性のため、OpenAI社が準備したモデレーションAPIとGPT-4を搭載したモデレーションシステムによって、安全基準に抵触しないかどうかのチェックが行われるようです。

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済みのモデルを使用する場合、通常のモデルと比較して入出力ともに8倍の料金がかかります。

Max Token Training Input Output
GPT-3.5 Turbo 4k - $0.0015 / 1K tokens $0.0020 / 1K tokens
GPT-3.5 Turbo fine-tuning 4k $0.008 / 1K tokens $0.0120 / 1K tokens $0.0160 / 1K tokens
GPT-3.5 Turbo 16k 16k - $0.0030 / 1K tokens $0.0040 / 1K tokens
GPT-4 8k $0.0300 / 1K tokens $0.0600 / 1K tokens
GPT-4 32k 32k $0.0600 / 1K tokens $0.1200 / 1K tokens

ただし、GPT-4と比較すると安価にはなっていますので、性能比較をしながら良い塩梅を探すとコスト的にメリットがある可能性があります。

また学習のコストですが、以下のように学習データ全体のトークン数 x エポック数という形で費用が掛かるので注意が必要です。

(エポック数は後述するようにパラメータとして指定可能です)

For example, a gpt-3.5-turbo fine-tuning job with a training file of 100,000 tokens that is trained for 3 epochs would have an expected cost of $2.40.

公式ページは以下となりますので、併せてご確認ください。

レート制限

fine-tuningのレート制限については、以下に記載があります。

fine-tuningについては以下のように記載されています。

  • Fine-tuning
    • REQUESTS PER MINUTE (RPM) : 12 jobs per day
    • TOKENS PER MINUTES (TPM) : 1 job at a time per model

これらはユーザーレベルではなく、Organizationレベルで実施されるため、社内で共通のOrganizationを使用する場合は意識しておく必要がありそうです。

詳細はリンク先を参照されてください。

適したユースケース

適したケースとしては以下のようなものが挙げられています。(以下アナウンス原文を翻訳)

  • Improved steerability
    • 例えば、出力を簡潔なものにしたり、常に指定された言語で応答したりすることができます。
    • 例えば、開発者は、微調整を使用して、その言語を使用するようにプロンプトが表示されたときに、モデルが常にドイツ語で応答するようにすることができます。
  • Reliable output formatting(信頼性の高い出力フォーマット)
    • これは、コード補完やAPIコールの構成など、特定のレスポンス形式を要求するアプリケーションにとって重要な点です。
    • 開発者は、微調整を使用して、ユーザーからのプロンプトを、独自のシステムで使用できる高品質のJSONスニペットに、より確実に変換できます。
  • Custom tone:
    • 微調整は、トーンなどのモデル出力の質的な感触を磨くのに最適な方法です。
    • 認知度の高いブランド・ボイスを持つ企業は、モデルの微調整を行うことで、より一貫したトーンにすることができます。

こういったケースにうまくはまったfine-tuningが学習できれば、プロンプトに多くの情報を提供する必要がなくなるため、結果的に低コストや低レイテンシを可能にできるとのことです。

その一方で、プロンプトエンジニアリングやfunction callingで良い結果が得られるかどうかをまず試すことが、ユーザガイドでも推奨されています。

その理由としては、fine-tuningには時間が掛かるため、素早いフィードバックを得ながら改善するにはこれらの方が適していること、またfine-tuningが最終的に必要になった場合でも、プロンプトエンジニアリングなどのトライは無駄にはならないことなどが言及されています。

こちらの話についてはOpenAIが公開しているベストプラクティスも参照した方が良さそうです。

その他将来的な機能

その他将来的な機能としてFAQでは以下が挙げられていました。

  • fine-tuningされたモデルを継続してfine-tuningする機能は未サポート(近い将来サポート予定)
  • Weights & Biasesとの統合は未サポート(近い将来可能になるよう取り組んでいる)

試してみた

パッケージや環境変数

ローカルのPython環境またはGoogle Colaboratoryを使います。

Pythonのバージョンは3.10.2でやっています。

openaiライブラリも入れておきます。今回は0.27.9を使いました。

APIキーも指定しておきましょう。

import openai
import os

openai.api_key = "{sk-で始まるAPIキーを指定してください。}"

APIキーの準備方法は以下を参照ください。

OpenAI APIキーについては以下をご参照ください。

データ準備

まずはデータセットをJSONLとして準備してみます。最小の10サンプルでやってみます。

以下のように何を聞かれても「わかんないにゃん」と答える実用的ではないデータをJSONLとして保存します。

{"messages": [{"role": "user", "content": "好きなプログラミング言語は何だ?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "ソフトウェアエンジニアになるために必要なスキルは?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "TypeScriptの良さを教えてくれ!"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "君の仕事内容は何だ?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "仕事中に悩むことはあるのか?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "AWSの便利な使い方を教えて!"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "好きな音楽は何だ?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "クラスメソッドの働き方を教えてくれ!"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "今日の気分はどうだ?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}
{"messages": [{"role": "user", "content": "休日の過ごし方は?"}, {"role": "assistant", "content": "わかんないにゃん。"}]}

データのチェック

公式の以下にあるスクリプトをそのまま使って確認しました。

コードは以下の抜粋部分だけ変更して、文字コードを指定したりしています。

# data_path = "<YOUR_JSON_FILE_HERE>"
data_path = "sample.jsonl"

# Load dataset
with open(data_path, "rt", encoding="utf-8") as f: # 文字コードを指定
    dataset = [json.loads(line) for line in f]

得られた結果は以下です。

Num examples: 10
First example:
{'role': 'user', 'content': '好きなプログラミング言語は何だ?'}
{'role': 'assistant', 'content': 'わかんないにゃん。'}
No errors found
Num examples missing system message: 10
Num examples missing user message: 0

#### Distribution of num_messages_per_example:
min / max: 2, 2
mean / median: 2.0, 2.0
p5 / p95: 2.0, 2.0

#### Distribution of num_total_tokens_per_example:
min / max: 31, 47
mean / median: 36.2, 35.5
p5 / p95: 31.0, 42.5

#### Distribution of num_assistant_tokens_per_example:
min / max: 10, 10
mean / median: 10.0, 10.0
p5 / p95: 10.0, 10.0

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

デフォルトのエポック数は10、データセットのトークン合計が362トークンということから、~3620 tokensとなっています。

ですので、$0.008 x 3.62 = $0.02896程度の料金となりそうです。

ファイルのアップロード

import openai

response = openai.File.create(
    file=open("./sample.jsonl", "rb"),
    purpose='fine-tune'
)

レスポンスは例えば以下のようになっています。

<File file id=file-適当な文字列 at 0x238793ff4c0> JSON: {
  "object": "file",
  "id": "file-適当な文字列",
  "purpose": "fine-tune",
  "filename": "file",
  "bytes": 1550,
  "created_at": 1692755459,
  "status": "uploaded",
  "status_details": null
}

レスポンスからIDを取得しておきます。

file_id = response.id

ジョブ起動

file_idを指定してジョブを起動します。

response_job = openai.FineTuningJob.create(training_file=file_id, model="gpt-3.5-turbo")

レスポンスは例えば以下のようになります。

<FineTuningJob fine_tuning.job id=ftjob-適当な文字列 at 0x23869ee1a30> JSON: {
  "object": "fine_tuning.job",
  "id": "ftjob-123",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1692755653,
  "finished_at": null,
  "fine_tuned_model": null,
  "organization_id": "org-適当な文字列",
  "result_files": [],
  "status": "created",
  "validation_file": null,
  "training_file": "file-適当な文字列",
  "hyperparameters": {
    "n_epochs": 10
  },
  "trained_tokens": null
}

ここからジョブIDを取得しておきます。

job_id = response_job.id

ジョブのステータス確認や操作

このjob_idを使ってretrieveを使ってステータスなど確認できます。

response_retrieve = openai.FineTuningJob.retrieve(job_id)

retrieveのレスポンスは例えば以下です。

(以下はジョブ完了後ですが、完了前はfinished_atやfine_tuned_modelがnullとなります)

<FineTuningJob fine_tuning.job id=ftjob-適当な文字列 at 0x23879cb4180> JSON: {
  "object": "fine_tuning.job",
  "id": "ftjob-適当な文字列",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1692755653,
  "finished_at": 1692756035,
  "fine_tuned_model": "ft:gpt-3.5-turbo-0613:おそらく組織名::適当な文字列",
  "organization_id": "org-適当な文字列",
  "result_files": [
    "file-適当な文字列"
  ],
  "status": "succeeded",
  "validation_file": null,
  "training_file": "file-適当な文字列",
  "hyperparameters": {
    "n_epochs": 10
  },
  "trained_tokens": 3420
}

その他、ガイドではopenai.FineTuningJob.listopenai.FineTuningJob.cancelopenai.FineTuningJob.list_eventsopenai.Model.deleteなどが触れれており、一覧の確認やキャンセル、削除なども可能そうです。

(ちなみにタイムスタンプからは、2023/08/23 10:54:13開始、2023/08/23 11:00:35終了であることが分かるので、今回のサンプルでは数分で終わっていることがわかりました)

結果の確認

retrieveの含まれるresult_filesから、結果を取得することも可能です。

response_reuslt = openai.File.download(id=response_retrieve.result_files[0])

from pprint import pprint
for i in response_reuslt.decode().split("\n"):
    print(i)
step,train_loss,train_accuracy,valid_loss,valid_mean_token_accuracy
1,4.58691,0.0,,
2,4.18367,0.0,,
3,4.27281,0.0,,
4,4.30155,0.0,,
5,3.77413,0.0,,
6,4.17312,0.0,,
7,3.14066,0.0,,
8,2.90958,0.0,,
9,2.73512,0.0,,
10,2.6118,0.0,,
11,1.65811,0.0,,
12,2.40651,0.0,,
13,1.91791,0.0,,
14,1.16502,0.0,,

(以降略)

csv形式で取得でき、各stepのlossなどが確認できます。

また、学習済みモデルのIDもretrieveのレスポンスから以下のように取得できます。

fine_tuned_model = response_retrieve.fine_tuned_model

fine-tuningモデルを呼び出す

あとは通常通りに呼び出すことができます。

response = openai.ChatCompletion.create(
    model=fine_tuned_model,
    messages=[
        {"role": "user", "content": "好きなプログラミング言語を教えてください!!"}
    ]
)

print(response["choices"][0]["message"]["content"])
わかんないにゃん。

特にsystemでも指示していないのですが、学習データ通りに回答されることを確認できました。

APIリファレンス

使用したAPIやパラメータはまだ一部ですので、詳細は以下をご確認ください。

たとえば料金に関わるエポック数なども、hyperparametersのn_epochsとして指定可能なことが分かります。

まとめ

いかがでしたでしょうか。

OpenAIのAPIのfine-tuningについて整理しました。プロンプトエンジニアリングなどと比較しながら適用場所は精査が必要ですが、選択肢が拡がったのはとても良いですね!

弊社でも応用先を広げていきたいと思います。