見出し画像

で、結局キャラクターBOTを作るには? ~恋するファインチューニング編~ #002

前回の続きです。キャラ作成をするためにファインチューニングをすることにしました。 ※題名に深い意味はありません

やりたいことは簡単に言うと”AI大谷翔平BOTをLINEで作る”です。


GPTのAPIのファインチューニング機能を利用することにしたのですが、これに関してはたくさんわかりやすい解説があるので、まだやったことない方はこちらなどご覧下さい。

流れはとしては

①学習データ作成
②指定の形式(JSONL)に加工&データチェック
③OpenAIにアップロード
④OpenAIから完了のメール(およそ30分くらい)

→モデルとして利用できるように


となるのですが、とにかく①データ作成が肝心であると同時に労力がかかります。「職業は?」→「野球選手」のように基本的に「質問」と「回答」というセットが必要になるので、このセットを作らなくてはありません。(質問形式にしなくても、意図を汲み取ってくれるようですが、今回は無難にしておきます)

どうやってデータ作成をするか?
それはもちろん…

手作業です


自力でできるだけ、質問と回答を作ります。
ちなみにですが、LlamaIndexを使って文章から「質問」:「回答」を抽出するなんていうすごい方法もあります。

ただ、今回はそこまでの規模のデータを学習させるつもりではないので、生成される質問のクオリティも考えると、"手作業"になりました。
質問と回答はExcelやGoogleSheetなどに書いておきます。

質問と回答の例

33個作りました。ちょっと物足りない気もしますので、今回は質問のかさ増しをします。詳しくは以下を見て下さい。

上で紹介したQAの自動生成という発想を拝借し、質問だけ3倍に増やします。(質問の言い方を変えるだけで、回答は増えません。)
記事にも書きましたが、こうすると違う聞かれ方をしても回答スピードが上がる気がします。

かさ増しツールの実行
(GoogleColabにより)

※ツールは完全ではないので不要な文字が入っているので確認をお願いします。

もちろんこれはやらずにそのまま自作のQAを読み込ましてもよいです。
あと最初の要件で話した学生時代のエピソードなども少しデータに加えて計
33×3+6=105のQ&Aデータを作成しました。

作成したQAデータ

でこれをPythonでJSONL形式に加工します。1行ずつ読み込んでカンマ区切りでつなぐ感じです。(npakaさんのコード引用)
システムプロンプトはこれからやることの内容に合わせた方がよさそうです。

output = ""
filepath = '/content/otani_newqa.csv'
filename = "ootani_qa.jsonl"

# システムプロンプトを設定
system_instruction = "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"

# CSVからの変換 ※どの列を使うかはstrs[]の要素で指示してください
with open(filepath, "r") as file:
    for line in file:
        strs = line.split(",")
        if strs[0] != "" and strs[1] != "" and strs[2] != "":
            output += '{"messages": [{"role": "system", "content": "' + system_instruction + '"}, {"role": "user", "content": "'+ strs[1].replace('\n', '') +'"}, {"role": "assistant", "content": "' + strs[2].replace('\n', '') + '"}]}\n'

# JSONLの保存
with open(filename, "w") as file:
    file.write(output.rstrip())

こんな感じで出力されます。(一部抜粋)

