PLAY DEVELOPERS BLOG

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

HuluやTVerなどの日本最大級の動画配信を支える株式会社PLAYが運営するテックブログです。

ChatGPTでファインチューニング (Fine-tuning) を試してみた

こんにちは、プラットフォーム技術部開発第2グループの李です。

最近AIサービスChatGPTがよく注目されます。米国時間2023年8月22日にOpenAIは、大規模言語モデル「GPT-3.5 Turbo」がFine-tuningに対応したと発表しました。

openai.com

今回、このFine-tuningを試してみました。

Fine-tuningとは

そもそも Fine-tuning は何のことでしょう?

わからないのでChatGPTに聞いてみました。そして下記のことを回答してきました。

ファインチューニングとは、機械学習やディープラーニングのコンセプトの一つで、事前にトレーニングされたモデルを特定のタスクに合わせて微調整するプロセスを指します。この方法は、新しいデータセットや特定の課題に対応するために、既存のモデルを最適化するために使用されます。

なるほど、ChatGPTはすでに強力な言語モデルですが、Fine-tuningによってさらにカスタマイズできるということですね。Fine-tuningは事前に学習されたモデルを特定のタスクに合わせて最適化するプロセスのようです。このプロセスによって、モデルの性能が向上し、特定の課題に合わせたより適切な回答が生成されるようになりそうです。

Fine-tuningのメリット

OpenAIの公式ドキュメントよりFine-tuningを利用することで以下のメリットが得られることがわかりました。

プロンプトよりも高品質な結果が得られる

特定のタスクに対して高品質な結果が得られるために、プロンプトを工夫することよりFine-tuningによってモデル自体を事前に学習させた方が良いという意味ですね。公式サイトにそう書いてありますが、実際に試してみて、本当にそうなのかを考察したほうが良いですね。

プロンプトに収まりきらないほどの多くの例を学習できる

ChatGPTにプロンプトを入力する時に文字数の制限があるので、長い文章を入力できません。しかしFine-tuningを利用すれば多くの例文や、長い文章のデータもトレーニングできます。

短いプロンプトによるトークンの節約

Fine-tuningによりChatGPTをトレーニングすることで、今後ChatGPTを使う時に複雑なプロンプトでタスクの意図を細かく伝えなくても良いです。チューニングデータによりカスタマイズされたアウトプットをもらうことが可能です。ChatGPTを使う時のトークン量を節約することができ、API使用料を抑えることができます。

低レイテンシーのリクエスト

レイテンシーとは、ユーザーのリクエストとその後のレスポンスの間に経過する時間のことです。つまり応答時間が短縮されるということです。

OpenAI APIを使用したFine-tuningの手順

OpenAIの公式ドキュメント よりFine-tuningを行うための手順は以下の通りです。

  1. OpenAI APIキーを取得する
  2. Fine-tuningの作業環境を準備する
  3. トレーニングデータを準備する
  4. トレーニングデータのフォーマットをチェックする
  5. データをアップロードし、モデルをFine-tuningする
  6. Fine-tuningの進捗状況を確認する
  7. 新しいモデルをテストする

1. OpenAI APIキーを取得する

ChatGPTのアカウントを取得した後、OpenAIのプラットフォーム にアクセスすると以下のような画面が表示されます。

OpenAI Platform

画面右上の「Personal」を押してポップアップメニューから「View API keys」を選択すると、APIキーの管理画面を開けます。

APIキーの管理画面 で [+ Create new secret key] ボタンを押してOpenAIのAPIキーを新規作成することができます。

Create new secret key

2. Fine-tuningの作業環境を準備する

実装プロセスに入る前に、PythonのOpenAIライブラリをインストールして作業環境を準備する必要があります。

pip install openai

インストールできたら、下記のようにOpenAIライブラリをインポートできます。

import openai

3. トレーニングデータを準備する

OpenAIの公式ドキュメント に例として提供されたトレーニングデータを先に紹介します。これらは、例の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."}]}

