chatGPT(gpt3.5-turbo)のfine-tuningをnode.jsで触ってみた


`gpt-3.5-turbo` に `fine-tuning` ができるようになりました。
`node.js`を使って触った際に、[公式のAPI Reference](https://platform.openai.com/docs/api-reference/fine-tuning) だけだと少し分かりづらかったので、備忘メモ。

## 準備

- 適当な`node.js環境`を用意。 
- `npm i dotenv openai` しておく。 （※ 最新バージョンの`openai`へ）
- `.env` に `OPENAI_API_KEY` として トークンを設置しておく。

## 学習データの用意

jsonオブジェクトを1行ごとに区切った `jsonl` 形式で用意。以下イメージ。

```data.jsonl
{"messages": [{"role": "user", "content": "AAAAAAと何ですか？"}, {"role": "assistant", "content": "AAAAAAとは〇〇〇〇〇〇〇〇〇〇のようなものです。"}]}
{"messages": [{"role": "user", "content": "BBBBBBと何ですか？"}, {"role": "assistant", "content": "BBBBBBとは〇〇〇〇〇〇〇〇〇〇のようなものです。"}]}
{"messages": [{"role": "user", "content": "CCCCCCと何ですか？"}, {"role": "assistant", "content": "CCCCCCとは〇〇〇〇〇〇〇〇〇〇のようなものです。"}]}
{"messages": [{"role": "user", "content": "DDDDDDと何ですか？"}, {"role": "assistant", "content": "DDDDDDとは〇〇〇〇〇〇〇〇〇〇のようなものです。"}]}
{"messages": [{"role": "user", "content": "EEEEEEと何ですか？"}, {"role": "assistant", "content": "EEEEEEとは〇〇〇〇〇〇〇〇〇〇のようなものです。"}]}
.
.
.
```

- 学習するサンプルは`最小10個必要`。`50～100個`で明確な改善が見られるらしい。
- サンプルは`それぞれ4096トークン内に収まっている必要`あり（越えた場合切り捨て）
- ファイルは`50MB以下`である必要がある
- 学習データはユーザ以外の学習には使用されない。`OpenAI側で安全性のチェック`は行われる。


## 学習データの登録

まず、[API Reference](https://platform.openai.com/docs/api-reference/fine-tuning)の以下のように書かれている部分の `"file-abc123"` に該当する`ID`を入手する。

```js
  const fineTune = await openai.fineTuning.jobs.create({
    training_file: "file-abc123"
  });
```

以下を実行し、実行結果のログからIDを入手。

```node.js

require('dotenv').config()

const fs = require('fs')
const dataPath = './path/to/data.jsonl'

const OpenAI = require('openai')
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
})

async function uploadFile() {
  try {
    const f = await openai.files.create({
      file: fs.createReadStream(dataPath),
      purpose: 'fine-tune',
    })
    console.log(`File ID: ${f.id}`) // このIDを使う
    return f.id
  } catch (err) {
    console.log('err uploadfile: ', err)
  }
}
uploadFile()
```

## ファインチューニングする

先程入手したIDを `TRAINING_FILE_ID` として`.env` に追記し、以下のコードを実行する。

```node.js
require('dotenv').config()

const OpenAI = require('openai')
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
})

async function main() {
  const fineTune = await openai.fineTuning.jobs.create({
    training_file: process.env.TRAINING_FILE_ID,
    model: 'gpt-3.5-turbo-0613',
  })
  console.log(fineTune)
}
main()
```

### ⚠️注意点

上記のサンプルコードの `uploadFile()` と `main()` を直列で、すぐに実行してしまうと、`BadRequestError: File file-....... is not ready` のようなエラーが帰ってくる。

処理にタイムラグがあり、**少し待たなければならない**。

## ファインチューニングされたモデルを使用する

成功すると、少し時間を置いて`successfully completed`と書かれたメールが届く。
これでファインチューニングしたモデルが使用可能になる。

[OpenAI Playground](https://platform.openai.com/playground)を使うとすぐに動作確認できて便利。

![CleanShot 2023-08-26 at 17.32.39@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274832/c5ba6e89-203f-6fc6-0076-bb46d3dd7895.png)


## おまけ

サンプルのjsonlを見る限り、これまで embedding を駆使して in-context learning していた内容を予めモデルに焼き付けておけるようなものに感じました。
普遍性の高いデータは予めfine-tuningしておきトークンを削減させ、更新されうるデータは embeddingで都度プロンプトに含めていくようなデザインパターンが良さそうに思えますが、このあたりは少し実験していきながら掴んでいきたいです。


## 参考

参考にさせていただきました。ありがとうございます。

- https://openai.com/blog/gpt-3-5-turbo-fine-tuning-and-api-updates
- https://platform.openai.com/docs/api-reference/fine-tuning/create
- https://zenn.dev/nano_sudo/articles/eaf0d77646d7b8
- https://dev.classmethod.jp/articles/openai-gpt35turbo-fine-tuning/
- https://dev.classmethod.jp/articles/gpt35-finetuning-tips-qna/