Rinnaの言語モデルを使って言語予測してみた

Rinnaの言語モデル出たので試してみたいです。

以前の記事を少し変えるだけで使えるぞ

rinna社が日本語に特化したGPT-2の大規模言語モデルを開発しオープンソース化したので、試してみる記事になります。

https://prtimes.jp/main/html/rd/p/000000009.000070041.html

下記が以前、言語モデルを作成した記事になります。

下記の記事も参考にTwitterからのデータを取得して、言語モデルをファインチューニングしてみます。

Twitterからのデータ取得

必要なライブラリを導入します。

import requests
import pandas as pd
import json
import ast
import pandas as pd

単語をもとに検索してマッチした上位100件のデータを取得します。

def create_twitter_url(key_word, max_results=100):
    mrf = "max_results={}".format(max_results)
    q = "query={} lang:ja".format(key_word)
    url = "https://api.twitter.com/2/tweets/search/recent?{}&{}".format(
        mrf, q
    )
    return url

APIを叩くための認証情報を確保するデータ・セットを作成します。

def create_bearer_token(data):
    return data["search_tweets_api"]["bearer_token"]

API認証情報と先ほど作成したTwitter検索クエリを使って検索結果を取得します。

def twitter_auth_and_connect(bearer_token, url):
    headers = {"Authorization": "Bearer {}".format(bearer_token)}
    response = requests.request("GET", url, headers=headers)
    return response.json()

取得したデータをjson形式に変換します。

def lang_data_shape(res_json):
    data_only = res_json["data"]
    doc_start = '"documents": {}'.format(data_only)
    str_json = "{" + doc_start + "}"
    dump_doc = json.dumps(str_json)
    doc = json.loads(dump_doc)
    return ast.literal_eval(doc)

`key_word_list`にある単語を検索した結果を取得します。

Twitter APIの認証情報を取得する方法は下記に記述しています。

key_word_list = ["陸上", "水泳", "ワクチン", "オリンピック", 'オリンピック日程', 'オリンピックテスト大会', 'オリンピック組織委員会', 'オリンピックチケット', 'オリンピック開催', 'オリンピック ボランティア']
text_list = []

for key_word in key_word_list:
    url = create_twitter_url(key_word)
    data = {'search_tweets_api': 
                {'bearer_token': 作成した認証情報}
          }
    bearer_token = create_bearer_token(data)
    res_json = twitter_auth_and_connect(bearer_token, url)
    documents = lang_data_shape(res_json)

    for doc in documents['documents']:
      text_list.append(doc['text'])

APIを何度も叩くと凍結の恐れがあるのでデータをcsvファイルにして保存します。

pd_data = pd.Series(text_list)
pd_data.to_csv('twitter_data.csv')

学習データと検証データを分けます。

from sklearn.model_selection import train_test_split

twitter_posinaga = pd.read_csv("twitter_data.csv")
x_train, x_test, = train_test_split(twitter_posinaga, train_size=0.95, test_size=0.05)

分けたデータを保存します。

x_train.to_csv('twitter_data_train.csv')
x_test.to_csv('twitter_data_test.csv')

前処理のためにデータのフォーマットを変えて保存します。

import os
import re
csv_update_file = 'twitter_data_train_update.csv'
csv_file = 'twitter_data_train.csv'

with open(csv_update_file, 'w') as fw:
    with open(csv_file, 'r') as f:
        fw.write('text\n')
        for index, data in enumerate(f.readlines()):
            if index == 0:
                continue
            if len(data.split(',')) > 2:
                re_data = data.split(',')[2]
                re_data = re.sub(r',', "、", re_data)
                re_data = re.sub(r'"', '', re_data)
                fw.write(re_data)

評価データを保存します。

csv_update_file = 'twitter_data_test_update.csv'
csv_file = 'twitter_data_test.csv'

with open(csv_update_file, 'w') as fw:
    with open(csv_file, 'r') as f:
        fw.write('text\n')
        for index, data in enumerate(f.readlines()):
            if index == 0:
                continue
            if len(data.split(',')) > 2:
                re_data = data.split(',')[2]
                re_data = re.sub(r',', "、", re_data)
                re_data = re.sub(r'"', '', re_data)
                fw.write(re_data)

Twitterのデータを前処理

前処理を行ってHugging Faceで実装されたRinnaのモデルで学習できるようにします。

Rinnaのモデルとトークナイザーを取得します。

from transformers import T5Tokenizer, AutoModelForCausalLM

tokenizer = T5Tokenizer.from_pretrained("rinna/japanese-gpt2-medium")

model = AutoModelForCausalLM.from_pretrained("rinna/japanese-gpt2-medium")

作成した学習データと検証データを取得します。

from datasets import load_dataset

datasets = load_dataset("csv", data_files={"train": 'twitter_data_train_update.csv', 
                                           "test": 'twitter_data_test_update.csv'})