データセットはjsonl形式で作成されます。各行には一連の会話が含まれています。messagesの中には、roleとcontentがあります。contentにはメッセージの内容が入ります。roleにはsystem、user、assistantの3種類があります。それぞれの意味は下記となります。

  • system:ChatGPTの役割を入力する
  • user:利用者側の質問内容を入力する
  • assistant:ChatGPTの解答例を入力する

2023年9月時点、実際に試してからわかったことですが、トレーニング用のデータセットには少なくとも10セットが必要そうです。

データセットの設計が大事なことですが、良い例を考えて作成するのは大変ですね。今回は一旦例として、下記のようなプロンプトでChatGPTに頼んで作ってみました。

プロンプト

日本の伝統的な情報に関する質問に答えるチャットボットのファインチューニングを行うため、下記のコードを参考して、jsonl形式でデータセットを10セット作成してください。

{"messages": [{"role": "system", "content": "このチャットボットは日本文化に関する質問に答えます。"}, {"role": "user", "content": "日本の伝統的な衣装について説明してください。"}, {"role": "assistant", "content": "着物は、日本の伝統的な衣装で、美しい絹の布地で作られ、特別な機会に着用されます。"}]}

mydata.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": "日本には多くの美しい花があり、桜が特に有名です。桜の花見は春の風物詩となり、日本中で楽しまれます。他にも梅、菊、蓮なども重要な花です。これらの花は季節や文化行事と深く結びついています。"}]}

4. トレーニングデータのフォーマットをチェックする

OpenAI Cookbook にトレーニングデータのフォーマットをチェックするためのPythonコードが用意されています。そのまま利用できます。チェックする際にエラーが表示されていなければOKです。

5. データをアップロードし、モデルをFine-tuningする

さっそくMacのターミナルでPythonをコマンドから起動、Pythonの対話モードに入ります。

% python
Python 3.8.5 (default, Sep  4 2020, 02:22:02) 
[Clang 10.0.0 ] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

下記のPythonコードを利用してファイルをOpenAIサーバにアップロードします。 OPENAI_API_KEYには先ほど登録したAPIキーを設定します。 mydata.jsonlには先ほど用意したトレーニングデータのファイルパスを設定します。

import openai
openai.api_key = "OPENAI_API_KEY"
openai.File.create(
  file=open("mydata.jsonl", "rb"),
  purpose='fine-tune'
)

返ってきたレスポンスはこんな感じです。その中にある、file-7RFPAxnDQjuEndtDnY9lKUgS は次のステップでモデルをトレーニングする時に必要となるファイルIDのことです。

{
  "object": "file",
  "id": "file-7RFPAxnDQjuEndtDnY9lKUgS",
  "purpose": "fine-tune",
  "filename": "file",
  "bytes": 781,
  "created_at": 1695981264,
  "status": "uploaded",
  "status_details": null
}

次に、下記のPythonコードを利用してモデルをFine-tuningできます。モデルは、現時点でgpt-3.5-turbobabbage-002davinci-002を指定できます。

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

そして下記のようなレスポンスが返ってきました。その中にある、ftjob-xEogudmh058X08zrYIfhYCTU は登録されたジョブIDのことです。

{
  "object": "fine_tuning.job",
  "id": "ftjob-xEogudmh058X08zrYIfhYCTU",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1695982310,
  "finished_at": null,
  "fine_tuned_model": null,
  "organization_id": "org-XXXXXXXXXXXXXXXXXXXXX",
  "result_files": [],
  "status": "validating_files",
  "validation_file": null,
  "training_file": "file-7RFPAxnDQjuEndtDnY9lKUgS",
  "hyperparameters": {
    "n_epochs": "auto"
  },
  "trained_tokens": null,
  "error": null
}

6. Fine-tuningの進捗状況を確認する

また下記のPythonコードによりFine-tuningの進捗状況を確認できます。

最新10件のFine-tuningジョブを取得する

入力

openai.FineTuningJob.list(limit=10)

出力

