【完全版】OpenAIのGPT-3.5 Turboファインチューニングのやり方・料金・メリットデメリットをわかりやすく解説!

📁
【完全版】GPT-3.5 Turboファインチューニングのやり方・料金・メリットデメリットを分かりやすく解説!

2023年8月22日(現地時間)、米OpenAI社は「GPT-3.5 Turbo」のファインチューニングの提供を開始したと発表しました。

ファインチューニングによって、汎用的な生成AIモデル「GPT3.5 Trbo」を追加で学習させて自社に最適化したAIモデルへカスタマイズすることが可能になります。

この記事では、「GPT-3.5 Turbo」のファインチューニングのやり方(実行手順)やメリットデメリット、ファインチューニングにかかる料金について、OpenAI社のファインチューニング解説記事をもとにわかりやすく解説します。


はじめに

2023年8月22日(現地時間)、米OpenAI社は「GPT-3.5 Turbo」のファインチューニングの提供開始を発表しました。

「GPT-4」のファインチューニングもこの秋に提供される予定です。

初期のテストでは、ファインチューニングした「GPT-3.5 Turbo」が、特定の狭いタスクに関して「GPT-4」と同等、またはそれ以上のパフォーマンスを発揮できることが示されています。


ファインチューニングとはなにか?

ファインチューニングとは、学習済みのAIモデル(GPTなど)にデータセットを与えて追加学習させることをいいます。

ファインチューニングの活用例としては、

  • 特定の業界や専門分野に特化
  • 企業の自社データ(FAQやノウハウ、専門情報など)を追加学習させて自社に最適化したAIモデルを構築
  • キャラクター設定(キャラクターとして一貫した口調)

などに活用されます。

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

ファインチューニングを行うことで得られるメリットは次の通りです。

(1) 応答精度の向上

ファインチューニングにより、プロンプトよりも多くの例文で学習できるためモデルが指示通りに従う能力が向上します。
これにより、出力を簡潔にしたり、常に特定の言語で応答したりできるようになります。
例えば、日本語での応答を求めた時、確実に日本語で応答するようにできます。

(2) 指定する出力形式(フォーマット)で出力できる精度向上

ファインチューニングにより、指定する出力形式(フォーマット)通りに正しく出力する精度が向上します。
例えば、コード補完API 呼び出しの作成など、特定のフォーマットでの出力を要求するアプリケーションにとって重要です。
開発者はファインチューニングにより、ユーザーのプロンプトを自分たちのシステムで使用できる高品質のJSONスニペットにより確実に変換できるようになります。

(3) トーン(文体・口調)のカスタマイズ

ファインチューニングにより、口調をカスタマイズすることができます。認識可能な口調を持つキャラクターは、ファインチューニングしてキャラクターの口調と一貫性を持たせることができます。
ファインチューニングにより、回答の定性的な雰囲気、例えばトーン(文体・口調)をカスタマイズするのに有効です。
例えば、独自のブランドの雰囲気・トーン(ブランドボイス)をもつ企業は、ファインチューイングに応答を自分たちのトーン(ブランドボイス)と一致するようにできます。

(4) トークン数(コスト)の削減

ファインチューニングにより、プロンプトで多くの例文を提示する必要がなくなるため、プロンプトの短縮によりトークン数(コスト)の節約や例文提示の手間を節約することができます。
GPT-3.5-Turboのファインチューニングでは、以前のファインチューニングモデルの2倍である 4,000 トークンも処理できます。
初期のテストでは、ファインチューニングすることでプロンプトのサイズを最大90%削減し、各API 呼び出しを高速化してコストを削減しました。

(5)応答速度の改善


上記(4)と同じで、ファインチューニングによりプロンプトを短縮することができるため、応答息女の改善が期待できます。


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

ファインチューニングのデメリットとしては、

  • データセットの準備や手間がかかること
  • コストがかかる

ことがあげられます。

OpenAI社もファインチューニングの有用性は認めつつも、上記デメリットを踏まえて、まずは次のような手法で改善する方法を試すべきだと指摘しています。

  • プロンプトエンジニアリング
  • プロンプトチェーン(複雑なタスクを複数のプロンプトに分割する手法)
  • function calling

GPTモデルのファインチューニングは、特定のアプリケーションに対してそれらを改善することができますが、時間と労力の慎重な投資が必要です。主な理由として以下の点が挙げられるため、最初にプロンプトエンジニアリング、プロンプトチェーン(複雑なタスクを複数のプロンプトに分割)、およびfunction calling,で良い結果を得ることをお勧めします。

