自然言語処理が簡単にできるhugging faceで日本語の言語モデルを作成

前回は英語の分類モデルを作成したが今回は日本語の言語モデルを作成してみるぞ

英語のように簡単につかえるんですか

データがあれば簡単に使えるぞ

動作環境

Google Colabで動作確認しました。

下記の記事で環境構築方法を記述しています。

言語モデルについて

シンプルに説明すると単語データが与えられたときに次の単語候補を予測するモデルです。

例えば下記のようなデータが与えられたとき

”今日は日曜焼き肉定食を”

次の候補として

”たべる”

を出力するようなモデルです。

データの準備

下記のページにある”How to fine-tune a model on language modeling“を参考にしました。

https://huggingface.co/transformers/notebooks.html?highlight=notebook

必要なライブラリを取得します。

! pip install datasets transformers

下記のコードで日本語のwikipediaのデータを取得します。

tensorflow_datasetsを使用すると簡単に取得できます。

本来は’train’データを取得して学習に使用したほうが良いのですが、データサイズが大きいため、今回は’valid’を代替させてもらいました。

import tensorflow_datasets as tfds

ds_valid = tfds.load('wiki40b/ja', split='validation')
ds_test = tfds.load('wiki40b/ja', split='test')

データの必要な部分を抽出してcsvファイルに出力します。

import os

def extract_output_csv(file_name, tf_data):
    divided_word_list = ['_START_PARAGRAPH_']

    start_section = False
    count = 0
    
    if os.path.isfile(file_name):
        os.remove(file_name)
    with open(file_name, 'a') as f:
        f.write('text\n')
        for wiki in tf_data.as_numpy_iterator():
            for text in wiki['text'].decode().split('\n'):
                if start_section is True:
                    for each_sentence in text.split('。')[:-1]:
                        each_sentence = each_sentence.replace('_NEWLINE_', '')
                        each_sentence = each_sentence.replace(',', '、')
                        each_sentence = each_sentence + '。'
                        if ',' in each_sentence:
                            continue
                        if len(each_sentence) <= 1:
                            continue
                        f.write(each_sentence + '\n')
                        count += 1
                    start_section = False
                if text in divided_word_list:
                    start_section = True

wiki_file_valid = 'wiki_40b_valid.csv'
extract_output_csv(wiki_file_valid, ds_valid)

wiki_file_test = 'wiki_40b_test.csv'
extract_output_csv(wiki_file_test, ds_test)

データサイズが大きく時間がかかるので、今回は一部のデータを抽出して試します。

! head -n 10000 wiki_40b_valid.csv > wiki_40b_valid_part.csv
! head -n 10000 wiki_40b_test.csv > wiki_40b_test_part.csv

csvファイルに書き込んだこれらのデータを読み込みます。

from datasets import load_dataset
wiki_file_valid = 'wiki_40b_valid_part.csv'
wiki_file_test = 'wiki_40b_test_part.csv'
datasets = load_dataset("text", data_files={"train": wiki_file_valid, "validation": wiki_file_test})

データが適切に読み込まれているか確認します。

datasets["train"][10]

下記のように確認することができます。

{'text': '中京圏や首都圏のヒートアイランドによる昇温、高層ビル群が「壁」となることで熱を逃がすための対流(海陸風や山谷風)が妨げられたことなども無視できない要因である。'}

ランダムにデータをピックアップして中身を確認してみます。

from datasets import ClassLabel
import random
import pandas as pd
from IPython.display import display, HTML

def show_random_elements(dataset, num_examples=10):
    assert num_examples <= len(dataset), "Can't pick more elements than there are in the dataset."
    picks = []
    for _ in range(num_examples):
        pick = random.randint(0, len(dataset)-1)
        while pick in picks:
            pick = random.randint(0, len(dataset)-1)
        picks.append(pick)
    
    df = pd.DataFrame(dataset[picks])
    for column, typ in dataset.features.items():
        if isinstance(typ, ClassLabel):
            df[column] = df[column].transform(lambda i: typ.names[i])
    display(HTML(df.to_html()))
show_random_elements(datasets["train"])

データの前処理

自然言語処理では文章を特定の単位に分けるトークナイズ処理が必要になります。

下記は文字ベースでトークナイズした例になります。

”今日は日曜焼き肉定食を”

”今”, “日”, “は”, “日”, “曜”, “焼”, “き”, “肉”, “定”, “食”, “を”

