はじめに
こんにちは、DROBE の都筑です。
みなさん LLM 使っていますか。今回は GPT-3.5-turbo の Fine-tuning の事例を紹介します。
結論
- GPT-4 を利用して得られたデータを使って GPT-3.5-turbo を Fine-tuning する事で、特定のタスクに関しては GPT-4 相当の性能が出る事が確認できた
- GPT-4 利用時点で使っていたプロンプトをそのまま使った場合の性能が一番高く、token 節約のためにプロンプトの省略をすると性能が劣化した
背景
LLM を利用したサービスの開発において、OpenAI を利用する場合にはモデルの選択肢がいくつかあります。2023年9月現在では、GPT-4 と GPT-3.5-turbo が主な選択肢になると思います。
ばっくりとは、性能が高いが価格が高くて応答速度と Rate Limit の面で取り扱いが難しい GPT-4 と、性能がそこそこだが価格が安くて応答速度が早く Rate Limit に余裕のある GPT-3.5-turbo という考え方になるため、性能が許す限り GPT-3.5-turbo を使うというのが基本戦略になります。
name | 性能 | 価格 | 応答速度 | Rate Limit |
---|---|---|---|---|
GPT-4 | 高い | 高い | 遅い | 厳しい |
GPT-3.5-turbo | そこそこ | 安い | 早い | 緩い |
課題
DROBE では非構造化データを解析して情報を抽出する事に LLM を利用しようと考えていました。以下のような条件のタスクになりす。
- Input: ~ 3000 token
- output: ~1000 token
- 実行したいタスクの数: ~4000/day 程度
このタスクをまずは GPT-3.5-turbo で解こうと考えましたが、抽出された情報の精度という意味で、性能が思うように出せませんでした。一方で GPT-4 であればある程度納得感のある性能が出せる事が確認できました。
タスクとしては 1600万 token /day 程度の token 数を前提としておいているので、24 時間以内にタスクを完了させるためには TPM (token per minutes) に直すと約 11111TPM
程度の性能が必要になります。
GPT-4 の Rate Limit はアカウントによって違いますが、 20000 TPM が許されているアカウントを例に計算すると、以下のような制約があります。
- 実行時間
- 20000 TPM を無駄なく(待ち時間などなく)利用したとして、約 13 時間掛かる
- Rate Limit
- api 呼び出しで 1 分間に 20000 token 以上のリクエストを行うとエラーになってしまうため、api 呼び出しの間で wait time を設けるなどの工夫が必要になる
- 金額
- $600/day (月間 72 万円程度と) と非常に高額になる
ここで、金額計算の根拠は以下になります
$600 = ((0.00003($/token) * 3000(token) * 4000) + (0.00006($/token) * 1000(token) * 4000))
こういった課題感から GPT-4 の性能を GPT-3.5-turbo で獲得できないかを試すために Fine-tuning を試す事にしました。
Fine-tuning とは?
OpenAI の model における Fine-tuning とは、追加での学習をさせる事で様々なタスクでの性能向上させる事ができる手法です。
Fine-tuning improves on few-shot learning by training on many more examples than can fit in the prompt, letting you achieve better results on a wide number of tasks
具体的には FIne-tuning 用のデータを準備した上で OpenAI の API を叩く事で作成する事ができます。
Fine-tuning のユースケースは公式にも乗っていますが、その中で興味深い一文があります。
Another scenario where fine-tuning is effective is in reducing costs and / or latency, by replacing GPT-4 or by utilizing shorter prompts, without sacrificing quality. If you can achieve good results with GPT-4, you can often reach similar quality with a fine-tuned
gpt-3.5-turbo
model by fine-tuning on the GPT-4 completions, possibly with a shortened instruction prompt.
GPT-4 で良い結果が得られるのであれば gpt-3.5-turbo
モデルを GPT-4 の結果で Fine-tuning する事で同等の性能が得られる可能性がある、という記載があります。
Data の準備
データを準備するために、まずは機能を作り GPT4 で数日程度稼働させます。この期間は応答速度も遅い上に金額も高いですがデータの取得期間という事で割り切って機能を稼働させます。
具体的には以下のような形で Lambda で GPT-4 を叩きつつ、入力と出力のペアを json 形式で Cloudwatch に落とします。
{ "input": "input text", "output": "output text" }
データが集まったら、以下のようなコマンドで Cloudwatch のデータを手元に落とします。(以下のコマンドは Mac での動作を前提にして gdate に依存しているので環境に合わせて適宜修正してください)
aws logs filter-log-events \ --region ap-northeast-1 \ --log-group-name <log-group-name> \ --start-time `TZ=Asia/Tokyo gdate --date='2023-09-01 00:00:00.000' +%s%3N` \ --end-time `TZ=Asia/Tokyo gdate --date='2023-09-10 00:00:00.000' +%s%3N` > fine-tuning-data.log
データをダウンロードしたらここを参考に Fine-tuning のデータの準備と validation を行います。
Fine-tuning 用のデータは jsonl
形式のデータになります。Fine-tuning する際の最大 token 数は 4096 で、それ以上は勝手に truncate されてしまうので、実施前にデータの token 数をカウントする事が推奨されています。
データを準備できたら Training 用のデータと Test 用のデータに分離しました。Test 用のデータは Training には使用せず、Fine-tuning されたモデルでタスクを実行してみて結果が同じになるかどうかを試してモデルの性能を評価するために利用します。
Fine-tuning を実施
Fine-tuning の実施は簡単です。OpenAI の API を利用して以下を実施します。
- トレーニングデータをアップロード
- アップロードしたデータを指定しつつトレーニングを開始
DROBE では以下のスクリプトを GitHub Actions で実施しました。
# Upload data file_response = openai.File.create( file=open("<path-to-file>.jsonl", "rb"), purpose="fine-tune" ) # Get the file ID file_id = file_response["id"] # Check the file's status status = file_response["status"] while status != "processed": print(f"File status: {status}. Waiting for the file to be processed...") time.sleep(10) # Wait for 10 seconds file_response = openai.File.retrieve(file_id) status = file_response["status"] # Create the fine-tuning job using the file ID if status == "processed": fine_tuning_response = openai.FineTuningJob.create( training_file=file_id, model="gpt-3.5-turbo" ) fine_tuning_job_id = fine_tuning_response["id"] # Store the fine-tuning job ID else: print(f"File processing failed with status: {status}") print(f"Fine-tuning job created with id: {fine_tuning_job_id}") fine_tuning_job_response = openai.FineTuningJob.retrieve(fine_tuning_job_id) fine_tuning_job_status = fine_tuning_job_response["status"] while fine_tuning_job_status != "succeeded": print( f"Fine-tuning job status: {fine_tuning_job_status}. Waiting for the fine-tuning job to complete..." ) time.sleep(10) # Wait for 10 seconds fine_tuning_job_response = openai.FineTuningJob.retrieve(fine_tuning_job_id) fine_tuning_job_status = fine_tuning_job_response["status"] print(f"Fine-tuning job completed with status: {fine_tuning_job_status}") print(f"Fine-tuned model id: {fine_tuning_job_response['fine_tuned_model']}")
結果
Fine-tuning が完了したら、そのモデルを利用して事前に準備しておいた Test データに対してタスクを実行します。
その際に以下のような事を検証しました。
- プロンプトは GPT-4 で利用していたものと完全に同じにする
- ただし実験として、プロンプトを省略したものも同時に実施しました
- few-shot learning に対してどれくらいの精度向上が見られるかを試す
結果としては以下のようになりました。
- Fine-tuning すると結果が GPT-4 に近づく事が観測できた
- 100 data よりも 500 data の方が性能が向上した
- 500 data で Fine-tuning した場合には 84% が GPT-4 の結果と完全一致した
- Fine-tuning しても prompt を削ると性能が悪化した
- 比較のため gpt-3.5-turbo-16k-0613 を few shot したものも入れた
- few shot なしよりも性能が向上したが、fine tune したものには及ばなかった
- fine tune したモデルは現状 4k token max なので few shot は出来なかった
success の定義は GPT-4 での結果と GPT-3.5 の結果が完全一致としています
model | finetune | prompt | few-shot | success | succes_rate |
---|---|---|---|---|---|
gpt-3.5-turbo-16k-0613 | no | オリジナル | なし | 20/50 | 0.4 |
gpt-3.5-turbo-16k-0613 | no | オリジナル | あり | 25/50 | 0.5 |
gpt-3.5-turbo | 100 data | オリジナル | なし | 39 / 50 | 0.78 |
gpt-3.5-turbo | 100 data | 簡略版 | なし | 27 / 50 | 0.54 |
gpt-3.5-turbo | 500 data | オリジナル | なし | 42/50 | 0.84 |
おわりに
GPT-4 での推論のデータを利用して GPT-3.5-turbo を Fine-tuning してみた事例を紹介しました。想像していたよりも性能の向上が出来て驚きました。
他にタスクにも利用できるのではないかなと考えているので、さらに色々と試してみたいと考えています。