CLOVER🍀

That was when it all began.

Transformersでマスクされたトークンを予測してみる(MLM:Masked Language Modeling)

これは、なにをしたくて書いたもの?

Transformersかつ日本語で、なにか動かしてみたいなと思って題材を探していたのですが、MLM(Masked Language Modeling)が
ちょうど良さそうだったので試してみました。

MLM(Masked Language Modeling)

MLM(Masked Language Modeling)は、マスクされたテキストからオリジナルのテキストを予測するものです。

A pretraining task where the model sees a corrupted version of the texts, usually done by masking some tokens randomly, and has to predict the original text.

Glossary / masked language modeling (MLM)

TransformersにおけるMLMタスクの説明は、こちらにあります。

Masked language modeling

MLMをサポートしているモデルは以下になります。

You can finetune other architectures for masked language modeling following the same steps in this guide. Choose one of the following architectures:
ALBERT, BART, BERT, BigBird, CamemBERT, ConvBERT, Data2VecText, DeBERTa, DeBERTa-v2, DistilBERT, ELECTRA, ERNIE, ESM, FlauBERT, FNet, Funnel Transformer, I-BERT, LayoutLM, Longformer, LUKE, mBART, MEGA, Megatron-BERT, MobileBERT, MPNet, MRA, MVP, Nezha, Nyströmformer, Perceiver, QDQBert, Reformer, RemBERT, RoBERTa, RoBERTa-PreLayerNorm, RoCBert, RoFormer, SqueezeBERT, TAPAS, Wav2Vec2, XLM, XLM-RoBERTa, XLM-RoBERTa-XL, X-MOD, YOSO

今回はBERTを使ってみたいと思います。

BERT

BERT

Transformersにおける、BERTモデルの説明はこちら。

BERT

BERTは、トロント書籍コーパスWikipediaで構成される大規模なコーパスの上に構築された、マスクされた言語モデル目標と次の文を
予測の組み合わせを使用した、事前学習済みの双方向Transformerです。

It’s a bidirectional transformer pretrained using a combination of masked language modeling objective and next sentence prediction on a large corpus comprising the Toronto Book Corpus and Wikipedia.

要するに、補完したい単語の前後のトークンを見て、その文脈から推論できるように学習したモデルのようです。

BERTのページを見ていると、MLM以外にも次の文の予測、分類、多肢選択、質疑応答などができそうです。

なお、BERT自体はGoogleが提案した言語モデルのようです。

[1810.04805] BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

BERT (言語モデル) - Wikipedia

BERT Japanese

BERTの特徴はわかりましたが、これを日本語で使おうと思うと単語を認識する必要があるらしく、日本語向けのBERTがTransformersでは
提供されています。

BertJapanese

トークン化にはMeCab(のラッパーであるfugashi)とWordPieceを使用していて、実体としては東北大学が作成したもののようです。

GitHub - cl-tohoku/bert-japanese: BERT models for Japanese text.

モデルはHugging Face Hubで公開されています。

cl-tohoku (Tohoku NLP Group)

これは、CC-100コーパスで学習した後にWikipediaコーパスで学習するという2段階の学習を行ったもののようです。

モデルには複数の種類があり、これらのモデルをベースにファインチューニングして評価したJGLUEベンチマークで結果が記載してあります。

Pretrained Japanese BERT models / Model Performances

今回は、cl-tohoku/bert-base-japanese-v3を使ってMLMを試してみたいと思います。

cl-tohoku/bert-base-japanese-v3 · Hugging Face

余談:JGLUE

JGLUEというのは「Japanese General Language Understanding Evaluation」の略で、日本語の一般的な
NLU(Natural Language Understanding)の能力を測定するためのベンチマークです。

GitHub - yahoojapan/JGLUE: JGLUE: Japanese General Language Understanding Evaluation

NLUは、テキストの内容を理解することに関するすべてのタスクです。

Glossary / Natural language understanding (NLU)

もともとGLUE(General Language Understanding Evaluation)というベンチマークがあり、日本語理解能力に関する版として
構築されているのがJGLUEのようです。Yahoo Japanと早稲田大学により構築されたのがJGLUEです。

日本語言語理解ベンチマークJGLUEの構築 〜 自然言語処理モデルの評価用データセットを公開しました - Yahoo! JAPAN Tech Blog

JGLUEは以下のタスクとデータセットで構成されています。

タスク データセット 説明
テキスト分類(Text Classification) MARC-ja テキストを肯定的(positive)と否定的(negative)でラベル付けして判定する
JCoLA (未公開)
文ペアの分類(Sentence Pair Classification) JSTS 2つの文の類似度を0〜5(5が最も類似)で判定する
JNLI 含意(entailment)、矛盾 (contradiction)、中立(neutral)のいずれの推論関係かを推定する
質疑応答(QA) JSQuAD 抽出型質疑応答(extractive question answering)または機械読解(machine reading comprehension)と呼ばれ、質問文と答えを含む可能性のある文章を元に解答箇所を抽出する
JCommonsenseQA 常識推論能力を評価するための多肢選択式質疑応答(multiple choice question answering)のデータセット

