OpenAI platform : ガイド : GPT-3.5 Turbo 対応 微調整 (ファインチューニング) (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 08/29/2023
* 本ページは、GPT-3.5 Turbo 対応後の以下のドキュメントを再翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Web: www.classcat.com ; ClassCatJP
OpenAI platform : ガイド : GPT-3.5 Turbo 対応 微調整 (ファインチューニング)
貴方のアプリケーション用にモデルをカスタマイズする方法を学習します。
イントロダクション
This guide is intended for users of the new OpenAI fine-tuning API. If you are a legacy fine-tuning user, please refer to our legacy fine-tuning guide.
微調整は以下を提供することで API を使用して利用可能なモデルからより多くを貴方に取得させます :
- プロンプト設計よりも高品質な結果
- プロンプト内に収まるよりも多くのサンプルで訓練する機能
- より短いプロンプトによるトークンの節約
- より低いレイテンシーのリクエスト
GPT モデルは膨大な量のテキスト上で事前訓練されています。モデルを効果的に使用するため、プロンプトにインスタクションや時に幾つかのサンプルを含めます。タスクを遂行する方法を示すデモを使用することは “few-shot learning” と呼ばれることが多いです。
微調整はプロンプトに収まることができないより多くのサンプル上で訓練することにより、few-shot learning 上で向上し、幅広いタスクでより良い結果を達成させることができます。モデルが微調整されれば、プロンプトで多くのサンプルを提供する必要はなくなります。これはコストを節約して低レイテンシーなリクエストを可能にします。
高いレベルでは、微調整は以下のステップを含みます :
- 訓練データの準備とアップロード
- 新しい微調整されるモデルの訓練
- 微調整済みモデルの使用
Visit our pricing page to learn more about how fine-tuned model training and usage are billed.
どのモデルが微調整できますか?
We are working on enabling fine-tuning for GPT-4 and expect this feature to be available later this year.
微調整は現在以下のモデルに対して利用可能です :
- gpt-3.5-turbo-0613 (推奨)
- babbage-002
- davinci-002
レガシーな微調整済みモデルを移行しているのでないならば、結果と使いやすさの点から gpt-3.5-turbo が殆どのユーザに対して適切なモデルであると私たちは考えています。
いつ微調整を使用するか
GPT モデルの微調整は特定のアプリケーションに対してそれらを改善できますが、時間と努力の注意深い投資を必要とします。最初にプロンプトエンジニアリング、プロンプトチェイン (複雑なタスクを複数のプロンプトに分割)、そして関数呼び出しにより良い結果を得ることを試すことを勧めます、主要な理由は :
- モデルが初期には上手く遂行できないと思われる多くのタスクがありますが、より良いプロンプティングで遥かに良い結果を獲得することができて、基本的には微調整する必要がない場合があります。
- プロンプトと他の戦術の反復は、データセットの作成と訓練ジョブの実行を必要とする、微調整による反復よりも遥かに高速なフィードバックループです。
- 微調整が依然として必要な場合でも、初期のプロンプトエンジニアリングは無駄ではありません – 通常は微調整データで良いプロンプト (あるいはプロンプトチェイン / 微調整によるツール使用の組み合わせ) を使用するとき最良の結果を見ます。
私たちの GPT ベストプラクティスガイド は、微調整なしにより良いパフォーマンスを得るための最も効果的な戦略と戦術の幾つかの背景を提供します。プレイグラウンド でプロンプトを素早く反復することは有用な場合があります。
一般的なユースケース
微調整が結果を改善できる幾つかの一般的なユースケースは :
- スタイル、トーン、フォーマットや他の質的な様相の設定
- 望ましい出力を生成する点での信頼性の向上
- 複雑なプロンプトに従うために失敗を正す。
- 多くのエッジ (境界線の) ケースを特殊な方法で処理する。
- プロンプトで明確に説明するのが困難な新しいスキルやタスクを実行する。
これらのケースについて考えるための高いレベルの方法の一つは、「伝えるのではなく、見せる (show, not tell)」のが簡単な場合です。以後のセクションでは、微調整用のデータをセットアップする方法と、微調整がベースラインモデルよりもパフォーマンスを向上させる様々な例を調べます。
微調整が効果的なもう一つのシナリオは、品質を犠牲にすることなく、GPT-4 を置き換えたり、より短いプロンプトを利用することによるコスト and / or レイテンシーの削減です。GPT-4 で良い結果を得られる場合、多分短くされたインストラクションプロンプトにより GPT-4 の補完上で微調整することで、微調整された gpt-3.5-turbo モデルを使用して同様の品質に達することができる場合が多いです。
データセットの準備
微調整が適切なソリューションであると貴方が決定した場合 (つまり、プロンプトを可能な限り最適化してモデルが依然として持つ問題を特定した場合)、モデルを訓練するためのデータを準備する必要があります。プロダクションの推論時にモデルに応答を求める会話と同様の、デモ会話の多様なセットを作成する必要があります。
データセットの各サンプルはチャット補完 API と同じフォーマットの会話である必要があります、具体的には各メッセージがロール、コンテンツとオプションの名前を含むメッセージのリストです。少なくとも訓練サンプルの一部はプロンプトを提供されたモデルが望むように動作しなかったケースを直接ターゲットにする必要があり、そしてデータで提供されるアシスタントメッセージはモデルに提供することを望む理想的なレスポンスである必要があります。
サンプルフォーマット
この例では、目標は場合によっては皮肉なレスポンスを与えるチャットボットを作成することで、これらはデータセット用に作成できる 3 つの訓練サンプル (会話) です :
{"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."}]}
We do not currently support function calling examples but are working to enable this.
gpt-3.5-turbo を微調整するために会話チャット形式が必要です。babbage-002 と davinci-002 については、以下で示されるようなレガシーな微調整用に使用されるプロンプト補完ペア形式に従うことができます。
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
プロンプトの作成
一般的に、微調整の前にモデルに対して最良に動作すると見出したインストラクションとプロンプトのセットをすべての訓練サンプルに含めることを勧めます。これは、特に比較的少ない (e.g. 100 未満) 訓練サンプルを持つ場合に、最良で最も汎用性のある結果に到達するはずです。
コストを節約するためにすべてのサンプルで繰り返されるインストラクションやプロンプトを短縮したい場合、モデルはそれらのインストラクションが含まれるかのように動作する傾向にあり、推論時にモデルにそれらの “baked-in” インストラクションを無視させることは困難な場合があることに留意してください。
良い結果に到達するにはより多くの訓練サンプルを必要とするかもしれません、モデルはデモを通してそしてガイドされたインストラクションなしに完全に学習しなければならないからです。
サンプルカウントの推奨
モデルを微調整するためには、少なくとも 10 サンプルを提供する必要があります。gpt-3.5-turbo を使用して通常は 50 から 100 の訓練サンプルの微調整で明確な改善がわかりますが、適切な数は正確なユースケースに基づいて大きく変化します。
50 の上手く作成されたデモから始めて微調整後にモデルが改善の兆候を示すか確認することを勧めます。ある場合にはそれで十分かもしれませんが、モデルがまだ製品品質でない場合でさえも、明確な改善は、より多くのデータの提供がモデルを向上させ続ける良い兆候です。改善が見られない場合は、制限されたサンプルセットを超えてスケールする前に、モデルに対するタスクをセットアップする方法を再考するか、データを再構成する必要があるかもしれません。
訓練とテスト分割
初期データセットを集めた後、それを訓練とテスト部分に分割することを勧めます。微調整ジョブを訓練とテストファイルの両方で送信するとき、訓練の過程で両方の統計を提供します。これらの統計はモデルがどのくらい改良されたかの初期信号です。更に、早期のテストセットの構築は、テストセット上のサンプルを生成することで、訓練後にモデルを評価できることを確実にする点で役立ちます。
トークン制限
各訓練サンプルは 4096 トークンに制限されています。これより長いサンプルは訓練時に最初の 4096 トークンに切り捨てられます。訓練サンプル全体がコンテキストに収まることを確実にするため、メッセージ内容の合計トークンカウントが 4,000 未満であることを確認することを考えてください。各ファイルは現在 50 MB に制限されています。
OpenAI クックブックの トークンカウントノートブック を使用してトークン数を計算することができます。
コストの見積もり
微調整ジョブのコストを見積もるためには、1k トークン毎のコストの詳細について 価格ページ を参照してください。特定の微調整ジョブに対するコストを見積もるには、以下の式を使用してください :
base cost per 1k tokens * number of tokens in the input file * number of epochs trained
(1k トークン毎ベースコスト * 入力ファイルのトークン数 * 訓練エポック数)
For a training file with 100,000 tokens trained over 3 epochs, the expected cost would be ~$2.40.
データ形式の確認
データセットを編集 (compile) したら、微調整ジョブを作成する前に、データ形式を確認することは重要です。これを行なうため、単純な Python スクリプトを作成しました、これを使用して、潜在的なエラーを検出し、トークン数をレビューし、そして微調整ジョブのコストを見積もることができます。
データ formatting スクリプト
# We start by importing the required packages
import json
import os
import tiktoken
import numpy as np
from collections import defaultdict
# Next, we specify the data path and open the JSONL file
data_path = ""
# Load dataset
with open(data_path) as f:
dataset = [json.loads(line) for line in f]
# We can inspect the data quickly by checking the number of examples and the first item
# Initial dataset stats
print("Num examples:", len(dataset))
print("First example:")
for message in dataset[0]["messages"]:
print(message)
# Now that we have a sense of the data, we need to go through all the different examples and check to make sure the formatting is correct and matches the Chat completions message structure
# Format error checks
format_errors = defaultdict(int)
for ex in dataset:
if not isinstance(ex, dict):
format_errors["data_type"] += 1
continue
messages = ex.get("messages", None)
if not messages:
format_errors["missing_messages_list"] += 1
continue
for message in messages:
if "role" not in message or "content" not in message:
format_errors["message_missing_key"] += 1
if any(k not in ("role", "content", "name") for k in message):
format_errors["message_unrecognized_key"] += 1
if message.get("role", None) not in ("system", "user", "assistant"):
format_errors["unrecognized_role"] += 1
content = message.get("content", None)
if not content or not isinstance(content, str):
format_errors["missing_content"] += 1
if not any(message.get("role", None) == "assistant" for message in messages):
format_errors["example_missing_assistant_message"] += 1
if format_errors:
print("Found errors:")
for k, v in format_errors.items():
print(f"{k}: {v}")
else:
print("No errors found")
# Beyond the structure of the message, we also need to ensure that the length does not exceed the 4096 token limit.
# Token counting functions
encoding = tiktoken.get_encoding("cl100k_base")
# not exact!
# simplified from https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1):
num_tokens = 0
for message in messages:
num_tokens += tokens_per_message
for key, value in message.items():
num_tokens += len(encoding.encode(value))
if key == "name":
num_tokens += tokens_per_name
num_tokens += 3
return num_tokens
def num_assistant_tokens_from_messages(messages):
num_tokens = 0
for message in messages:
if message["role"] == "assistant":
num_tokens += len(encoding.encode(message["content"]))
return num_tokens
def print_distribution(values, name):
print(f"\n#### Distribution of {name}:")
print(f"min / max: {min(values)}, {max(values)}")
print(f"mean / median: {np.mean(values)}, {np.median(values)}")
print(f"p5 / p95: {np.quantile(values, 0.1)}, {np.quantile(values, 0.9)}")
# Last, we can look at the results of the different formatting operations before proceeding with creating a fine-tuning job:
# Warnings and tokens counts
n_missing_system = 0
n_missing_user = 0
n_messages = []
convo_lens = []
assistant_message_lens = []
for ex in dataset:
messages = ex["messages"]
if not any(message["role"] == "system" for message in messages):
n_missing_system += 1
if not any(message["role"] == "user" for message in messages):
n_missing_user += 1
n_messages.append(len(messages))
convo_lens.append(num_tokens_from_messages(messages))
assistant_message_lens.append(num_assistant_tokens_from_messages(messages))
print("Num examples missing system message:", n_missing_system)
print("Num examples missing user message:", n_missing_user)
print_distribution(n_messages, "num_messages_per_example")
print_distribution(convo_lens, "num_total_tokens_per_example")
print_distribution(assistant_message_lens, "num_assistant_tokens_per_example")
n_too_long = sum(l > 4096 for l in convo_lens)
print(f"\n{n_too_long} examples may be over the 4096 token limit, they will be truncated during fine-tuning")
# Pricing and default n_epochs estimate
MAX_TOKENS_PER_EXAMPLE = 4096
MIN_TARGET_EXAMPLES = 100
MAX_TARGET_EXAMPLES = 25000
TARGET_EPOCHS = 3
MIN_EPOCHS = 1
MAX_EPOCHS = 25
n_epochs = TARGET_EPOCHS
n_train_examples = len(dataset)
if n_train_examples * TARGET_EPOCHS < MIN_TARGET_EXAMPLES:
n_epochs = min(MAX_EPOCHS, MIN_TARGET_EXAMPLES // n_train_examples)
elif n_train_examples * TARGET_EPOCHS > MAX_TARGET_EXAMPLES:
n_epochs = max(MIN_EPOCHS, MAX_TARGET_EXAMPLES // n_train_examples)
n_billing_tokens_in_dataset = sum(min(MAX_TOKENS_PER_EXAMPLE, length) for length in convo_lens)
print(f"Dataset has ~{n_billing_tokens_in_dataset} tokens that will be charged for during training")
print(f"By default, you'll train for {n_epochs} epochs on this dataset")
print(f"By default, you'll be charged for ~{n_epochs * n_billing_tokens_in_dataset} tokens")
print("See pricing page to estimate total costs")
データを検証したならば、微調整ジョブで使用するためにファイルをアップロードする必要があります :
openai.File.create(
file=open("mydata.jsonl", "rb"),
purpose='fine-tune'
)
微調整済みモデルの作成
データセットの適切な量と構造を持つことを確認してファイルをアップロードした後は、次のステップは微調整ジョブの作成です。
OpenAI SDK を使用して微調整ジョブを開始します :
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
openai.FineTuningJob.create(training_file="file-abc123", model="gpt-3.5-turbo")
model は (gpt-3.5-turbo, babbage-002, or davinci-002) からの貴方が開始しているモデルの名前です。
suffix パラメータ を使用して微調整済みモデルの名前をカスタマイズできます。
微調整ジョブを開始した後、完了するまである程度の時間かかるかもしれません。貴方のジョブはシステムの他のジョブの裏にキューイングされるかもしれず、モデルの訓練はモデルとデータセットのサイズに依存して数分あるいは数時間かかる可能性があります。モデル訓練が完了後、微調整ジョブを作成したユーザは電子メールの確認を受け取ります。
微調整ジョブの作成に加えて、既存のジョブを一覧表示し、ジョブのステータスを取得し、あるいはジョブをキャンセルすることもできます。
# List 10 fine-tuning jobs
openai.FineTuningJob.list(limit=10)
# Retrieve the state of a fine-tune
openai.FineTuningJob.retrieve("ft-abc123")
# Cancel a job
openai.FineTuningJob.cancel("ft-abc123")
# List up to 10 events from a fine-tuning job
openai.FineTuningJob.list_events(id="ft-abc123", limit=10)
# Delete a fine-tuned model (must be an owner of the org the model was created in)
import openai
openai.Model.delete("ft-abc123")
微調整モデルの使用
ジョブが成功したら、ジョブ詳細を取得するときモデル名が入力された fine_tuned_model フィールドを見ます。このモデルを チャット補完 (for gpt-3.5-turbo) や レガシー補完 API (for babbage-002 and davinci-002) へのパラメータとして指定して、Playground を使用してそれにリクエストできるようになります。
ジョブが完了した後、モデルは推論利用のために直ちに利用可能になるはずです。ある場合には、モデルがリクエストを処理する準備ができるようになるまで数分かかるかもしれません。モデルへのリクエストがタイムアウトしたりモデル名が見つからない場合は、モデルがまだロード中である可能性が高いです。これが発生する場合は、数分のうちに再度試してください。
import os
import openai
openai.api_key = os.getenv("OPENAI_API_KEY")
completion = openai.ChatCompletion.create(
model="ft:gpt-3.5-turbo:my-org:custom_suffix:id",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"}
]
)
print(completion.choices[0].message)
上記そして GPT ガイド で示されるようにモデル名を渡すことでリクエストし始めることができます。
微調整済みモデルの分析
訓練過程に渡り計算された以下の訓練メトリクスを提供します : 訓練損失、訓練トークン制度、テスト損失、そしてテストトークン精度。これらの統計は訓練がスムースに進んだか (損失は減少し、トークン精度は向上すべきです) のサニティチェックを提供することが目的です。
けれども微調整済みモデルからのサンプル評価がモデル品質の最も現実的な (relevant) 感覚を提供すると私たちは考えます。テストデータセットでベースモデルと微調整済みモデルの両方からサンプルを生成してサンプルを並べて比較することを勧めます。テストセットは理想的には、推論用にモデルに送るかもしれない入力の完全な分布を含む必要があります。手動評価は時間がかかり過ぎる場合、GPT-4 を使用して評価を実行する方法のための Evals ライブラリ の使用を検討してください。
データ品質上の反復
微調整ジョブからの結果が期待したほど良くない場合、訓練データセットを調整する以下の方法を検討してください :
- 残りの問題を対象とするサンプルを収集する
- モデルが特定の局面で依然として良くない場合、モデルにこれらの局面を正しく行なう方法を直接的に示す訓練サンプルを追加します。
- 問題に対して既存のサンプルを精査する
- モデルが文法、ロジックやスタイルの問題を持つ場合、データが同じ問題のいずれかを含んでいないか確認します。例えば、モデルが (そうすべきでない時に) “I will schedule this meeting for you” と言う場合、既存のサンプルが (できないのに) 新しい何かを行えると言うことをモデルに教えていないか確認します。
- データのバランスと多様性を考えます。
- データのアシスタントレスポンスの 60% が “I cannot answer this” と言っているのに、推論時にレスポンスの 5% だけがそのように言っている場合、拒否の過多を取得する可能性が高いです。
- 訓練サンプルがレスポンスに必要な情報のすべてを含むことを確認してください。
- モデルがユーザを個人的な特徴に基づいて称賛することを私たちが望み、そして訓練サンプルが前の会話にはない特徴に対してアシスタントの称賛を含む場合、モデルは情報をハルシネイトすることを学習するかもしれません。
- 訓練サンプルの合意 / 一貫性を見る
- 複数の人が訓練データを作成した場合、モデル性能が人々の間の合意 / 一貫性のレベルにより制限される傾向にあります。例えば、テキスト抽出タスクで、人々が抽出スニペットの 70% にだけ同意した場合、モデルはこれより良く行なうことはできない可能性が高いです。
- 訓練サンプルのすべてが、推論用に想定されるのと同じフォーマットにあることを確認してください。
データ量上の反復
サンプルの品質と分布に満足したのであれば、訓練サンプル数のスケールアップを検討することができます。これはモデルがタスクをより良く学習するのに役立つ傾向があります、特に起こりうる「エッジケース」に関して。訓練サンプル数を 2 倍にするたびに同様の量の改良が期待できます。以下による訓練データサイズの増大から期待される品質の向上をおおまかに見積もることができます :
- 現在のデータセット上の微調整
- 現在のデータセットの半分で微調整
- 2 つの間の品質のギャップを観察する。
一般に、妥協する必要がある場合、少ない量の高品質なデータは一般に大量の低品質データよりも効果的です。
ハイパーパラメータ上の反復
モデルを微調整するエポック数を指定することを可能にしています。最初はエポック数を指定しないで訓練することを勧めます、これはデータサイズに基づいてデフォルトを選択することを私たちに可能にします、そして以下を観察する場合に調整します :
- モデルが期待したほどには訓練データに従っていない場合、1 か 2 エポックずつ数値を増やします。
- これは単一の理想的な補完がある (あるいは類似した理想的な補完の小さいセットがある) タスクに対してより一般的です。幾つかの例は分類、エンティティ抽出、そして構造化パーシングを含みます。これらは、参照する回答に対して最終的な精度メトリックを計算できるタスクである場合が多いです。
- モデルが想定よりも多様性がなくなる場合は、1 か 2 エポックずつ数値を減らします。
- これは、広範囲に可能性がある良い補完があるタスクに対してより一般的です。
以上