【自然言語処理】日本語GPT-2モデルをファインチューニングして文章生成をやってみる
はじめに
2022年11月にOpenAIが公開したchatGPTの反響が凄いですね。
公開6日目にして100万人がユーザー登録したとか。
今のところは、無料で使うことができますが、いずれは利用が制限されるかもしれませんね。
ここでは、chatGPTよりは精度が落ちますが、無料で使うことができて、日本語に特化した事前学習済みモデルもあるGPT-2を採り上げ、文章生成にチャレンジしたいと思います。
具体的にはrinna社が開発したGPT-2モデルを使用します。
事前学習済みモデルだけでもそれなりの文章を生成できますが、せっかくなので、特定のドメインでファインチューニングさせてみて、文章生成をしていきたいと思います。
rinna社の日本語GPT-2の特徴
- 言語モデルは、会話や文章の「人間が使う言葉」を確率としてモデル化したものです。優れた言語モデルとは、確率を正確に推定できるものを指します。例えば、 "確率(吾輩は猫である)>確率(吾輩が猫である)" と推定できるのが、言語モデルの能力です。
- GPT-2は、単語の確率の組み合わせから文の確率を計算する言語モデルです。例えば、 "確率(吾輩は猫である)=確率(吾輩)×確率(は|吾輩)×確率(猫|吾輩,は)×確率(で|吾輩,は,猫)×確率(ある|吾輩,は,猫,で)" のような方法で推定を行います。この性質を用いて、GPT-2は「吾輩は」という単語を入力したとき、次にくる単語として確率が高い「猫」を予測することができます。
- rinna社が公開した日本語GPT-2は、一般的な日本語テキストの特徴を有した高度な日本語文章を自動生成できます。例えば「本日はご参加ありがとうございました。誰も到達していない人工知能の高みへ、ともに」という講演後のメールを想定した文章をGPT-2に入力として続きの文章を自動生成すると、下図のように入力文章の文脈を考慮した文章が生成されます。

モデルの概要
| 言語モデル | パラメータ数 | レイヤー数 | エポック数 | トレーニング時間 | 
|---|---|---|---|---|
| rinna/japanese-gpt-1b | 1.3B | 24 | 10+ | n/a** | 
| rinna/japanese-gpt2-medium | 336M | 24 | 4 | 45 days | 
| rinna/japanese-gpt2-small | 110M | 12 | 3 | 15 days | 
| rinna/japanese-gpt2-xsmall | 37M | 6 | 3 | 4 days | 
ファインチューニング→文章生成の実装
ここからGoogleコラボを使って、日本語GPT-2モデルをファインチューニングし、文章生成を行います。
必要なライブラリーのインストール
transformerとsentencepieceをインストール。transformerはバージョンを指定してください。
!pip install transformers==4.20.1
!pip install sentencepiece
日本語用の事前学習済みモデル
日本語の事前学習モデルとして、rinna/japanease-gpt2-smallを使います。
from transformers import T5Tokenizer,AutoModelForCausalLM
tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-small")
model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-small")
テキストデータの準備
上場企業等が作成した有価証券報告書の「事業等のリスク」を学習させます。
変数dfに収納します。
import pandas as pd
df = pd.read_csv("/content/2203有報セット.csv",index_col=0).reset_index()
df

テキストの整形
ファインチューニングに使用するテキストを用意し、クリーニングしたうえで、テキストファイルに格納します。
事業等のリスクはdfの3列目にあります。そのテキストデータを読み込んでいきます。
import re
texts = []
for i in range(len(df)):
    text = df.iloc[i,3]
    text = re.sub("【[^】]+】","",text)
    text = re.sub("[<>]","",text)
    text = re.sub("[■]","",text)
    text = text.split("。")
    texts.append("。\n".join(text))
train_data_path = "train.text"
with open(train_data_path,"w") as f:
    f.writelines(texts)