JGLUEはデータセットが提供されており、Hugging FaceのTransformersでファインチューニングする方法も書かれています。

https://github.com/yahoojapan/JGLUE/tree/v1.1.0/fine-tuning

つまり、こちらを使って既存のモデルをファインチューニングするとMARC-jaを使った肯定・否定の判定や、JSTSを使ったテキストの
類似度を判定するようなタスクが実行できるということになります。

先述の東北大学のBERTもそのようにファインチューニングした旨が記載されていましたし、JGLUEにもいくつかのモデルで実行した
結果が記載されています。

  • Tohoku BERT base
  • Tohoku BERT base (char)
  • Tohoku BERT large
  • NICT BERT base
  • Waseda RoBERTa base
  • Waseda RoBERTa large (s128)
  • Waseda RoBERTa large (s512)
  • XLM RoBERTa base
  • XLM RoBERTa large

JGLUE: Japanese General Language Understanding Evaluation / Baseline Scores

BERT以外にもRoBERTa(BERTを改善したモデル)、XLM RoBERTa(XLM-R:RoBERTaの他言語版)などもあるのだなと思いつつも、
今回は学習まではさせないので東北大学のBERTをそのまま使います。

環境

今回の環境はこちら。

$ python3 --version
Python 3.10.12


$ pip3 --version
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

CPU環境です。

Transformersのマスクされたトークンを予測するタスクを試す

では、Transformersを使ってMLMを試してみたいと思います。

仮想環境の設定。

$ python3 -m venv venv
$ . venv/bin/activate

BertJapaneseを見ると、BERTの日本語モデルはtransformers[ja]に含まれているようです。

BertJapanese

Transformersを含め、以下のようにインストール。

$ pip3 install transformers[torch,ja]

インストールされたライブラリー。

$ pip3 list
Package                  Version
------------------------ ----------
accelerate               0.25.0
certifi                  2023.11.17
charset-normalizer       3.3.2
filelock                 3.13.1
fsspec                   2023.12.2
fugashi                  1.3.0
huggingface-hub          0.20.1
idna                     3.6
ipadic                   1.0.0
Jinja2                   3.1.2
MarkupSafe               2.1.3
mpmath                   1.3.0
networkx                 3.2.1
numpy                    1.26.3
nvidia-cublas-cu12       12.1.3.1
nvidia-cuda-cupti-cu12   12.1.105
nvidia-cuda-nvrtc-cu12   12.1.105
nvidia-cuda-runtime-cu12 12.1.105
nvidia-cudnn-cu12        8.9.2.26
nvidia-cufft-cu12        11.0.2.54
nvidia-curand-cu12       10.3.2.106
nvidia-cusolver-cu12     11.4.5.107
nvidia-cusparse-cu12     12.1.0.106
nvidia-nccl-cu12         2.18.1
nvidia-nvjitlink-cu12    12.3.101
nvidia-nvtx-cu12         12.1.105
packaging                23.2
pip                      22.0.2
plac                     1.4.2
psutil                   5.9.7
PyYAML                   6.0.1
regex                    2023.12.25
requests                 2.31.0
rhoknp                   1.3.0
safetensors              0.4.1
setuptools               59.6.0
SudachiDict-core         20230927
SudachiPy                0.6.8
sympy                    1.12
tokenizers               0.15.0
torch                    2.1.2
tqdm                     4.66.1
transformers             4.36.2
triton                   2.1.0
typing_extensions        4.9.0
unidic                   1.1.0
unidic-lite              1.0.8
urllib3                  2.1.0
wasabi                   0.10.1

容量。

$ du -sh venv
5.2G    venv

マスクする箇所は[MASK]と表記すればよいようなので、以下のようにpipelineで作成。

fill_mask_pipeline.py

import time
from transformers import pipeline

start_time = time.perf_counter()

mask_filler = pipeline("fill-mask", model="cl-tohoku/bert-base-japanese-v3")

texts = [
    "日本の首都は[MASK]である",
    "魚は[MASK]を泳ぐ",
    "吾輩は[MASK]である"
]

for text in texts:
    outputs = mask_filler(text, top_k=3)
    print(f"input = {text}:")
    print("outputs:")

    for output in outputs:
        print(f"  {output}")

    print()

elapsed_time = time.perf_counter() - start_time
print(f"elapsed time = {elapsed_time:.3f} sec")

パイプラインの使用例はこちらを参考に。

Masked language modeling

使用しているモデルはこちらですね。

cl-tohoku/bert-base-japanese-v3 · Hugging Face

3つの文で試してみます。

texts = [
    "日本の首都は[MASK]である",
    "魚は[MASK]を泳ぐ",
    "吾輩は[MASK]である"
]

実行結果。

