見出し画像

GPTはガノタの友になれるのか

GPT3に「機動戦士ガンダム」(ファーストガンダム)のストーリーや名言を追加学習させてみました。

きっかけ

ChatGPT に「機動戦士ガンダムにミハルが出てくるのは第何話ですか」と聞いたら以下のようなお返事でした。

ミハル・ラトキエは、アニメ『機動戦士ガンダム』に登場するキャラクターです。彼は地球連邦軍のパイロットであり、主人公であるアムロ・レイと共に戦闘を行います。ミハルは優れたパイロットであり、特にジムの操縦に長けています。彼はアムロとは対照的に冷静で合理的な性格であり、しばしばアムロと対立します。しかし、彼は仲間たちと共に戦い、地球連邦軍の勝利に貢献します。ミハルは機動戦士ガンダムの重要なキャラクターの一人であり、物語の進行に大きく関わっています。

嘘ばっかり!

これではガノタ(ガンダムオタク)の友にはなれません。

追加学習させることにしました。

前提

  • GPT3に追加学習させる方法はいくつかありますが、ここでは一問一答を準備し、GPTに学習させる方法を取ります。

  • pythonのコードはGoogle Colaboratoryで実行しました。

  • OpenAIのアカウント開設、API KEYの取得は済んでいるものとします。

  • この記事を書いている時点で追加学習(ファインチューニング)ができるのはChatGPT より世代の古いGPT3です。

  • コストがかかります。(今回の例では、885セットの質問・回答で8ドル程度)

  • 実際にうまくいったコードを記載していますが、この記事の主たる目的は自分のための備忘録であり、またわかりやすさを優先しているため、正確性・厳密性に問題があるかもしれません。その点はご容赦ください。

準備

まずはopenaiのインストールとAPI KEYの登録。「Your API KEY」にはOpenAIで取得したKEYを入れてください。