{
  "object": "list",
  "data": [
    {
      "object": "fine_tuning.job",
      "id": "ftjob-egRNfkwfd7M3OviIs6qw7U9y",
      "model": "gpt-3.5-turbo-0613",
      "created_at": 1695992067,
      "finished_at": 1695992437,
      "fine_tuned_model": "ft:gpt-3.5-turbo-0613:personal::847Oft9X",
      "organization_id": "org-XXXXXXXXXXXXXXXXXXXXX",
      "result_files": [
        "file-FEGzBsJ7FlCc3BRgmFb1eOqE"
      ],
      "status": "succeeded",
      "validation_file": null,
      "training_file": "file-4oyi25jnDMG7uKO9K5YDTLoP",
      "hyperparameters": {
        "n_epochs": 10
      },
      "trained_tokens": 4580,
      "error": null
    },
    {
      "object": "fine_tuning.job",
      "id": "ftjob-xEogudmh058X08zrYIfhYCTU",
      "model": "gpt-3.5-turbo-0613",
      "created_at": 1695982310,
      "finished_at": null,
      "fine_tuned_model": null,
      "organization_id": "org-XXXXXXXXXXXXXXXXXXXXX",
      "result_files": [],
      "status": "failed",
      "validation_file": null,
      "training_file": "file-7RFPAxnDQjuEndtDnY9lKUgS",
      "hyperparameters": {
        "n_epochs": "auto"
      },
      "trained_tokens": null,
      "error": {
        "code": "invalid_n_examples",
        "param": "training_file",
        "message": "Training file has 3 example(s), but must have at least 10 examples"
      }
    }
  ],
  "has_more": false
}

成功になったジョブであれば、トレーニングされたモデル名が fine_tuned_model に表示されます。失敗になったジョブであれば、error.messageにエラーメッセージが表示されます。

Fine-tuningの詳細状況を取得する

入力

openai.FineTuningJob.retrieve("ftjob-xEogudmh058X08zrYIfhYCTU")

出力

{
  "object": "fine_tuning.job",
  "id": "ftjob-egRNfkwfd7M3OviIs6qw7U9y",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1695992067,
  "finished_at": 1695992437,
  "fine_tuned_model": "ft:gpt-3.5-turbo-0613:personal::847Oft9X",
  "organization_id": "org-XXXXXXXXXXXXXXXXXXXXX",
  "result_files": [
    "file-FEGzBsJ7FlCc3BRgmFb1eOqE"
  ],
  "status": "succeeded",
  "validation_file": null,
  "training_file": "file-4oyi25jnDMG7uKO9K5YDTLoP",
  "hyperparameters": {
    "n_epochs": 10
  },
  "trained_tokens": 4580,
  "error": null
}

Fine-tuningジョブの最新10件イベントを取得する

入力

openai.FineTuningJob.list_events(id="ftjob-xEogudmh058X08zrYIfhYCTU", limit=10)

出力

{
  "object": "list",
  "data": [
    {
      "object": "fine_tuning.job.event",
      "id": "ftevent-sV30yEWdW5FwRDxUQQsIYcDL",
      "created_at": 1695992070,
      "level": "info",
      "message": "Fine-tuning job started",
      "data": null,
      "type": "message"
    },
    {
      "object": "fine_tuning.job.event",
      "id": "ftevent-AS0jU3OsCh5P0Jw9jiEIe9JT",
      "created_at": 1695992069,
      "level": "info",
      "message": "Files validated, moving job to queued state",
      "data": {},
      "type": "message"
    },
    {
      "object": "fine_tuning.job.event",
      "id": "ftevent-m7WzqOwNoKjW7AT3sm4V3mHz",
      "created_at": 1695992067,
      "level": "info",
      "message": "Validating training file: file-4oyi25jnDMG7uKO9K5YDTLoP",
      "data": {},
      "type": "message"
    },
    {
      "object": "fine_tuning.job.event",
      "id": "ftevent-rRUiaVIaulEE010QZAjfXWVP",
      "created_at": 1695992067,
      "level": "info",
      "message": "Created fine-tuning job: ftjob-egRNfkwfd7M3OviIs6qw7U9y",
      "data": {},
      "type": "message"
    }
  ],
  "has_more": false
}