出典:OpenAIのファインチューニングGUIDE

ファインチューニングをせずに、まずはプロンプトエンジニアリングなどを試すべき理由としては以下が挙げられます。

1.ファインチューニングを行わずに、プロンプトの改善だけで解決できるケースがあるから

2.プロンプトの改善等を繰り返す方が、ファインチューニングよりも迅速だから

3.結果的にファインチューニングが必要となった場合も、初期のプロンプトエンジニアリングは無駄にならないから

また、OpenAI社は、ファインチューニングせずにパフォーマンスを向上させるための効果的な戦略・戦術として「GPTベストプラクティスガイド」で解説しているので、参考にしてみてください。


RAG / Embeddingとファインチューニングをどう使い分けたらいい?

GPTをカスタマイズする手法として、ファインチューニングの他に代表的なものとしてRAG(Retrieval Augmented Generation)/ Embeddingがありますが、それとどう使い分けたらいいのでしょうか?

OpenAI社は、同社のファインチューニングGUIDEの中で次のように説明しています。

関連するコンテキストと情報を持つ大規模な文書データベースが必要な場合、RAG / Embeddingが最適に適しています。

デフォルトでは、OpenAIのモデルは、役立つ一般的なアシスタントとして訓練されています。ファインチューニングは、狭く焦点を当て、特定の固有の行動パターンを示すモデルを作るために使用することができます。

RAGは、応答を生成する前に関連するコンテキストを提供することで、モデルに新しい情報を利用可能にするために使用することができます
RAGはファインチューニングの代替手段ではなく、実際には補完的な関係にあります。

出典:OpenAIのファインチューニングGUIDE

例えば、「自社のデータ(FAQやノウハウ、業界特有の専門情報など)を学習させて自社に最適化したAIモデルを構築したい」とお考えの企業が多くいらっしゃいますが、その場合はファインチューニングよりもRAG / Embeddingの手法のが相応しいケースが多くあります。


ファインチューニングの安全性

ファインチューニングを通じてデフォルトモデルの安全機能を維持するために、ファインチューニングの学習データは「モデレーションAPI」と「GPT-4を利用したモデレーションシステム」を介して渡され、OpenAIの安全基準と矛盾する安全でない学習データを検出するようになっており、安全性は確保されているとOpenAIは説明しています。


ファインチューニングできるモデル

ファインチューニングできるモデルは、次の3種類です。

・gpt-3.5-turbo-0613 (推奨)
・babbage-002
・davinci-002

gpt-3.5-turbo」が推奨されます。

babbage-002」と「davinci-002」は7月に終了が発表されたGPT-3ベースモデル (ada、babbage、curie、davinci) の代替モデルとして、過去のファインチューニングモデル(GPT-3)から移行のため用意されてます。

GPT-4 と GPT-3.5-Turbo-16kは今年後半にリリース予定です。


ファインチューニングの料金体系

ファインチューニングの使用料金は、初期学習コスト(以下➀)と使用コスト(以下②③)の2つのに分類されます。

学習 : $0.008 / 1000 Tokens
➁ 入力テキスト : $0.012 / 1000 Tokens
③ 出力テキスト : $0.016 / 1000 Tokens

たとえば、3 エポックで学習される 100,000 トークンの学習ファイルを含む gpt-3.5-turbo のファインチューニングのジョブの予想コストは 2.40 ドル($0.008×100×3)になります。

ファインチューニングの料金プランはこちらからも確認できます。

OpenAIファインチューニングの料金表


ファインチューニングのやり方(実行手順)

ファインチューニングの実行手順は、次のとおりです。

(1) 学習データの準備
(2) 学習データのアップロード
(3) ファインチューニングの実行
(3) ファインチューニングしたモデルの使用

(1) データの準備

まずは、モデルに学習させるデータを準備する必要があります。多くの会話のデータセットを作成する必要があります。

データセット内の例文は、Chat completions APIと同じ形式の会話であるべきで、具体的には、各メッセージがrole、content、optional nameを持つメッセージのリストです。

学習させる例文の少なくとも一部は、プロンプトされたモデルで望ましいように応答しないケースを対象として、アシスタントメッセージは本来あるべき理想的な応答メッセージにしましょう。