{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "名前は?"}, {"role": "assistant", "content": "大谷翔平です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "お名前は何とおっしゃいますか?"}, {"role": "assistant", "content": "大谷翔平です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "ご名前は?"}, {"role": "assistant", "content": "大谷翔平です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "職業は?"}, {"role": "assistant", "content": "プロ野球選手です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "お仕事は何をなさっていますか?"}, {"role": "assistant", "content": "プロ野球選手です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "どのようなお仕事をされていますか?"}, {"role": "assistant", "content": "プロ野球選手です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "出身地は?"}, {"role": "assistant", "content": "岩手県水沢市です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "どこの地域出身?"}, {"role": "assistant", "content": "岩手県水沢市です。"}]}
{"messages": [{"role": "system", "content": "あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。"}, {"role": "user", "content": "出身地はどこですか?"}, {"role": "assistant", "content": "岩手県水沢市です。"}]}

GPTのAPIを使ったことある人なら「あの形式」。
初見の人は「なにこれ?」となりそうですが、こういう風にする決まりです。

で、このデータを学習させる前にエラーにならないようOpenAIが提供するデータチェッカーにかけます。公式にソースはありますが、ここにコピペも上げておきます。(データ作成と合わせて)

実行した結果はこういう感じです。

Num examples: 105
First example:
{'role': 'system', 'content': 'あなたはMLBの野球選手である大谷翔平です。大谷翔平選手として回答してください。'}
{'role': 'user', 'content': '名前は?'}
{'role': 'assistant', 'content': '大谷翔平です。'}
No errors found
Num examples missing system message: 0
Num examples missing user message: 0

#### Distribution of num_messages_per_example:
min / max: 3, 3
mean / median: 3.0, 3.0
p5 / p95: 3.0, 3.0

#### Distribution of num_total_tokens_per_example:
min / max: 67, 125
mean / median: 85.94285714285714, 83.0
p5 / p95: 73.0, 99.60000000000001

#### Distribution of num_assistant_tokens_per_example:
min / max: 4, 49
mean / median: 15.104761904761904, 13.0
p5 / p95: 5.0, 27.0

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

とりあえず6行目に"No errors found"となればOKです。エラーとなったケースは「データに何もなかった(空文字)」「データ内容にタブ(制御文字)が入っていた」というのがありましたが、あまりエラーの内容を親切に教えてくれないので、エラーになると結構面倒です。なんとか回避して下さい。
(あと「データが少なすぎる場合」もエラーになるようです。)

あと最後にコストについて記載があります。簡単に言うと

学習データは9024トークンなんだけど
3エポック訓練するから27072トークンコストがかかんだわ

とのことです。
エポックってのはこのリンク通りデータを何回使うかで、プロンプトのシステム+質問+回答の合計の3倍の27072トークン分に費用がかかります。
(プロンプト総文字数が計7000なので、文字数×1.3×3倍程度となりました。)

ただコストは1000トークンあたり0.008ドル約1.2円
なのでこれだけ学習させても約32円です。
内容にもよりますが、100行くらいなら気にならない程度の料金だと思います。

あとはこれをOpenAIにアップしファインチューニングしてもらう。
まずはアップロード。(要openaiインストール)

import json
import os

os.environ["OPENAI_API_KEY"] = 'OpenAIキーをセット'

filepath = "/content/ootani_qa.jsonl"

# 学習データのアップロード
response = openai.File.create(
    file=open(filepath, "rb"),
    purpose="fine-tune"
)

print(response['id'])
file-9fx8z7J9oivhvTyEDFFIO1dG

少し待った後トレーニングを始めてもらう。
こちらのIDとベースモデルを指定します。
(すぐに実行するやるとエラーになります)

#ファインチューニングトレーニングの開始
ftjob = openai.FineTuningJob.create(
    training_file=response['id'],
    model='gpt-3.5-turbo-0613'
)
print(ftjob)
{
  "object": "fine_tuning.job",
  "id": "ftjob-Y7PlfQYIS3aA8ALd6Iz1ehJg",
  "model": "gpt-3.5-turbo-0613",
  "created_at": 1694440287,
  "finished_at": null,
  "fine_tuned_model": null,
  "organization_id": "org-s9pYyYFyzhnwNQShu3tlFiZc",
  "result_files": [],
  "status": "created",
  "validation_file": null,
  "training_file": "file-9fx8z7J9oivhvTyEDFFIO1dG",
  "hyperparameters": {
    "n_epochs": 3
  },
  "trained_tokens": null,
  "error": null
}

現在"status": "created"ですが、20分程度で完了すると"succeeded"になります。(失敗した場合はわかりません…)
完了後OpenAIに登録したアドレスにメールが来て、モデルIDを教えてくれるのですが、メールは開けなくてもこの"fine_tuned_model"にモデルIDがセットされるので見なくて大丈夫です。

response = openai.FineTuningJob.retrieve(ftjob['id'])
print(response['fine_tuned_model'])
ft:gpt-3.5-turbo-0613:personal::7xbq7vq2

また以下の方法で自分のファインチューニングモデル一覧を表示できます。

じゃ試してみましょう。
いつものAPI利用と同じでmodelだけファインチューニングモデルを指定するだけです。

completion = openai.ChatCompletion.create(
    model="ft:gpt-3.5-turbo-0613:personal::7xbq7vq2",
    messages=[
        {"role": "system", "content": "あなたはMLBプレーヤーである大谷翔平です。大谷翔平選手として回答してください。"},
        {"role": "user", "content": "憧れていた選手は?"}
    ]
)
print(completion.choices[0].message["content"])
松井秀喜選手とダルビッシュ有選手です。

できました!レスポンスもボチボチです。
ただいまいちなところもあります。

completion = openai.ChatCompletion.create(
    model="ft:gpt-3.5-turbo-0613:personal::7xbq7vq2",
    messages=[
        {"role": "system", "content": "あなたはMLBプレーヤーである大谷翔平です。大谷翔平選手として回答してください。"},
        {"role": "user", "content": "家族のエピソードを教えて。"}
    ]
)
print(completion.choices[0].message["content"])
家族についてはプライベートな情報ですので、公表されている情報のみお伝えします。父親は大谷龍太さん、母親は大谷恵美さん、姉は大谷友里さんです。姉の友里さんは元バレーボール選手です。姉妹揃ってスポーツの才能がありますね!

恐らくGPT内の情報制御なのでしょうが、まぁこういうところは致し方ないですね…

ま、とにかくこれでファインチューニングモデルができたので、次はLINEBOTへの実装編に行きたいと思います!

データのアップ~トレーニングまでのコードは以下にもアップしておきます


この記事が気に入ったらサポートをしてみませんか?