fill_mask_pipeline.py
input = 日本の首都は[MASK]である:
outputs:
  {'score': 0.8841696977615356, 'token': 12569, 'token_str': '東京', 'sequence': '日本 の 首都 は 東京 で ある'}
  {'score': 0.024819981306791306, 'token': 12759, 'token_str': '大阪', 'sequence': '日本 の 首都 は 大阪 で ある'}
  {'score': 0.020863836631178856, 'token': 13017, 'token_str': '京都', 'sequence': '日本 の 首都 は 京都 で ある'}

input = 魚は[MASK]を泳ぐ:
outputs:
  {'score': 0.4581770598888397, 'token': 18965, 'token_str': '水中', 'sequence': '魚 は 水中 を 泳ぐ'}
  {'score': 0.10925627499818802, 'token': 23309, 'token_str': '水面', 'sequence': '魚 は 水面 を 泳ぐ'}
  {'score': 0.0645589679479599, 'token': 27988, 'token_str': '海中', 'sequence': '魚 は 海中 を 泳ぐ'}

input = 吾輩は[MASK]である:
outputs:
  {'score': 0.4953293800354004, 'token': 3767, 'token_str': '猫', 'sequence': '吾輩 は 猫 で ある'}
  {'score': 0.04334617033600807, 'token': 20277, 'token_str': '勇者', 'sequence': '吾輩 は 勇者 で ある'}
  {'score': 0.037576641887426376, 'token': 3732, 'token_str': '犬', 'sequence': '吾輩 は 犬 で ある'}

elapsed time = 11.969 sec

スコアの高いものを見ると、それっぽい文がsequenceとして現れています。

いくつか試してみましたが、こういうキレイなハマり方をしない文章も出てきましたね。

続いて、Auto Classesで作成してみます。AutoTokenizerAutoModelForMaskedLMを使えば良さそうです。

fill_mask_autoclasses.py

import time
from transformers import AutoTokenizer, AutoModelForMaskedLM

start_time = time.perf_counter()

tokenizer = AutoTokenizer.from_pretrained("cl-tohoku/bert-base-japanese-v3")
model = AutoModelForMaskedLM.from_pretrained("cl-tohoku/bert-base-japanese-v3")

texts = [
    "日本の首都は[MASK]である",
    "魚は[MASK]を泳ぐ",
    "吾輩は[MASK]である"
]

for text in texts:
    inputs = tokenizer(text, return_tensors='pt')
    outputs = model.generate(**inputs, max_new_tokens=15)
    generated_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True)
    print(f"input = {text}:")
    print("outputs:")

    for generated_text in generated_texts:
        print(f"  {generated_text}")

    print()

elapsed_time = time.perf_counter() - start_time
print(f"elapsed time = {elapsed_time:.3f} sec")

実行結果。

$ python3 fill_mask_autoclasses.py
input = 日本の首都は[MASK]である:
outputs:
  日本 の 首都 は で ある 日本 国 の 首都 一覧 # 日本 の 首都 一覧 日本 の 歴史 # 歴史

input = 魚は[MASK]を泳ぐ:
outputs:
  魚 は を 泳ぐ 魚 一覧 深海 魚 一覧 深海 魚 一覧 # 深海 魚 # 魚類 深海 魚

input = 吾輩は[MASK]である:
outputs:
  吾輩 は で ある 」 ) 。 」 ( 「 吾輩 は 猫 」 「 そして 猫 」

elapsed time = 4.284 sec

ちょっと変?です。

そこで、デコードする際にskip_special_tokens=Trueを指定しないようにしてみます。

    #generated_texts = tokenizer.batch_decode(outputs, skip_special_tokens=True)
    generated_texts = tokenizer.batch_decode(outputs)

結果。

$ python3 fill_mask_autoclasses.py
input = 日本の首都は[MASK]である:
outputs:
  [CLS] 日本 の 首都 は [MASK] で ある [SEP] 日本 国 の 首都 一覧 # 日本 の 首都 一覧 日本 の 歴史 # 歴史

input = 魚は[MASK]を泳ぐ:
outputs:
  [CLS] 魚 は [MASK] を 泳ぐ [SEP] 魚 一覧 深海 魚 一覧 深海 魚 一覧 # 深海 魚 # 魚類 深海 魚

input = 吾輩は[MASK]である:
outputs:
  [CLS] 吾輩 は [MASK] で ある [SEP] 」 ) 。 」 ( 「 吾輩 は 猫 」 「 そして 猫 」

elapsed time = 4.136 sec

どうやら、pipelineで実行した時には[MASK]トークンを入れ込むところなどの面倒をみていてくれたみたいですね…。

おわりに

TransformersでMLMを使って、マスクされたトークンを予測してみました。

MLMというよりはBERTやJGLUEなどの周辺知識の勉強になった気がしますが、これはこれで良いかなと思います。

Transformersの使い方として、Auto Classesを全然使いこなせていない感じがするので…ここはちょっとなんとかしたいですね…。