最近よくChatGPTが話題となっている。ChatGPTはOpenAIが開発している文章を生成するAIで、GPT-3.5の言語モデル上に構築されている。文章の自動生成に多少興味があった。もし文章の生成ができれば、記事やドキュメントを書く場合に助けになるし、入力サジェストの品質の向上、要約の作成など様々な場面で利用できる。ただ、なかなか腰が重くて手を付けていなかった。今回はそれを行う良い機会を得たので、文章の自動生成に挑戦してみる事にする。
また、ChatGPTが提供するAPIを使って、Emacsのdoctorを拡張した話は「Emacsの対話セラピー機能doctorをChatGPTに対応させる」に記載した。
GPT-2を用いて文章を生成する
文章の生成で参考になる記事がないかと探していた所、やってみようと思えるものがあった1。そこでここではGPT-3ではなく、GPT-2を用いて文章生成をするには、どのようにすればよいのか、またどのような文章が生成されるかを確認する。
GPT-2用の言語モデルはrinna社が公開しているものがあるので、それをそのまま使用する。通常こういった処理ではGPUを使用する。GPUが手元に無くてもGoogle Colaboratoryを使用すれば、 GPUを用いた計算をさせることができる。Mac Book ProでもIntel Iris Plus Graphicsといった内蔵GPUが搭載されており、これを用いて計算させるように設定することもできる23 。ただし今回は使い方だけを理解するという目的であるため、GPUは使用せずCPUで計算させることにした。
https://github.com/rinnakk/japanese-pretrained-models をcloneする。
git clone --depth 1 https://github.com/rinnakk/japanese-pretrained-models
必要なパッケージをインストールする。pytorchを使用していたため、今回はPython3.10系を使った。なおvenv等の説明は割愛する。
cd japanese-pretrained-models
pip install -r reqirements.txt
Pythonを起動し、インタラクティブシェルを用いて挙動を確認する。
>>> import torch >>> from transformers import T5Tokenizer, AutoModelForCausalLM 2022-12-25 19:38:42.481522: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags. >>> tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt-1b") Downloading: 100%|███████████████████████████████████████| 283/283 [00:00<00:00, 141kB/s] >>> model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt-1b") Downloading: 100%|██████████████████████████████████| 2.66G/2.66G [05:27<00:00, 8.10MB/s]
macOSで実行しており、GPUを使用する設定をしていないため、CPUで計算を行う。
>>> prompt = "むかしむかしあるところにおじいさんとおばあさんがいました。おじいさんは" >>> input_ids = tokenizer.encode(prompt, return_tensors="pt",add_special_tokens=False) >>> num = 1 >>> with torch.no_grad(): output = model.generate( input_ids, max_length=100, min_length=100, do_sample=True, top_k=500, top_p=0.95, pad_token_id=tokenizer.pad_token_id, bos_token_id=tokenizer.bos_token_id, eos_token_id=tokenizer.eos_token_id, bad_word_ids=[[tokenizer.unk_token_id]], num_return_sequences=1 ) ... ... ... ... ... ... ... ... ... ... ... ... ... ... Traceback (most recent call last): File "<stdin>", line 2, in <module> File "/Users/foo/.venv/py310/lib/python3.10/site-packages/torch/autograd/grad_mode.py", line 27, in decorate_context return func(*args, **kwargs) File "/Users/foo/.venv/py310/lib/python3.10/site-packages/transformers/generation/utils.py", line 1296, in generate self._validate_model_kwargs(model_kwargs.copy()) File "/Users/foo/.venv/py310/lib/python3.10/site-packages/transformers/generation/utils.py", line 993, in _validate_model_kwargs raise ValueError( ValueError: The following `model_kwargs` are not used by the model: ['bad_word_ids'] (note: typos in the generate arguments will also show up in this list)
bad_word_idsで問題が発生したため、この引数は一旦削除する。
>>> with torch.no_grad(): output = model.generate( input_ids, max_length=100, min_length=100, do_sample=True, top_k=500, top_p=0.95, pad_token_id=tokenizer.pad_token_id, bos_token_id=tokenizer.bos_token_id, eos_token_id=tokenizer.eos_token_id, num_return_sequences=1)
CPUで計算を行ったため、2〜3分待ったが処理が完了した。結果を表示する。
>>> decoded = tokenizer.batch_decode(output,skip_special_tokens=True) >>> for i in range(num): print(decoded[i]) ... むかしむかしあるところにおじいさんとおばあさんがいました。おじいさんは山へ芝刈りに出かけ、おばあさんは畑へ行っていました。あるときおばあちゃんのお父さんは山へ芝刈りに出ましたが、おばあさんが畑を耕していると、奥の方からたくさんの蜂の大軍が飛んできて、次から次へと白い花と緑色の小さな花を木から落として行きました。山へ入ることも恐れ、しばらくもがいていると、小さな足が岩に登れそうなところを見つけました。おばあさんはおそるおそ
出来ている。他の文章を入力して結果を確認する。
def generate_sentence(text):
input_ids = tokenizer.encode(text, return_tensors="pt",add_special_tokens=False)
with torch.no_grad():
return model.generate(
input_ids,
max_length=100,
min_length=100,
do_sample=True,
top_k=500,
top_p=0.95,
pad_token_id=tokenizer.pad_token_id,
bos_token_id=tokenizer.bos_token_id,
eos_token_id=tokenizer.eos_token_id,
num_return_sequences=1,
)
output = generate_sentence("GPT-1bを用いて文章を生成してみることにした。")
decoded = tokenizer.batch_decode(output,skip_special_tokens=True)
for msg in decoded:
print(msg)
for i in range(num): print(decoded[i])
それっぽい文章が生成されてはいるが、ところどころ不自然だったり嘘があったりする。当然そのままどこかに記載する文章として使うことはできなさそうだった。
自分の言葉に寄せる
文章の自動生成の機能を自分の補助役として使う為には、意図したデータを学習させる必要がある。そうでないと、どこかおかしなデータが出力される。例えば自分自身に寄せた表現をしてほしいなら自分自身の表現を、特定のドキュメントであればその表現を学習させたい。ドキュメントを書いているのに、いきなり歌詞が出てきては困るのだ。幸い、このブログに記述している文章がそこそこの量ある。学習させるにはちょっと少ない気もするが、そのあたりはあまり深く考えず、やってみることにする4。
ファインチューニング
モデルを最初からトレーニングする事もできなくはない。ただし、それには時間もかかるし、多くのデータも必要だし、計算リソースも必要になる。それらを簡略化する方法をここでは試す。これはファインチューニングと呼ばれる。トレーニング済みモデルを、用途に応じたデータセットを用いて再トレーニングする事でパラメータを調整し、期待する挙動の状態に近づける。
ライブラリのインストールと事前学習済みモデルの取得
まず、必要なライブラリをインストールする。
pip install transformers==4.20.1 sentencepiece
次にPythonインタプリタを起動し、トークナイザーとモデルを読み込む。この時にダウンロードが走る。今回は事前学習済みモデルに rinna/japanese-gpt2-small
を使用した56
from transformers import AutoTokenizer, AutoModelForCausalLM
t = AutoTokenizer.from_pretrained("rinna/japanese-gpt2-small")
m = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-small")
再トレーニング
再トレーニング用のデータセットとして、このサイトの掲載している記事の原稿(Org-modeのファイル)を適当にcatで繋げて a.txt
という1つのテキストファイルに出力した。このファイルを読み込む。block_sizeは文章の長さのサイズで、揃える必要がある7。
その他のトレーニングに必要な引数の設定を行う。
引数 | 意味 |
---|---|
output_dir | 保存先のディレクトリへのパス。存在しなければ作成される。 |
overwrite_output_dir | 真の場合、ファイルを上書きする。 |
num_train_epochs | エポック数。 |
per_device_train_batch_size | バッチサイズ。 |
logging_steps | 処理の経過表示をする間隔。 |
save_steps | 保存間隔。例えば10を指定すると、10ステップ毎にモデルを保存する。 |
ここまでで再トレーニングの準備が完了した。それでは再トレーニングする。データや環境にもよるが、この処理はかなり時間がかかる。
私の環境(型落ちのMacBook Pro)では8時間強かかった。夜実行を開始し寝て、朝起きたらトレーニングは終了していた。GPUなどは使用してない環境でもあるため、とても遅かった。
文章の生成
それでは文章を生成する。ここでは「こんにわ、私は」という言葉を、元の言葉として続きの文章を生成してみる。
input_ids = tokenizer.encode("こんにわ、私は", return_tensors="pt",add_special_tokens=False)
input_idsにはtorch.Tensorのインスタンスが格納される。
これを用いて新たな文章となるデータを生成する。
with torch.no_grad():
output = m.generate(
input_ids,
max_length=100,
min_length=100,
do_sample=True,
top_k=500,
top_p=0.95,
pad_token_id=tokenizer.pad_token_id,
bos_token_id=tokenizer.bos_token_id,
eos_token_id=tokenizer.eos_token_id,
num_return_sequences=1,
)
outputにはtorch.Tensorのインスタンスが格納される。
トークナイザーを用いてこのデータを言葉に変換する。
message_list = t.batch_decode(output,skip_special_tokens=True)
message_listには文字列のリストが格納される。この文字列が生成された文章となる。
今回は以下のような文章が生成された。
所感
かなりイマイチな結果だ。ChatGPTなどのGPT-3.5以降のアルゴリズムで構築されたモデルが生成する文章と比較すると、どうも不自然で違和感のある文章だ。トレーニング用のデータの量やクリーニングの度合い、過去世代のアルゴリズムであることを考えると、これは仕方がないようにも思う。ただし、トレーニングや文章生成が、ローカル(しかも型落ちのMac Book Pro)の環境で実行できる事を確認できた事には意味があったように思う。
作業にあたっては https://zenn.dev/robes/articles/24ee45dd81636a]] を参考にした。
smallとは銘打っているが、それでも400MB強はある。もしかしたら、400MB程度は今の時代では小さいと感じるかもしれないが、それでも、少しは気を付ける必要のある大きさではあると思う。
何と揃える必要があるの?元データ?