GPT3.5ファインチューニングで川柳AIを作る

## 完成品
![スクリーンショット 2024-05-08 14.16.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/9bff2323-4612-10ac-d3f9-9079c961012d.png)
このように、テーマを入力すると、それに合わせた川柳を詠んでくれるAIです。

## データ収集
川柳のデータを5000件ほど集めました。
テーマと川柳が対応付くようにしておきます。読みやすいように五七五で空白を入れておきます。
実際に集めたデータは以下のようになっています。１行目はテーマです。
```
コロナ明け
コロナ明け　歩く体力　落ちている
マスクなし 花粉で再び　マスクつけ
マスクなし 人ごみ慣れず 逃げる足
```
これを１つのファイルとして、dataフォルダにまとめて入れておきます。

## データフォーマット
dataフォルダの中身を、GPT3.5で学習させるためのフォーマットに整形します。
Node.jsのコードは以下の通りです。
```js
const fs = require("fs");
const path = require("path");

let jsonl = "";

// ディレクトリ内のすべてのファイルを取得
const files = fs.readdirSync("data");

files.forEach((file) => {
  const filePath = path.join("data", file);

  // ファイルの内容を読み込む
  const content = fs.readFileSync(filePath, "utf-8");

  // 行に分割
  const lines = content.split("\n");

  // 1行目をodaiに、2行目以降をsenryuに設定
  const odai = lines[0];
  const senryu = lines.slice(1);

  for (let i = 0; i < lines.length; i++) {
    if (senryu[i] === "") return;
    const jsonlContent = {
      messages: [
        {
          role: "system",
          content:
            "あなたは川柳を詠むAIです。五七五（〇〇〇〇〇　〇〇〇〇〇〇〇　〇〇〇〇〇）で回答します。",
        },
        { role: "user", content: "お題: " + odai },
        { role: "assistant", content: senryu[i] },
      ],
    };
    jsonl += JSON.stringify(jsonlContent) + "\n";
  }
});

// JSONLファイルを書き出す
fs.writeFileSync("training_data.jsonl", jsonl);
```
最終的に`training_data.jsonl`の中身は以下のようなデータになります。
```json
{"messages":[{"role":"system","content":"あなたは川柳を詠むAIです。五七五（〇〇〇〇〇　〇〇〇〇〇〇〇　〇〇〇〇〇）で回答します。"},{"role":"user","content":"お題: コロナ明け"},{"role":"assistant","content":"コロナ明け　歩く体力　落ちている"}]}
{"messages":[{"role":"system","content":"あなたは川柳を詠むAIです。五七五（〇〇〇〇〇　〇〇〇〇〇〇〇　〇〇〇〇〇）で回答します。"},{"role":"user","content":"お題: コロナ明け"},{"role":"assistant","content":"マスクなし 花粉で再び　マスクつけ"}]}
{"messages":[{"role":"system","content":"あなたは川柳を詠むAIです。五七五（〇〇〇〇〇　〇〇〇〇〇〇〇　〇〇〇〇〇）で回答します。"},{"role":"user","content":"お題: コロナ明け"},{"role":"assistant","content":"マスクなし 人ごみ慣れず 逃げる足"}]}
```

## ファインチューニングの実行
Google Colaboratoryで以下のコードを実行していきます。
```bash
!pip install -q openai
```
次に、OpenAIに学習データをアップロードします。`api_key`はご自身のものに書き換えてください。
```python
from openai import OpenAI
client = OpenAI(api_key="sk-***********************")

client.files.create(
  file=open("/content/training_data.jsonl", "rb"),
  purpose="fine-tune"
)
```
上記のコードを実行すると`fileId`が表示されます。`fileId`を書き換えて以下のコードを実行します。
```python
client.fine_tuning.jobs.create(
  training_file="file-*****************",
  model="gpt-3.5-turbo",
  hyperparameters={
    "n_epochs":3
  }
)
```
１時間ほどで学習は完了します。

## かかったファインチューニングの料金
![スクリーンショット 2024-05-08 14.43.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/b7cd175c-613e-49e1-7f41-a4f9a01c373d.png)

5000行ほどのデータでかかったのは$13.16でした。1ドル=155円として日本円に換算すると2040円かかりました。

## APIからファインチューニングしたモデルにアクセスする
TypeScriptのコードは以下の通りです。
```js
import OpenAI from "openai";
import * as dotenv from "dotenv";
dotenv.config();

const openai = new OpenAI({
  apiKey: process.env["OPENAI_API_KEY"]
});

export async function callSenryu(content: string) {
  const response = await openai.chat.completions.create({
    model: "ft:gpt-3.5-turbo-0125:personal::********",//変える
    max_tokens: 50,
    temperature: 0.2,
    messages: [
      {
        role: "system",
        content:
          "川柳を読んでください。川柳とは五・七・五の音を持つ日本語の定型詩です。言葉遊びやダジャレを含めてください。例えば、「コロナ明け　歩く体力　落ちている」などです。"
      },
      {
        role: "user",
        content: content
      }
    ]
  });
  return response.choices[0]?.message?.content;
}

callSenryu("春").then(console.log);
```

## Teamsで川柳ボットを作成する

Copilot Studioを使用してAPIを呼び出しました。Copilot Studioは簡単にTeamsとも接続できるので便利です。手順はこちらに書いています。

https://qiita.com/ssc-yshikeda/items/6272ef35d3c9bb5a4de1

## 精度の評価

![スクリーンショット 2024-05-15 14.48.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503518/1de444e8-e8bd-a0f3-403e-4d0ae828e824.png)

川柳っぽく五七五に似せて出力はできているものの、リズムが良くなかったり、字余り・字足らずだったり。欲を言えば、言葉遊びやダジャレを入れてもらいたいものですが、そこまではできませんでした。

## まとめ

GPT3.5のファインチューニングを使って川柳AIを作ってみました。
データさえ集まればお手頃価格で作れるので、ぜひ皆さんも作ってみてください。