👼

ChatGPT で専門知識を Fine-tuning してみた

2023/08/24に公開

GPT-3.5 TurboのFine-tuningがきました。

https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates

というわけで、ChatGPTが知識として持っていない情報をFine-tuningして、特定の知識について回答できるようにしてみましょう。

ちなみに、この記事には約$190かかっています。調子に乗ってぶん回しすぎた・・・。

プロンプトに知識を注入するさいの課題

従来のベクトル検索などでドキュメントを検索し、プロンプトにドキュメントを埋め込む手法には下記のような課題がありました

  • トークン制限によりドキュメントをchunk分割することで文脈が喪失する
    • (ただし、これは16kモデルが出たことである程度緩和されました)
  • トークン制限によりドキュメント間の文脈や関連情報が喪失する
  • 回答精度が検索精度に依存する
    • 特に関連しないドキュメントを埋め込んだ場合にハルシネーションが発生しやすくなる
  • ChatGPTが全く知らないことだと回答精度が下がってしまう

例えば伺かの仕様ドキュメントは下記になります。

http://usada.sakura.vg/contents/specification.html

このドキュメントは階層を持っており、前提としてこのドキュメントは「伺かの旧仕様」であり、その詳細として各ページがある形になります。
そのため、例えばSSTPでは、本文中に一切「伺か」というワードがなく、ドキュメント単独で「伺かの旧仕様」であることがわかりません。

これがChatGPT自体がある程度知識を持っていれば緩和できるのですが、2023/04時点での検証では「伺か」に関する知識はなく、「SSTP」は「Secure Socket Tunneling Protocol」と認識しているため「伺か」と「SSTP」を紐づけることができない形になります。

また、ドキュメントは1つで完結することはなく、リンクなどで他のドキュメントと関連させ知識を補完するような形が一般的です。

つまり、単独のドキュメントを使ったプロンプトでの注入では特定の知識に対して精度高く回答するには不十分でした。
もし、これを実現するなら関連する知識を過不足なく収集し上手く注入する仕組みが必要で、それは2023/08時点ではまだ決定的な解決策が生まれていません。

学習データの生成

1. ドキュメントの準備

https://zenn.dev/kinzal/articles/39f17a62bb6b20

ちょうどLlamaIndexの検証で伺かの旧仕様をMarkdown変換したファイルがあるのでそちらを学習元のドキュメントとして扱います。
ざっくり流れを記載すると

  1. HTMLファイルをダウンロード
  2. HTMLが最近の構造化されたHTMLではないので、Markdownに変換しやすいように微調整
  3. HTMLファイルのMarkdown変換
  4. Markdownを手動で補正(気合いと根性)

4でプログラムやgptを使って補正もできますが、全部はできないので最終的には気合いと根性です。たしか3日ぐらい黙々と補正して全20ドキュメント作成してます。

2. ドキュメントから質問を生成

利用しているモデルはgpt-3.5-turbo-16kです。
これはドキュメント大きくgpt-3.5-turboではおさまらないのと、個人的に16kの方が精度が高く感じるため16kモデルを利用しています。

user
内容から回答可能な質問を新しく作成してください。
この新しく作成する質問は過去に作成した質問とは別の新しい質問を作成しなければなりません。

質問はドキュメント:の文脈を含めた形で、誰が見ても何について質問しているのかわかる具体的な質問を作成してください。
例えば、「Aとは何ですか?」ではなく「BにおけるAとは何ですか?」というようにより具体的な質問でなければなりません。

質問は全て日本語で作成してください。

ドキュメント:
{{document}}
assistant
SSTPサーバとSSTPクライアントが同時に動作する場合に使用されるDirect SSTPについて詳しく教えてください。具体的には、Direct SSTPはどのような動作原理で動作するのでしょうか?

上記のようなuserのドキュメントを埋め込んだプロンプトから、assitantで質問を回答させるようにしています。

user
ドキュメント:の内容から他の質問を作成してください

そのままやりとりを継続して、他の質問を繰り返し生成するようにしています。
ただし、この方法では数件なら問題ありませんが、100件など大量の質問を生成するとすぐにトークン制限にかかってしまいます。

そこで、最初のドキュメント:の内容から他の質問を作成してくださいというプロンプトと回答の組み合わせは20件までやりとりとして保持して、古いやりとりは消していくキュー方式を採用しています。
この方式では質問の重複がおこりえるためEmbeddings APIを使いcosine_similarityの結果が0.9754より小さいときだけ質問を保存して100件になるまでループを回すという小細工も入れています。(0.9754というのはいくつか例を出して同じ質問だと感じたラインがこの辺りだったぐらいの感覚で決めてます)

これによってある程度試行回数を少なくしつつ、多様性のある質問を生成できたかと思います。
しかし、この手法にも問題はあり、直前の質問に引きずられるケースはありました。

assitant
ドキュメントによれば、`SSTP`サーバは`SEND SSTP/1.1`リクエストを使用して`embryo`にスクリプトを送り、それを処理させたり、反応させたりすることができます。このリクエストは現段階ではローカルマシンからのリクエストに限られるとありますが、外部からリクエストを送ることも可能なのでしょうか?
assitant
ドキュメントによれば、SSTPサーバはローカルマシン内で閉じられないため、他のマシンのクライアントから別のマシンのembryoにSSTPパケットを送付することが可能です。これはインターネット経由で行うことも可能でしょうか?
assistant
ドキュメントによれば、`SEND/1.3`リクエストによって選択肢インターフェイスを実現することができます。このリクエストでは、クライアントに対して選択肢を表示し、その選択肢に応じてサーバがスクリプトを実行することができます。このリクエストでは、クライアントが選択肢を返すまで双方の処理がブロックされますが、このブロッキングは2秒ルールの適用外となります。つまり、正しく選択肢が選ばれるまでサーバは応答を返さず、タイムアウトした場合は`204`のステータスコードが返されます。この仕様に基づいて、クライアントが選択肢を返さなかった場合のデフォルトの挙動を設定することは可能でしょうか?