データをトークナイズ、文章の長さ、attention_maskを取得するための関数を設定します。

def tokenize_function(examples):
    examples = examples['text']
    length = len(tokenizer.encode(examples))
    return {'input_ids': [tokenizer.encode(examples)][0], 
            'attention_mask': [1 for i in range(length)]
            }

先ほど作成した関数を適用します。mapを使用するとnum_procで複数CPUを指定して実行できます。

tokenized_datasets = datasets.map(tokenize_function, num_proc=4, remove_columns=["text"])

バッチ処理を行うためのデータのサイズを揃えるための最大長を取得します。

length_list = []
for i in range(len(tokenized_datasets["train"])):
    length_list.append(len(tokenized_datasets["train"][i]['input_ids']))

max(length_list)

先程取得した最大長までパディングする関数を作成します。これでバッチ処理が可能になります。

パディングしたデータは学習の対象にしたくないので`-100`のラベルを設定します。

from collections import defaultdict
import copy

block_size = max(length_list)
def group_texts(examples):
    result = defaultdict(list)
    for k, t in examples.items():
        size = len(t)
        if k == 'attention_mask':
            for size in range(block_size - size):
              t.append(0)
        else:
            label = copy.deepcopy(t)
            for size in range(block_size - size):
              # PAD data
              t.append(3)
              label.append(-100)
        result[k] = t
    result["labels"] = label
    return result

先程作成した関数を同様に`map`で学習データに適用します。

lm_datasets_train = tokenized_datasets['train'].map(
    group_texts,
    num_proc=4,
)

検証データに適用します。

lm_datasets_test = tokenized_datasets['test'].map(
    group_texts,
    num_proc=4,
)

Rinnaのモデルを使用して学習

学習するためのライブラリを設定します。

from transformers import Trainer, TrainingArguments

学習用パラメータを設定します。

training_args = TrainingArguments(
    "twitter-rinna",
    evaluation_strategy = "epoch",
    num_train_epochs=1,
    save_steps=114,
    per_device_train_batch_size=8,
    per_device_eval_batch_size=8,
    learning_rate=0.5,
    warmup_steps=1000,
)

モデル、パラメータ、学習データ、検証データを学習するためのTrainerを設定します。

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=lm_datasets_train,
    eval_dataset=lm_datasets_test
)

下記のコードでモデルを学習します。

trainer.train()

学習したモデルによる推論

下記のコードで学習したモデルで推論します。

  • sequence = f””: から文字を入力します。
  • input_ids = torch.tensor([tokenizer.encode(sequence)]).to(‘cuda’):入力文字をCPUで処理できるように変換します。
  • next_token_logits = model(input_ids).logits[:, -1, :] :モデルで候補を推論します。
  • filtered_next_token_logits = top_k_top_p_filtering(next_token_logits, top_k=50, top_p=1.0) :候補の上位50件を抽出します。
  • probs = F.softmax(filtered_next_token_logits, dim=-1):推論値を確率値に変換します。
  • next_token = torch.multinomial(probs, num_samples=1):最も高い値のインデックスを取得します。これが予測したトークンになります。
  • generated = torch.cat([input_ids, next_token], dim=-1):次の予測できるように現在の入力と予測値を組み合わせます。
  • resulting_string = tokenizer.decode(generated.tolist()[0]):現在の結果を確認できるようにデコードします。
from transformers import top_k_top_p_filtering
import torch
from torch.nn import functional as F

sequence = f""

input_ids = torch.tensor([tokenizer.encode(sequence)]).to('cuda')
model.to('cuda')

for i in range(30):
    next_token_logits = model(input_ids).logits[:, -1, :]

    filtered_next_token_logits = top_k_top_p_filtering(next_token_logits, top_k=50, top_p=1.0)

    probs = F.softmax(filtered_next_token_logits, dim=-1)

    next_token = torch.multinomial(probs, num_samples=1)

    generated = torch.cat([input_ids, next_token], dim=-1)

    resulting_string = tokenizer.decode(generated.tolist()[0])

    print(resulting_string)
    input_ids = generated

下記のような結果になります。

</s> スポーツ
</s> スポーツ変異
</s> スポーツ変異変異
</s> スポーツ変異変異o
</s> スポーツ変異変異o<unk>
</s> スポーツ変異変異o<unk> :
</s> スポーツ変異変異o<unk> :東京オリンピック
</s> スポーツ変異変異o<unk> :東京オリンピック2013
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態ni
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選し
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよ
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ます
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますです
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです開催
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです開催開催
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです開催開催くない
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです開催開催くないませ
</s> スポーツ変異変異o<unk> :東京オリンピック2013ni事態niは。を当選しよたかった。でも怖のための開催ますですです開催開催くないませ?

言語モデルの実装はほとんど変える必要はなかったですね。

Hugging Faceはいろんなモデルをすぐに使えるのがメリットだ

Close Bitnami banner
Bitnami