訓練の準備
ファインチューニングを行うためのモデル側の準備をします。
- データセットの設定
- データの入力に関する設定
- 訓練に関する設定
- トレーナーの設定
from transformers import TextDataset, DataCollatorForLanguageModeling
from transformers import Trainer, TrainingArguments, AutoModelWithLMHead
#データセットの設定
train_dataset = TextDataset(
    tokenizer = tokenizer,
    file_path = train_data_path,
    block_size = 128 #文章の長さを揃える必要がある
)
#データの入力に関する設定
data_collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm= False
)
# 訓練に関する設定
training_args = TrainingArguments(
    output_dir="./gpt2-ft",  # 関連ファイルを保存するパス
    overwrite_output_dir=True,  # ファイルを上書きするかどうか
    num_train_epochs=3,  # エポック数
    per_device_train_batch_size=8,  # バッチサイズ
    logging_steps=100,  # 途中経過を表示する間隔
    save_steps=800  # モデルを保存する間隔
)
#トレーナーの設定
trainer = Trainer(
    model =model,
    args=training_args,
    data_collator = data_collator,
    train_dataset = train_dataset
)
ファインチューニング(訓練)を実行
%%time
trainer.train()
文章を生成する関数
ファインチューニングしたモデルを使って、文章を生成する関数を作成します。
細かい仕様はコメントの通りです。
def getarate_sentences(seed_sentence):
    x = tokenizer.encode(seed_sentence, return_tensors="pt", 
    add_special_tokens=False)  # 入力
    x = x.cuda()  # GPU対応
    y = model.generate(x, #入力
                       min_length=50,  # 文章の最小長
                       max_length=100,  # 文章の最大長
                       do_sample=True,   # 次の単語を確率で選ぶ
                       top_k=50, # Top-Kサンプリング
                       top_p=0.95,  # Top-pサンプリング
                       temperature=1.2,  # 確率分布の調整
                       num_return_sequences=3,  # 生成する文章の数
                       pad_token_id=tokenizer.pad_token_id,  # パディングのトークンID
                       bos_token_id=tokenizer.bos_token_id,  # テキスト先頭のトークンID
                       eos_token_id=tokenizer.eos_token_id,  # テキスト終端のトークンID
                       bad_word_ids=[[tokenizer.unk_token_id]]  # 生成が許可されないトークンID
                       )  
    generated_sentences = tokenizer.batch_decode(y, skip_special_tokens=True)  # 特殊トークンをスキップして文章に変換
    return generated_sentences
いざ、文章を生成!
seed_sentenceにワードを入れると、それに続く文章をモデルが生成します。
上段の設定で、生成する文章を3としているので、ここでは3つの文章が生成されます。
今回は「コンプライアンスとは」と入れてみました。
seed_sentence = "コンプライアンスとは"  
generated_sentences = getarate_sentences(seed_sentence)  # 生成された文章
for sentence in generated_sentences:
    print(sentence)
出力結果
有価証券報告書に記載される、「コンプライアンスとは」に続く文章として、まあまあ適切ではないでしょうか。
コンプライアンスとは、「企業倫理・法令・社内規範・倫理」、これを基盤に企業倫理とコンプライアンスを基本に
社会における正しい・公平的な判断と行動を実践することとしています。 コンプライアンス体制の基本方針とし
て、社員のコンプライアンス行動憲章に則り、人権尊重、環境保全を重要な経営課題と捉え、
グループ全体のコンプライアンス意識の底流である「コンプライアンス・倫理委員会」を
コンプライアンスとは、企業社会的責任と企業倫理に基づく誠実さ。 「従業員が安心・安全を得られる
コンプライアンスを実践することが企業の社会的責任である。 サプライチェーンを通じて社会の公正な
発展に貢献できる会社をめざす企業理念」として制定してまいりましたが、当社グループでは、事業活動を
行うに当たり、コンプライアンスを経営上の重要課題と位置づけております。 この意識が不十分な場合は、
社員に対し、法令違反や
コンプライアンスとは、リスクが事業活動を通じて損失を生ずり、損失を被るリスクの事です。 また、
これらの行為が、当行の損失認識とも関係し、当行に不利な判断をさせることとなり、当行の業務運営や、
業績、財政状態等に悪影響を及ぼす可能性があります。 また、当行は、役職員に対し、コンプライアンスに
関する基本方針やルールを周知徹底し、コンプライアンス遵守の
モデルの保存
pickleでモデルを保存しておきます。
import pickle
with open("/content/drive/MyDrive/model_gpt2.pkl","wb") as f:
    pickle.dump(model,f)




Discussion