イベントのメッセージから見ると、ジョブが登録されるまで下記のような流れになっています。

- Fine-tuning job started
- Files validated, moving job to queued state
- Validating training file: file-4oyi25jnDMG7uKO9K5YDTLoP
- Created fine-tuning job: ftjob-egRNfkwfd7M3OviIs6qw7U9y

Fine-tuningジョブをキャンセルする

openai.FineTuningJob.cancel("ftjob-xEogudmh058X08zrYIfhYCTU")

Fine-tuningモデルを削除する

openai.Model.delete("ftjob-xEogudmh058X08zrYIfhYCTU")

7. 新しいモデルをテストする

Fine-tuningジョブが実行完了したら、動作確認として下記のPythonコードで、実際に対話をしてみます。 modelには、先ほどジョブ詳細で確認されたモデル名を入れます。

completion = openai.ChatCompletion.create(
  model="ft:gpt-3.5-turbo-0613:personal::847Oft9X",
  temperature=1,
  messages=[
    {"role": "system", "content": "このチャットボットは日本文化に関する質問に答えます。"},
    {"role": "user", "content": "日本の伝統的な芸術について教えてください。"}
  ]
)
print(completion.choices[0].message)

しばらく待って、下記ように全く読めないのUnicodeのコードポイントでレスポンスが表示されてしまいました。

{
  "role": "assistant",
  "content": "\u65e5\u672c\u306e\u4f1d\u7d71\u7684\u306a\u82b8\u8853\u306b\u306f\u591a\u304f\u306e\u7a2e\u985e\u304c\u3042\u308a\u307e\u3059\u3002\u4ee3\u8868\u7684\u306a\u3082\u306e\u306e\u4e00\u3064\u306f\u300c\u8336\u9053\u300d\u3067\u3059\u3002\u8336\u9053\u306f\u8336\u306e\u6e6f\u3092\u901a\u3058\u3066\u5fc3\u306e\u6d17\u7df4\u3084\u7985\u306e\u7cbe\u795e\u3092\u8ffd\u6c42\u3059\u308b\u3082\u306e\u3067\u3001\u8336\u5ba4\u3067\u306e\u5100\u5f0f\u3084\u714e\u8336\u306e\u70b9\u3066\u65b9\u304c\u7279\u5fb4\u3067\u3059\u3002\n\n\u307e\u305f\u3001\u65e5\u672c\u306e\u4f1d\u7d71\u7684\u306a\u7d75\u753b\u306e\u4e00\u3064\u306b\u300c\u6d6e\u4e16\u7d75\u300d\u304c\u3042\u308a\u307e\u3059\u3002\u6d6e\u4e16\u7d75\u306f\u6c5f\u6238\u6642\u4ee3\u306b\u767a\u5c55\u3057\u305f\u6728\u7248\u753b\u3067\u3001\u4e3b\u306b\u98a8\u4fd7\u3084\u7f8e\u4eba\u753b\u3001\u6b4c\u821e\u4f0e\u306a\u3069\u3092\u63cf\u304b\u308c\u3066\u3044\u307e\u3059\u3002\n\n\u3055\u3089\u306b\u3001\u65e5\u672c\u306e\u4f1d\u7d71\u7684\u306a\u821e\u8e0a\u306e\u4e00\u3064\u306b\u300c\u80fd\u300d\u304c\u3042\u308a\u307e\u3059\u3002\u80fd\u306f\u5e73\u5b89\u6642\u4ee3\u306b\u59cb\u307e\u3063\u305f\u4f1d\u7d71\u7684\u306a\u821e\u53f0\u82b8\u8853\u3067\u3001\u795e\u79d8\u7684\u306a\u821e\u8e0a\u3084\u8a9e\u308a\u3001\u96c5\u697d\u3068\u547c\u3070\u308c\u308b\u4f34\u594f\u97f3\u697d\u304c\u7279\u5fb4\u3067\u3059\u3002\n\n\u3053\u308c\u3089\u306f\u4e00\u90e8\u3067\u3059\u304c\u3001\u65e5\u672c\u306b\u306f\u3055\u307e\u3056\u307e\u306a\u4f1d\u7d71\u7684\u306a\u82b8\u8853\u304c\u5b58\u5728\u3057\u307e\u3059\u3002"
}

