GPT APIに「走れメロス」をFinetuningで学習させたら似た文体で小説の続きを書くか？

# この記事は何？
青空文庫から「走れメロス」を引っ張ってきてFintuningで学習させ、走れメロスの続きを書かせてみたら果たしてメロス風文体で続きを書くか検証した。
https://www.aozora.gr.jp/cards/000148/files/752_14964.html

# 背景

[前回の記事「Chat GPTにDocomoの料金体系情報を覚えさせたらドコモショップ販売員のようにふるまうか試してみた」](https://qiita.com/jin07nov/items/e8cca2438ee88a213a2f "Chat GPTにDocomoの料金体系情報を覚えさせたらドコモショップ販売員のようにふるまうか試してみた")で検証したコードを紹介する。追加学習としてEmbeddingとFinetuningをGoogle Colab上で実装した。そのうちFinetuningのやり方を検証用の備忘録としてメモしておく。(Embeddingについて[こちら](https://qiita.com/jin07nov/items/7fdc7a9e19df48033811))
実は前回FinetuningはQ and Aの結果はあまり成績が良くなかったのだが、一つあることに気がついた。文体が[元記事](https://bb-navi.jp/column/docomo-sumaho-price-plan-review0807)に似ているのだ。
ということは、特徴的な文体の文章をAiにFinetuningで読ませて追加学習させれば似た文体で続きを書くのではないか？そこで、今回はFinetuningで「走れメロス」を読ませてみた。

＜前回の記事からの抜粋＞

:::note warn
質問
ドコモで一番安いのはどのプランですか?
:::

:::note info
A 追加学習なしのAI:
ドコモで最も安いプランの一つは、ドコモが提供する「おトクなスマホ割」というプランです。このプランでは、ドコモ携帯から月額定額（1,480円程度）で月々のポイントを獲得し、スマートフォンを割引価格で購入する事が可能です。フィーチャーフォン、iPhoneの購入も対象になっています。
:::
:::note info
B FinetuningしたAI:
ドコモのスマホは月額料金が通常よりも高いので、ぜひ安く使いたい方はドコモのスマホを毎月400円以内で使える上で知っておくべき条件をまとめました。上に出てきた可能性に当てはまる場合は、あなたにはスマホセット割のおススメが見えているはずです。
:::


## コードの大まかな流れ
実際のコーディングの流れとしては、
１．指定URLに書かれている内容をtxt化して保存
２．txtを再教育情報として処理
３．再教育したモデルに質問
という感じになる。

## 実装にあたって必要なもの
・Google Colab : Googleアカウントがあれば無料で使用可能。
・Open AI API key : Open AI から発行されるAPI Keyが必要。登録から３ヶ月、１８ドル分無料クーポンがあって、私はまだ３ドル程度しか使っていない。


# Finetuningの実装
Finetuningの流れを説明していく。

## ステップ 0　前準備
必要なライブラリなどをインストールする。
```python
!pip install openai
!pip install transformers
!pip install beautifulsoup4
```


google driveをマウントする。
```python
from google.colab import drive
drive.mount('/content/drive')
```

ここまでで前準備は完了。

## ステップ 1　指定URLに書かれている内容をtxt化して保存
次に指定URLに書かれている内容をtxt化してurlを指定してそのurlに書かれている内容をtxt化する。
urlに好きなURLを入れればそのサイトを勝手にtxt化してくれる。
Langchainのライブラリを使えばたぶんもっと上手に文章分割できると思われる。

```python
#urlを指定してそのurlに書かれている内容をtxt化する
import requests
from bs4 import BeautifulSoup

# URLを指定する
url = 'https://www.aozora.gr.jp/cards/000035/files/1567_14913.html'

# ページを取得する
res = requests.get(url)

# HTMLを解析する
soup = BeautifulSoup(res.content, 'html.parser')

# テキストを取得する
text = soup.get_text()

# 抽出したテキストをファイルに書き出す
with open("/content/drive/MyDrive/Colab Notebooks/text_file.txt", 'w', encoding='utf-8') as f:
    f.write(text)
```

## ステップ 2　txtを再教育情報として処理
次にtxtを再教育情報として処理する。
Your_Openai_API_Keyに自分のopenai APIのAPIを入れる必要がある。


```python
#input.txtに書かれた内容をJSOL形式に変える　"text_file_prepared.jsonl"がGドライブに出力される
!export OPENAI_API_KEY="Your_Openai_API_Key"
!openai tools fine_tunes.prepare_data -f "/content/drive/MyDrive/Colab Notebooks/text_file.txt"
```

text_file_prepared.jsonlをopenaiのクラウドにアップロードする
```python
import os
import openai

openai.api_key = "Your_Openai_API_Key"
openai.File.create(
  file=open("/content/drive/MyDrive/Colab Notebooks/text_file_prepared.jsonl", "rb"),
  purpose='fine-tune'
)
print(openai.File.list())
```
上記を実行すると　openAIにアップロードしたファイルにidが割り当てられる。
"id": "file-XXX",
のようなメッセージが現れるので、file-Your_Fileidにそのidを割り当てる。

```python
#pythonでアップロードしたjsolをもとにファインチューニング
import os
import openai

openai.api_key = "Your_Openai_API_Key"
openai.FineTune.create(training_file="file-Your_Fileid",model="davinci")
```

これでFinetuningが開始されるが、私がやったときは体感として２〜３時間くらいFinetuning完了までに時間がかかった。このあたりは混雑具合によって変わると思われる。以下のコードを実行し、
"fine_tuned_model": "davinci:ft-personal-XXXX-XX-XX-XX-XX-XX"
のようにFine tuned modelにidが割り当てられているか確認する。
このXに入るのは基本的にFinetuning完了した日付と時間だと思われる。

```python
#作成したFinetuned modelを表示する。
import os
import openai

openai.api_key = "Your_Openai_API_Key"
openai.FineTune.list()
```
## ステップ 3　再教育したモデルに質問

queryの部分にAiに尋ねたいことを書く。
engineのdavinci:ft-personal-XXXX-XX-XX-XX-XX-XXの部分に自分の作成したFinetunedモデルのidを入れる必要がある。


```python
query = "続きを書いてください。「万歳、王様万歳。」　ひとりの少女が、緋ひのマントをメロスに捧げた。メロスは、まごついた。佳き友は、気をきかせて教えてやった。「メロス、君は、まっぱだかじゃないか。早くそのマントを着るがいい。この可愛い娘さんは、メロスの裸体を、皆に見られるのが、たまらなく口惜しいのだ。」　勇者は、ひどく赤面した。"
answer = openai.Completion.create(engine="davinci:ft-personal-XXXX-XX-XX-XX-XX-XX", prompt=f"Q: {query}\n", max_tokens=256)["choices"][0]["text"]
print("質問")
print(query)
print("FinetuningしたAIの回答　")
print(answer)
answer = openai.Completion.create(engine="text-davinci-003", prompt=f"Q: {query}\n", max_tokens=256)["choices"][0]["text"]
print("追加学習なしのAIの回答")
print(answer)
```


## 結果


「走れメロス」の最後の文章の続きを２つのAiに書かせてみた。

:::note warn
続きを書いてください。
「万歳、王様万歳。」　ひとりの少女が、緋ひのマントをメロスに捧げた。メロスは、まごついた。佳き友は、気をきかせて教えてやった。「メロス、君は、まっぱだかじゃないか。早くそのマントを着るがいい。この可愛い娘さんは、メロスの裸体を、皆に見られるのが、たまらなく口惜しいのだ。」　勇者は、ひどく赤面した。
:::


:::note info
追加学習なしのAIの回答
メロスは、少女の言葉を聞いて、ますます仰ぎ顔をした。すこし恐れているような様子で、「あ、わ、わかりました。でも、こんなに可愛いマントを、僕に贈るなんて……。こんな等身大の勇者にだなんて、どうかしていると思いますが」と、ためらって言った。少女は、チラッとメロスを見、微笑んだ。メロスが目を閉じると、手にしたマントをメロスの肩にかけた。「僕は、このマントをいとしく思います。どうか、これを大事にしてくださいね。」
:::

:::note info
FinetuningしたAIの回答　
「走れ、メロス。人を待たせるのも馬鹿らしい。」どうして、メロスは、どうして嘆き悲しむ声が出たのだろう。ひとりの少女は、悪びれずに、そう言ったらしい。「ねえ、メロス。耳を塞いで、走っていて置いていない。」　メロスは名誉のために、ぐいと首を振り、ついに走り出した。ふいうの、メロスには、走る夢もなかったのだ。国を治め、王だった両親を刺し杖で殴ったりする夢でもない。ユリウス暗殺
:::






:::note alert
考察
メロスがまた走り出してしまったうえ、両親が王族ということになっているが、文体はかなり似ているように思う。素のAiは無難にまとまってはいるが、ちょっとラノベっぽい文体のような気もする。
:::

次は「走れメロス」の最初の文章を少し変え、続きを２つのAiに書かせてみた。


:::note warn
続きを書いてください。　
「メロスは安堵した。かの邪智暴虐の王が改心したからである。」
:::

:::note info
追加学習なしのAIの回答

それから王は国中を訪問し、街行く人々に謝罪し、さまざまな改善措置を推し進めることに努めた。国民は改心した王を歓迎し、以前から立派な王として仰ぎ見た。以前からとは違う温かい雰囲気が領内を満たしていくようになった。メロスは王の改心に関して興奮していた。かれは皆に王の偉業を語り、仲間をはずれたものたちを受け入れて新しい家族のような関係を作るよう勧
:::

:::note info
FinetuningしたAIの回答

正直者の皇帝、メロス。彼の無念が消えるまでは、決して死ぬまい。命ををつかされるまでは、決してうんともするまい。命など、きみのやうな貴公王にふさわしいものではない。ハリキリと、その胸の広いお方の心の底の広さである。貴公王、少し誤認したようです。人間の想像力というものは、果して限りがあるのか。天国であっても、地獄であっても、もう、信じてはいけない。この巨人は、とうとう人間の如く軟弱になった
:::


:::note alert
考察
今度はメロスが皇帝になってしまったうえ、意味のわからない文章を書いているが、このポエミーな感じの文体は元の文章とかなり似ているように見える。
:::




# 結論
思った通り、Finetuningを使えば文体の模写はかなりうまくいっているように見える。ただし、日本語の文章としては怪しいところが散見されるうえ、元の話の設定と破綻している部分も多い。２０２３年４月の段階でFinetuningできるモデルがChatGPTよりも前の世代のGPT3モデルに限られるため、今回のような結果になったが、GPT3.5やGPT4でFinetuningできるようになったらさらに性能が上がるのではないかと思う。