例文の形式は以下を参考にしてください。以下の例では、時折皮肉な応答をするチャットボットを作成することが目標で、データセットのために作成できる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."}]}

データの準備段階において重要なポイントは以下の通りです。

ポイント➀ プロンプトの作成

最も効果的と思われる指示やプロンプトのセットを選択し、すべての学習用の例文に含めることをお勧めします。これにより、特に学習用の例文が比較的少ない(例えば100以下)場合、最も一般的で最適な結果を得ることができます。

コストを節約するために、すべての例で繰り返される指示やプロンプトを短くしたい場合、モデルはそれらの指示が含まれているかのように動作する可能性が高く、推論時にモデルにそれらの「焼き込まれた」指示を無視させるのは難しいかもしれないことに留意してください。

ポイント➁ 例文の数の推奨

モデルをファインチューニングするには、少なくとも10の例文を提供する必要があります。gpt-3.5-turboで50から100の学習用の例文でファインチューニングすると明確な改善が見られることが一般的ですが、正確な使用ケースに基づいて適切な数は大きく異なります。

50のよく作られたデモンストレーションから始め、ファインチューニング後にモデルが改善の兆候を示すかどうかを確認することをお勧めします。場合によってはそれで十分な場合がありますが、モデルがまだ製品品質でない場合でも、明確な改善は、より多くのデータを提供するとモデルが改善し続ける良い兆候です。改善がない場合、モデルのタスクの設定方法を再考するか、限られた例のセットを超えて拡大する前にデータを再構築する必要があるかもしれません。

ポイント③ 学習とテストの分割

初期データセットを収集した後、学習部分とテスト部分に分割することをお勧めします。学習とテストファイルの両方でファインチューニングジョブを提出すると、学習の過程で両方の統計を提供します。これらの統計は、モデルがどれだけ改善しているかの初期の信号になります。さらに、早期にテストセットを構築することは、テストセットでサンプルを生成することによって学習後にモデルを評価できるようにするのに役立ちます。

ポイント④ トークンの制限

各学習用の例文は4096トークンに制限されています。これより長い例文は、学習時に最初の4096トークンに切り捨てられます。全体の学習用の例文がコンテキストに収まるようにするために、メッセージの内容のトータルトークン数が4000未満であることを確認することを検討してください。
各ファイルは現在50MBに制限されています。
OpenAIのクックブックからトークン数を計算するノートブックを使用してトークン数を計算できます。

ポイント⑤ データフォーマットの確認

データセットをコンパイルし、ファインチューニングジョブを作成する前に、データフォーマットを確認することが重要です。
データフォーマットのスクリプトは以下を参考にしてください。

データフォーマットのスクリプト

# 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 = "<YOUR_JSON_FILE_HERE>"

# 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")

(2) 学習データファイルをアップロード

データセットの量や構造が適切であることを検証できたら、ファインチューニングで使用する学習データのファイルをアップロードします。

copenai.File.create(
  file=open("mydata.jsonl", "rb"),
  purpose='fine-tune'
)

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

ファイルをアップロードしたら、ファインチューニングを実行しましょう。

▼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")

ファインチューニングジョブを開始した後、完了するまでに数分から数時間かかることがあります。
モデルの学習が完了すると、ファインチューニングジョブを作成したユーザーにメールで確認が送信されます。

▼既存のジョブを一覧表示、ジョブのステータス表示、ジョブをキャンセルしたりすることもできます。

# 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")

(4) ファインチューニングしたモデルの利用

ジョブが成功した場合、ジョブの詳細を取得するときにモデルの名前でfine_tuned_modelフィールドが埋められていることが確認できます。

ジョブが完了した後、すぐに利用できるはずです。場合によっては、モデルがリクエストを処理する準備ができるまでに数分かかることがあります。モデルへのリクエストがタイムアウトするか、モデル名が見つからない場合、モデルがまだロード中である可能性が高いです。このようなことが起きた場合、数分後にもう一度試してみてください。

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)

なお、OpenAIは近い将来、ファインチューニングUIも提供する予定とのことです。
開発者は進行中のファインチューニングジョブ、完了したモデルのスナップショットなどの情報に、より簡単にアクセスできるようになるとのことです。


ファインチューニングモデルの評価方法

ファインチューニングが適切に行われたかどうかを評価する指標として、

  • トレーニングロス
  • トレーニングトークンの正確さ
  • テストロス
  • テストトークンの正確さ