Googleで解決方法を調べてみたらこちらのブログを見つけました。

nikkie-ftnext.hatenablog.com

そして、人が読める日本語で出力するために、下記のようなPythonコードでもう一度試してみました。

import json
completion = openai.ChatCompletion.create(
  model="ft:gpt-3.5-turbo-0613:personal::847Oft9X",
  temperature=1,
  messages=[
    {"role": "system", "content": "このチャットボットは日本文化に関する質問に答えます。"},
    {"role": "user", "content": "日本の伝統的な芸術について教えてください。"}
  ]
)
print(json.dumps(completion.to_dict_recursive(), indent=2, ensure_ascii=False))

今回contentに日本語がうまく表示できました。^_^ またダンプされた内容より、この対話で合計 131 トークンがかかったこともわかりました。

{
  "id": "chatcmpl-857hZ95cxTov6XtA5q02lBGbPnjxA",
  "object": "chat.completion",
  "created": 1696231937,
  "model": "ft:gpt-3.5-turbo-0613:personal::847Oft9X",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "日本の伝統的な芸術には、茶道、華道、書道、能楽などが含まれます。これらの芸術は日本文化の美意識や精神を表現する重要な要素です。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 58,
    "completion_tokens": 73,
    "total_tokens": 131
  }
}

ちなみに返ってきた答えは、先ほど用意されたデータセットに書いたassistantロールのcontent内容がそのまま表示されています。

Fine-tuningの効果を比べるために、OpenAIが提供されたPlayground サイトで Fine-tuning していないモデル gpt-3.5-turbo を使って、同じ質問をしてみました*1

playground

なるほど、普通のChatGPTでも上手に回答してくれましたが、毎回異なる答えを返しています。 それより Fine-tuning されたモデルであれば、優先に用意された答えが返ってくるように見えますね。

Fine-tuningの料金について

Fine-tuningの料金は、トレーニングデータのトークン数、エポック数、およびFine-tuningジョブの実行回数によって決まるようです。各モデルの最新価格プランについては、OpenAI公式ウェブサイトの価格ページ で確認できます。

2023年9月現在、各モデルの料金は下記となります。

Fine-tuning Price

支払方法

こちらの Billing 画面でアカウントのクレジット数を確認できます。

Billing

2023年9月現在、アカウントが登録された時点で5ドルの試用クレジットをもらえます。[Start payment plan] ボタンよりクレジットカードを登録する時に、最低5ドルを決済しないといけません。*2 また、自動決済を設定できます。想定外の大金を払ってしまわないよう注意する必要がありますね。

利用金額の上限設定

使用料金をコントロールするため、OpenAIプラットフォームの Usage limits 画面で利用金額の上限を設定できます。

Usage limits

[Hard limit] では、上限利用金額を設定することができます。 [Soft limit] では、超えるとお知らせメールを送る金額ラインを設定することができます。

さいごに

今回はChatGPTのFine-tuningについて紹介しました。 Fine-tuningによって、ChatGPTをトレーニングすることで、さまざまなタスクに特化した利用が可能になります。通常のChatGPTでは得られない知識を提供することで、さらなる可能性を広げることができます。

Fine-tuningの一連のアクションは難しくないですが、トレーニングの目的および期待結果に合わせて、トレーニングのデータセットの用意が重要ですね。 ぜひ皆さんも試してみてくださいね!

*1:Maximum lengthを指定せずに質問をしたところ、回答が途中で途切れてしまったので、すべての回答が終わるようにMaximum lengthを2000に指定しました。

*2:以前は従量課金方式でしたが、最近登録されたOpenAIアカウントについてはクレジットを事前にチャージする方式に変更されたようです。