!pip install --upgrade openai
import os
os.environ["OPENAI_API_KEY"] = "Your API KEY"
OPENAI_API_KEY="Your API KEY"
  • GundamEpisodeQA.txt」というテキストファイルを用意し、1行ずつ、以下のフォーマットで質問と回答を入力します。(「 ->」や「###」はセパレータと呼ばれ、プログラムが質問や回答の範囲を特定できるようにするものです。)

    • フォーマット:「質問」+「 ->」+「タブ(\t)」+「 (半角スペース)」+「回答」+「###」

    • 例:機動戦士ガンダム第1話のタイトルは何ですか? -> 「ガンダム大地に立つ!!」です。###

  • 今回、800セットくらいの質問・回答を作りました。

  • エンコードの種類:UTF-8で保存

  • GundamEpisodeQA.txt」をGoogle Colaboratoryにアップロード

  • 質問・回答を作成する際、著作権を侵害しないように気をつけましょう。

  • ちなみにチャットGPTで以下のPromptを使うと、文章を上記のフォーマットに整形できます。(タブが抜けることがあるので点検・修正が必要。)

Write 10 sets of a question and an answer in Japanese using the text below. Please use tab as a separator between questions and answers.

            Desired format:
             <question 1> ->\t <answer 1>###
             <question 2> ->\t <answer 2>###
            ...
            <question 9> ->\t <answer 9>###
            <question 10> ->\t <answer 10>###

            Text: """{ここにテキストを入力。改行ありでもOK}"""

JSONLファイルへの変換

作成した一問一答をGPT3に追加学習させるためにはJSONLという形式に変換する必要があります。以下のコードを実行し、まずはテキストファイルをpandasの表(データフレーム)に変換。

import pandas as pd

# データセットの準備
df = pd.read_csv(
    'GundamEpisodeQA.txt',
    names=['prompt','completion'],
    sep='	')

「df.head()」すると以下のように表示されます。うまくいっているようです。

JSONLファイルに変換します。

# JSONLファイルに出力
df.to_json("Gundam.jsonl", orient='records', lines=True)

データセットの検証

以下を実行するとJSONLファイルの検証および修正をしてくれます。

!openai tools fine_tunes.prepare_data -f Gundam.jsonl -q

以下のように出力されます。

Analyzing...

- Your file contains 885 prompt-completion pairs
- There are 2 duplicated prompt-completion sets. These are rows: [170, 375]
- More than a third of your `prompt` column/key is uppercase. Uppercase prompts tends to perform worse than a mixture of case encountered in normal language. We recommend to lower case the data if that makes sense in your domain. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details
- More than a third of your `completion` column/key is uppercase. Uppercase completions tends to perform worse than a mixture of case encountered in normal language. We recommend to lower case the data if that makes sense in your domain. See https://platform.openai.com/docs/guides/fine-tuning/preparing-your-dataset for more details
- All prompts end with suffix ` ->`
- All completions end with suffix `###`

Based on the analysis we will perform the following actions:
- [Recommended] Remove 2 duplicate rows [Y/n]: Y
- [Recommended] Lowercase all your data in column/key `prompt` [Y/n]: Y
/usr/local/lib/python3.10/dist-packages/openai/validators.py:452: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
x[column] = x[column].str.lower()
- [Recommended] Lowercase all your data in column/key `completion` [Y/n]: Y


Your data will be written to a new JSONL file. Proceed [Y/n]: Y

Wrote modified file to `Gundam_prepared.jsonl`
Feel free to take a look!

Now use that file when fine-tuning:
> openai api fine_tunes.create -t "Gundam_prepared.jsonl"

After you’ve fine-tuned a model, remember that your prompt has to end with the indicator string ` ->` for the model to start generating completions, rather than continuing with the prompt. Make sure to include `stop=["###"]` so that the generated texts ends at the expected place.
Once your model starts training, it'll approximately take 48.9 minutes to train a `curie` model, and less for `ada` and `babbage`. Queue will approximately take half an hour per job ahead of you.

留意点

  • 「-q」を入れることで自動的に提案内容に修正してくれます。上記の例では自動修正され、「Gundam_prepared.jsonl」というファイル名のファイルが自動的に生成されます。

  • 出力されたメッセージ(英文)は熟読した方がいいです。テキストファイルの時点でセパレータの入力がうまくいっていない場合、ここで発見されます。

  • たとえば質問がすべて「か?」で終わっている場合「か? ->」がセパレータとして認識されてしまいます。そういう場合はどれか一つでいいので「?」を削除しましょう。

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

無事にJSONLファイルが生成できたらいよいよファインチューニングの実行です。以下のコードを実行します。

"Gundam_prepared.jsonl"の部分が、データセットの検証で生成されたファイル名になっているか確認しましょう。(何度も検証を実行した場合、たとえば"Gundam_prepared(3).jsonl"が最終版です。)

!openai api fine_tunes.create     -t "Gundam_prepared.jsonl"     -m davinci

たいてい一発ではうまくいきません。以下のようなメッセージが表示され、中断されます。「ft-xxxxxxxxxxxxxxxxxxx」は固有の番号です。

Upload progress: 100% 175k/175k [00:00<00:00, 161Mit/s]
Uploaded file from Gumdam_prepared.jsonl: file-xxxxxxxxxxxxxxxx
Created fine-tune: ft-xxxxxxxxxxxxxxxxxxx
Streaming events until fine-tuning is complete...

(Ctrl-C will interrupt the stream, but not cancel the fine-tune)
[2023-07-28 13:00:47] Created fine-tune: ft-xxxxxxxxxxxxxxxxxxx

Stream interrupted (client disconnected).
To resume the stream, run:

openai api fine_tunes.follow -i ft-xxxxxxxxxxxxxxxxxxx

しばらく経ってから以下を実行しましょう。(データセットの検証の時に「Queue will approximately take half an hour per job ahead of you.」のように待ち時間の目安が表示されます。)

!openai api fine_tunes.follow -i ft-xxxxxxxxxxxxxxxxxxx

以下のように表示されたらファインチューニング成功です!

[2023-07-28 13:00:47] Created fine-tune: ft-xxxxxxxxxxxxxxxxxxx
[2023-07-28 16:51:07] Fine-tune costs $8.06
[2023-07-28 16:51:07] Fine-tune enqueued. Queue number: 0
[2023-07-28 17:01:09] Fine-tune is in the queue. Queue number: 8
[2023-07-28 17:01:56] Fine-tune is in the queue. Queue number: 7
[2023-07-28 17:02:22] Fine-tune is in the queue. Queue number: 6
[2023-07-28 17:04:30] Fine-tune is in the queue. Queue number: 5
[2023-07-28 17:04:32] Fine-tune is in the queue. Queue number: 4
[2023-07-28 17:04:33] Fine-tune is in the queue. Queue number: 3
[2023-07-28 17:05:15] Fine-tune is in the queue. Queue number: 2
[2023-07-28 17:07:53] Fine-tune is in the queue. Queue number: 1
[2023-07-28 17:08:20] Fine-tune is in the queue. Queue number: 0
[2023-07-28 17:10:34] Fine-tune started
[2023-07-28 17:16:47] Completed epoch 1/4
[2023-07-28 17:26:15] Completed epoch 3/4
[2023-07-28 17:31:39] Uploaded model: davinci:ft-personal-2023-07-28-xx-xx-xx
[2023-07-28 17:31:41] Uploaded result file: file-xxxxxxxxxxx
[2023-07-28 17:31:41] Fine-tune succeeded

Job complete! Status: succeeded 🎉
Try out your fine-tuned model:

openai api completions.create -m davinci:ft-personal-2023-07-28-xx-xx-xx -p <YOUR_PROMPT>

実行させてみよう

追加学習させたGPT3がどんな回答をするか見てみましょう。(回答にもコストがかかります。)「'davinci:ft-personal-2023-07-28-xx-xx-xx'」の部分は入れ替えてください。

import openai
prompt='機動戦士ガンダム第1話のタイトルは何ですか? ->'
response = openai.Completion.create(
    engine='davinci:ft-personal-2023-07-28-xx-xx-xx',
    temperature=0,
    prompt=prompt,
    max_tokens=200,
    stop='###')
print(prompt+response['choices'][0]['text'])

prompt(質問)の最後に「 ->」を、それから「stop='###'」の指定を忘れないように。(データセットの検証をしたときに出てきた以下のメッセージに従うため。)

After you’ve fine-tuned a model, remember that your prompt has to end with the indicator string ` ->` for the model to start generating completions, rather than continuing with the prompt. Make sure to include `stop=["###"]` so that the generated texts ends at the expected place.

回答は以下のとおり。

機動戦士ガンダム第1話のタイトルは何ですか? -> 「ガンダム大地に立つ!!」です。

うまくいったようです!

「機動戦士ガンダムでミハルが出てくるのは第何話ですか」という質問への回答は以下のとおりでした。

ミハルが出てくるのは第10話です

残念ながら間違いでした。(正解は第26話「復活のシャア」第27話「女スパイ潜入!」第28話「大西洋、血に染めて」です。)いつも教えたとおりに答えてくれるとは限らないようです。(GPT3の限界かな?)

実行させてみよう2(temperatureの値を変えてみる)

意図したとおりの答えを得るためにはtemperatureというパラメータが重要です。

  • 0にするとほぼ教えたとおりの回答をする(はず)

  • 2に近づけると多様・独創的な答えをするようになる

以下のように、temperatureにいくつかの値を入れ、複数の答えをするようにしました。

import openai
prompt='機動戦士ガンダムのコンスコンとはどのような人物ですか? ->'
for t in [0.1, 0.5, 1, 2]:
  print("temperature:", t)
  response = openai.Completion.create(
    engine='davinci:ft-personal-2023-07-28-xx-xx-xx',
    temperature=t,
    prompt=prompt,
    max_tokens=200,
    stop='###')
  print(prompt+response['choices'][0]['text'])

以下のような回答でした。

temperature: 0.1
機動戦士ガンダムのコンスコンとはどのような人物ですか? -> コンスコンとは、アムロ・レイの偽名です。
temperature: 0.5
機動戦士ガンダムのコンスコンとはどのような人物ですか? -> コンスコンとはアムロ・レイが撃破したザクのことです。
temperature: 1
機動戦士ガンダムのコンスコンとはどのような人物ですか? -> コンスコンとはガンダムのリック・ドムのことです。
temperature: 2
機動戦士ガンダムのコンスコンとはどのような人物ですか? -> コンスコンは危険係owshipprarenVar卒Alb asrea urvnenUnderineenWoil rawaSprenDureauLa wa血eneseen

temperature0.1でも残念ながら教えたとおりには回答してくれませんでした。(「コンスコンとは、アムロ・レイの偽名です。」はおもしろい回答ではありますが。)

temperatureを2に近づけていくと自由度(というかメチャクチャ度?)が増していくのがわかります。

ちなみに、追加学習していないGPT3に同じ質問をしたら以下の回答でした。

temperature: 0.1
機動戦士ガンダムのコンスコンとはどのような人物ですか?
コンスコンは、ガンダムの世界において、人類を守るために戦う人間です。

temperature: 0.5
機動戦士ガンダムのコンスコンとはどのような人物ですか?
憧れの人物のようですね。
(質問者)
ありがとうございます。

temperature: 1
機動戦士ガンダムのコンスコンとはどのような人物ですか?この方は、情緒不安定で暴言をポロりと、さほど筋肉も腕力も強くない、ごく平凡な何か人間のようにみえます!さあ!全ての無名より有名な「コスプレ武闘派若き豪傑」この私の父もこのような人でしたが、同様に剣と腕力を活かし戦国武将で、「活奥武神ホホタクシー孫造」と時の孫権

temperature: 2
機動戦士ガンダムのコンスコンとはどのような人物ですか? > Tsubochiblioji see A Distrifice Machine; Apõ Roonlyenta cf Unrecorr

「情緒不安定で暴言をポロりと、さほど筋肉も腕力も強くない、ごく平凡な何か人間のようにみえます」というのはコンスコンの描写として合ってる気もしますが、それ以外の部分はかなりめちゃくちゃです。

これと比べると追加学習後の回答内容はなかなかのものに見えてきます。

まとめ

今回の実験でわかったことは以下のとおりです。

  • GPTを追加学習することは可能

  • しかしGPT3をベースにした場合、800セット程度の質問・回答のセットの学習では、回答の質は必ずしも高くなく、実用できるレベルではない

  • 今後、GPT3.5やGPT4の追加学習が可能になれば質の高い回答が期待できる

GPT3.5やGPT4の追加学習が可能になったら以下のようなことが一般的になるかもしれません。

  • 会社のマニュアルを覚えさせる(会社の業務でわからないことがあったら、ベテラン社員並みの助言ができる)

  • 自分の考え、口調を覚えさせる(コピーロボット!)

本題とは直接関係ありませんが、今回ChatGPT に整形作業をやらせてみて、Promptの使い方を勉強することができました。ホワイトカラーの仕事の効率を上げるには、Promptエンジニアリングの習得がとても大切になることを実感しました。

今回はお遊びでしたが、いずれはもう少し高度なこともやってみたいです。

最後までお読みいただきありがとうございました!

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