が挙げられます。適切にファインチューニングが行われた場合、上記指標においてトークンの正確さは増加し、ロスは減少しています。

ただ、より良い評価方法として、ファインチューニングモデルと基本モデルで同じサンプルを生成して、そのサンプルを比較評価する方法をOpenAIは推薦しています。
なお、この評価手法で時間がかかりすぎる場合、GPT-4を使用して評価を実行する方法について、OpenAIのEvalsライブラリを使用してみるといいでしょう。


ファインチューニングモデルの調整

ファインチューニングの結果が満足いくものではない場合、以下の方法で学習データセットを調整してみましょう。

(1)データ品質の改善

➀残存する問題に対処するための例文を収集 する

モデルがまだ特定の側面でうまくいっていない場合、モデルにこれらの側面を正しく行う方法を直接示す学習の例文を追加ましょう。

➁既存の例文の問題を精査 する

モデルに文法、論理、またはスタイルの問題がある場合、データに同じ問題がないか確認してください。
たとえば、モデルが「この会議を予定します」(しない場合)と言うようになった場合、既存の例文がモデルに新しいことをすることができないことを教えるかどうかを確認してみましょう。

③データのバランスと多様性を考慮する

もし、データ上のアシスタント回答の60%が「答えられない」と回答しているにもかかわらず、推論時には5%しかそう回答していないとすると、回答拒否が過剰になる可能性が高いでしょう。

④学習の例文に応答に必要なすべての情報が含まれていることを確認する

もし、モデルにユーザーの個人的な特徴に基づいて褒めさせたい場合、トレーニングの例に、直前の会話にない特徴に対する褒め言葉をアシスタントに含めると、モデルは情報を幻覚するように学習する可能性があります。

⑤学習の例文の一致/整合性を確認する

複数の人が学習データを作成した場合、モデルの性能は、人々の間の合意/一貫性のレベルによって制限される可能性が高いです。
例えば、テキスト抽出タスクにおいて、抽出されたスニペットの70%にしか人々が同意しなかった場合、モデルはこれ以上の結果を出すことはできないでしょう。

⑥すべての学習の例文が推論に期待される同じフォーマットであることを確認する

(2)データ量の改善

例文の品質と分布に問題がない場合、学習用の例文の数を増やすことを検討してみましょう。

特に「エッジケース」の可能性がある領域で、例文の数を増やすことでモデルがタスクをよりよく学習できるようになる傾向があります。学習用の例文の数を2倍にするたびに、同様の改善が期待できます。

訓練データサイズを増やすことで期待される品質向上は、次のようにして大まかに見積もることができます

・現在のデータセットでのファインチューニング
・現在のデータセットの半分でのファインチューニング
・2つの間の品質ギャップを観察する

一般的に、トレードオフをしなければならない場合、低品質のデータを大量に使うよりも、高品質のデータを少量使う方が効果的です。

(3)ハイパーパラメータの改善

モデルのファインチューニングのエポック数を指定することができます。
最初はエポック数を指定せずにトレーニングすることをお勧めしますが、以下の状態の場合にエポック数を調整してみましょう。

➀モデルが期待されるほど学習データに従わない場合、エポック数を1または2増やす

これは、理想的な応答が一つに絞られるタスクに対してよく見られます。例えば、分類、エンティティ抽出、構造化パースなどが挙げられます。

➁モデルが期待されるよりも多様性が少なくなる場合、エポック数を1または2減らします

これは、上記とは逆に理想的な応答が一つに絞られず複数の応答が考えられるタスクでより一般的です。


まとめ

この記事では、「GPT-3.5 Turbo」のファインチューニングのやり方(実行手順)やメリットデメリット、ファインチューニングにかかる料金などについて、OpenAI社のファインチューニング解説記事をもとにわかりやすく解説しました。

ファインチューニングによって、特定の用途でAIモデルIの精度を向上したり、自社に最適化したAIモデルを構築することが可能ですが、その目的やカスタマイズしたい内容によって、ファインチューニング以外にもプロンプトエンジニアリングやRAG / Embeffingなどの手法もあわせて検討するのがよいでしょう。

「ChatGPTマガジン」を運営するゴートマン合同会社は、
・生成AIの受託開発
・ファインチューニングによるカスタマイズ開発
・RAGによるカスタマイズ開発
など豊富な開発実績を有しており、スピーディーかつ柔軟な対応が可能です。

生成AIを活用した開発のことなら、まずは無料相談してしてみましょう。