例えば上記のように頭に「ドキュメントによれば」と続くケースなどですね。
もう少し、多様性を生むためにキュー方式ではなくtemperaturetop_pを高くして、cosine_similarityでチェックして類似性の高い質問は弾くなどしても良かったかもしれないと思っています。(ただそうすると$190では済まなかったでしょうけど・・・)

3. ドキュメントと質問から回答を生成

user
ドキュメント:の内容を元に下記の質問に具体的に回答してください。

{{question}}

ドキュメント:
{{document}}
assistant
Direct SSTPは、SSTPサーバとSSTPクライアントが同じWindows上で同時に動作する場合に使用される方法です。通常のSSTPではポートを使用して通信しますが、Direct SSTPではWindowsのCOPYDATAメッセージを使用して通信します。

Direct SSTPの動作原理は、SSTPクライアントがSSTPサーバにメッセージを送信するために、COPYDATAメッセージを使用します。SSTPクライアントは、コマンドやデータなどのメッセージをCOPYDATAメッセージのデータ領域に格納し、SSTPサーバのウィンドウハンドルを指定してメッセージを送信します。SSTPサーバは、COPYDATAメッセージを受信し、メッセージの内容に応じて適切な処理を行います。

つまり、Direct SSTPは、通常のSSTPと比べてポートを使用せず、COPYDATAメッセージを介して直接通信を行う方法です。これにより、SSTPクライアントとSSTPサーバが同じWindows上で動作する場合でも、通信を行うことができます。

回答は上記のような簡単なプロンプトで生成しています。

ここで出来たQAの組み合わせから学習用のデータセットの形に成型してデータセットの準備は完了です。
最終的に20のドキュメントからそれぞれ100のQAを生成し、合計2,000件のデータセットになっています。

本気でやるならここでデータセットの補正などを行うべきだとは思いますが、どの程度動いてくれるのか見るために一旦は生成したデータセットをまるっと使って学習します。
個人的な印象ではデータセットの品質は40点ぐらいで、ちらほらと「うーん」と思うようなものが混ざっている感じです。そういう意味ではここの精度を上げればもう少し結果が変わるかもしれません。

Fine-tuningの実行

ファイルアップロード

curl https://api.openai.com/v1/files \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -F "purpose=fine-tune" \
  -F "file=@path_to_your_file" 

Fine-tuningの実行

curl https://api.openai.com/v1/fine_tuning/jobs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
  "training_file": "TRAINING_FILE_ID",
  "model": "gpt-3.5-turbo-0613"
}'

Blogに書かれていたコマンドを使ってそのままアップロードしています。
ファイルのアップロード後に数分待って処理できるようになったらFine-tuningを実行します。私の場合はだいたい15〜30分ぐらいで完了メールが届きました。

Fine-tuningの結果の検証

元のGPT-3.5 turboではできなかった、それっぽい回答をするようになりました。
正確かというとそうではないけどだいぶ惜しい回答をできるようになったという印象です。


プログラムを生成能力も残っており、仕様を提示させてプログラムを生成させるという使い方もできそうです。
また、これは試していませんがコードレビューといった用途でも扱えるかもという期待があります。

より精度を上げるために

いくつか考えていることを箇条書き

  • データパターンを増やす
    • データセットを眺めてる感じではQAの数がやっぱり足りていないとは感じてます
    • 例えばSSTPのリクエスト例がドキュメントに載っていますが、この例を全てデータセットに含めるようにQAを作るとか
  • データセットの精度を上げる
    • データセットを眺めてる感じではこの質問、回答はどうなんだろ・・・?と感じるものがちらほらあります
    • そういったものを消したり、修正すればもう少し良い回答得られそう
    • プログラムやLLMで品質を点数化とかしてもいいかもだけど、たぶん最後は気合いと根性なので時間かかりそう
  • Fine-Tuningしたモデルを使って再度データセット作りをやる
    • 16kモデルでFine-Tuningできないので2023/08時点では不可
    • 何も知らない状態でQAを生成するより精度高くならないかなという期待
      • 特にドキュメントを跨いだ知識を質問や回答に含まれるようになると良さそう
  • Fine-Tuningしたモデルとベクトル検索の組み合わせ
    • これもドキュメントを跨いだ回答をできるようにならないかなという期待

Fine-tuning済みのモデルで遊んでみたのと、データセットを眺めた感じではなんかデータセットが悪そうという印象だったので、とりあえずもう一度データセット作りはしたいなと思いつつ、今月のUsage Limitまであと$170しかないのと、$360も使ったらPS5買えちゃうじゃん!!!というので悩ましいです。
8兆円ぐらい欲しいです。

まとめ

なんだかんだで悪くない結果になったなという印象です。
もし、勝因があるなら

  • データセットの量がそれなりにあること
  • 1つのことに関していろんな観点のデータセットを作ったこと
  • そもそも、まともに回答できなかったことなので期待値が低かったこと

というあたりかなと思います。AI系に詳しい訳でないので本当にそうなのかは知りませんが。

とはいえ、実用するにはまだ足りないという印象はあるので、そのあたりはもう少し詰めてみたいとは思います。

Discussion