モデルごとに必要なトークナイズ処理が異なります。これを実装するのは手間なのですがhuggingfaceはこの点もカバーしてくれているので簡単に使用することができます。

日本語のトークナイズは外部ライブラリが必要になるので下記で必要なライブラリを導入します。

! pip install fugashi ipadic

下記でBERTの文字単位で分かち書きするためのトークナイザーを取得します。

from transformers import BertJapaneseTokenizer, BertForMaskedLM
    
model_checkpoint = "cl-tohoku/bert-base-japanese-char"
tokenizer = BertJapaneseTokenizer.from_pretrained(model_checkpoint, use_fast=True, sep_token='', cls_token='')

先程作成したデータにトークナイズ処理するための関数を設定します。

def tokenize_function(examples):
    return tokenizer(examples["text"])

トークナイズ処理を行います。下記のようなmap関数を使用した記述で全データに処理を実行できます。バッチ単位実行、複数CPUでの実行ができるため高速に実行が可能です。

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

実行の際は下記のように進捗状況を把握することができます。

データをまとめて処理を行うための関数を設定します。

block_size = 128
def group_texts(examples):
    # Concatenate all texts.
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the small remainder, we could add padding if the model supported it instead of this drop, you can
        # customize this part to your needs.
    total_length = (total_length // block_size) * block_size
    # Split by chunks of max_len.
    result = {
        k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
        for k, t in concatenated_examples.items()
    }
    result["labels"] = result["input_ids"].copy()
    return result

先程と同様のやり方でmap関数を使用すると一気にデータを処理できます。

lm_datasets = tokenized_datasets.map(
    group_texts,
    batched=True,
    batch_size=1000,
    num_proc=4,
)

処理後のデータを確認してみます。

128のバッチサイズにまとめられたデータを確認することができます。

tokenizer.decode(lm_datasets["train"][1]["input_ids"])

日本語で事前に学習済みのBERTモデルを使用します。BERTに関しては下記の記事をご覧ください。BERTの構成要素であるTransformerに関して説明しています。

from transformers import BertLMHeadModel

model_checkpoint = "cl-tohoku/bert-base-japanese-char"
model = BertLMHeadModel.from_pretrained(model_checkpoint, is_decoder=True)

モデルの学習

学習のために設定するパラメータは下記のコードで設定できます。設定可能なパラメータの詳細は下記リンクになります。

https://huggingface.co/transformers/main_classes/trainer.html?highlight=trainingarguments#transformers.TrainingArguments

from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
    "test-clm",
    evaluation_strategy = "epoch",
    learning_rate=2e-5,
    weight_decay=0.01,
    save_steps=540,
    per_device_train_batch_size=40,
    per_device_eval_batch_size=40,
    num_train_epochs=5
)

学習したモデルと学習データ、評価データを設定します。

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=lm_datasets["train"],
    eval_dataset=lm_datasets["validation"],
)

下記コードで学習を行います。

trainer.train()

下記のように学習の様子を確認できます。

学習したモデルの性能を見てみます。Perplexity は低いほうが良い指標になります。

import math
eval_results = trainer.evaluate()
print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")

では実際に学習したモデルを試してみましょう。

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

実行すると一例として下記のような結果を取得できます。

1
1 9
1 9 5
1 9 5 7
1 9 5 7 年
1 9 5 7 年 、
1 9 5 7 年 、 大
1 9 5 7 年 、 大 学
1 9 5 7 年 、 大 学 の
1 9 5 7 年 、 大 学 の 卒
1 9 5 7 年 、 大 学 の 卒 業
1 9 5 7 年 、 大 学 の 卒 業 、
1 9 5 7 年 、 大 学 の 卒 業 、 2
1 9 5 7 年 、 大 学 の 卒 業 、 2 0
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取 得
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取 得 し
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取 得 し 、
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取 得 し 、 新
1 9 5 7 年 、 大 学 の 卒 業 、 2 0 0 9 年 4 月 1 1 日 付 に 取 得 し 、 新 書

今回はデータの用意が最も簡単な言語モデルをhugging faceで学習してみたぞ

思ったより簡単にできましたね

データが揃っていれば簡単に試せるので自然言語処理において、いろんなことにチャレンジできそうだな

Hugging Faceを用いて日本語のデータのポジネガ分析を行い方は下記をご覧ください。

Close Bitnami banner
Bitnami