とりあえず動かしてみる自然言語処理入門 ~ BERT MLM 編 ~

こんにちは。カスタマーサクセス部 研究チームリサーチャーの坂田です。 レトリバでは、主にPoCやPoC支援ツールの開発、話し言葉関連の研究に取り組んでいます。

今回は、最近自然言語処理に興味を持ち始めた方に向けてPythonを使ってBERTを試す方法をご紹介します。

BERTとは?

BERT自体に関しては、Web上に分かりやすい解説記事が既に多く存在していますので、ごく簡単な説明とさせて頂きます。 BERTは、2018年の10月にGoogleから発表され、2019年の6月のNAACL 2019に採択されました。 大規模な文書群から、それぞれの語彙がどのような使われ方をしているのかを統計的な言語モデルの学習を行います。 BERTでは、言語モデルの学習に、以下の2つのタスクを解きます

  • Masked Language Model (MLM)
    文書中の単語をランダムに隠し、それが何なのかを当てるタスクです。

  • Next Sentence Prediction (NSP)
    2つの文A,Bがあった時に、それらが文書中で連続して出てくるものなのかを当てるタスクです。

画期的な点の一つに、この言語モデルを使用することで、実際に解くべき課題のラベル付きデータが比較的少量でも高い精度を実現したことがあります。 例えば、Twitterのツイートがあったときに、それがポジティブなことを言っているのかネガティブなことを言っているのかを当てる機械学習モデルを作りたいとします。その場合、ツイートと{positive, negative}のラベルの組が大量に必要です。 BERTは、事前に学習した言語モデルの情報を使うことで、このツイート・ラベルの組が比較的少量で済んだり、 同じ数のラベル付きデータでより良いスコアを出すことが可能になっています。

MLMを試してみる

本記事では、Transformersというライブラリを使って、日本語のBERTを簡単に動かしてみる方法をご紹介します。 TransformersはHuggingFace社が公開しているPython用ライブラリで、BERTやその発展形の言語モデルを簡単に使用出来るように設計されています。 以下では、先ほどご紹介したMLMがどれほど学習出来ているのかを試します。

必要な各種依存ライブラリのインストール

pythonが使用可能な環境ならば、基本的には、依存ライブラリを含めて、pipコマンドを使用することで簡単にインストールが可能です。 ただし、transformersの使用には、Deep Learning用ライブラリのPytorchまたは、Tensorflowの2系のインストールが必要です。 どちらを使うかで、使用出来るモデルや使用するAPIが多少異なります。本記事では、Pytorchを使用します。 また、日本語では、英語などの単語ごとにスペースで区切られている言語と違い、単語ごとに区切る処理が必要になります。 そのため、形態素解析器MeCabのラッパーfugashiMeCab用辞書のipadicのインストールが必要です。

pip install torch transformers fugashi ipadic

学習済み言語モデル

Transformersでは、学習済みの言語モデルを使用出来るようになっています。英語のモデルが多いですが、最近は様々な言語のモデルが追加されてきています。 日本語でも、東北大学が公開したBERTの学習済みモデルが使用可能です。こちらは、日本語のWikipediaを使ってBERTの学習が行われています。

実際のPythonコード

以下のPythonスクリプトを実行すると、[MASK]という文字列に当てはまるものをBERTが予測します。

import transformers 
from transformers import BertConfig, BertJapaneseTokenizer, BertForMaskedLM
from transformers import pipeline

config = BertConfig.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
model = BertForMaskedLM.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking')
fill_mask = pipeline('fill-mask', model=model, tokenizer=tokenizer, config=config)
print(fill_mask('誕生日に[MASK]を食べるのが楽しみだ。'))
print(fill_mask('校庭に[MASK]が入ってきて、皆大騒ぎだ。'))
print(fill_mask('将来は、[MASK]になるのが夢だ。'))
print(fill_mask('太郎は、[MASK]部のエースだ。'))

それぞれの入力と出力結果

BERTに入力した[MASK]を含む文と、出力結果をそれぞれ見ていきます。

  • 入力1
誕生日に[MASK]を食べるのが楽しみだ。
  • 出力1
[{'sequence': '[CLS] 誕生 日 に チョコレート を 食べる の が 楽しみ だ 。 [SEP]', 'score': 0.11986105144023895, 'token': 14532, 'token_str': 'チョコレート'},
 {'sequence': '[CLS] 誕生 日 に ケーキ を 食べる の が 楽しみ だ 。 [SEP]', 'score': 0.07337545603513718, 'token': 17960, 'token_str': 'ケーキ'},
 {'sequence': '[CLS] 誕生 日 に パン を 食べる の が 楽しみ だ 。 [SEP]', 'score': 0.04976422339677811, 'token': 3469, 'token_str': 'パン'}, 
{'sequence': '[CLS] 誕生 日 に ワイン を 食べる の が 楽しみ だ 。 [SEP]', 'score': 0.04545435681939125, 'token': 5299, 'token_str': 'ワイン'},
 {'sequence': '[CLS] 誕生 日 に 肉 を 食べる の が 楽しみ だ 。 [SEP]', 'score': 0.041543059051036835, 'token': 2990, 'token_str': '肉'}]

「ワイン」以外の予測値は食べ物なので、ある程度「食べる」という単語と食べ物を結び付けられているような気がしますね。恐らく多くの人は、「ケーキ」を当てはめるのではないかと思いますが、「ケーキ」も予測値の2番目に来ています。

  • 入力2
校庭に[MASK]が入ってきて、皆大騒ぎだ。
  • 出力2
[{'sequence': '[CLS] 校庭 に 生徒 が 入っ て き て 、 皆 大騒ぎ だ 。 [SEP]', 'score': 0.13585437834262848, 'token': 2845, 'token_str': '生徒'}, 
{'sequence': '[CLS] 校庭 に 人 が 入っ て き て 、 皆 大騒ぎ だ 。 [SEP]', 'score': 0.06785658001899719, 'token': 53, 'token_str': '人'},
 {'sequence': '[CLS] 校庭 に 犬 が 入っ て き て 、 皆 大騒ぎ だ 。 [SEP]', 'score': 0.031415294855833054, 'token': 2928, 'token_str': '犬'},
 {'sequence': '[CLS] 校庭 に 幽霊 が 入っ て き て 、 皆 大騒ぎ だ 。 [SEP]', 'score': 0.030851121991872787, 'token': 15573, 'token_str': '幽霊'}, 
{'sequence': '[CLS] 校庭 に 子供 が 入っ て き て 、 皆 大騒ぎ だ 。 [SEP]', 'score': 0.029350435361266136, 'token': 1803, 'token_str': '子供'}]

ひと昔前はあるあるネタだった「校庭に犬が入ってくる。」ですが、BERTも予測値の3番手に出してきています。

  • 入力3
将来は、[MASK]になるのが夢だ。
  • 出力3
[{'sequence': '[CLS] 将来 は 、 声優 に なる の が 夢 だ 。 [SEP]', 'score': 0.0654960423707962, 'token': 3341, 'token_str': '声優'}, 
{'sequence': '[CLS] 将来 は 、 俳優 に なる の が 夢 だ 。 [SEP]', 'score': 0.04995379596948624, 'token': 2125, 'token_str': '俳優'}, 
{'sequence': '[CLS] 将来 は 、 アナウンサー に なる の が 夢 だ 。 [SEP]', 'score': 0.046676404774188995, 'token': 3648, 'token_str': 'アナウンサー'}, 
{'sequence': '[CLS] 将来 は 、 女優 に なる の が 夢 だ 。 [SEP]', 'score': 0.043845877051353455, 'token': 2396, 'token_str': '女優'}, 
{'sequence': '[CLS] 将来 は 、 歌手 に なる の が 夢 だ 。 [SEP]', 'score': 0.041193947196006775, 'token': 2698, 'token_str': '歌手'}]

子供の夢として良くありそうなものが並んでいますね。

  • 入力4
太郎は、[MASK]部のエースだ。
  • 出力4
[{'sequence': '[CLS] 太郎 は 、 野球 部 の エース だ 。 [SEP]', 'score': 0.8085199594497681, 'token': 1201, 'token_str': '野球'},
 {'sequence': '[CLS] 太郎 は 、 この 部 の エース だ 。 [SEP]', 'score': 0.02859514206647873, 'token': 70, 'token_str': 'この'}, 
{'sequence': '[CLS] 太郎 は 、 サッカー 部 の エース だ 。 [SEP]', 'score': 0.024342671036720276, 'token': 1301, 'token_str': 'サッカー'},
 {'sequence': '[CLS] 太郎 は 、 ラグビー 部 の エース だ 。 [SEP]', 'score': 0.011369947344064713, 'token': 6252, 'token_str': 'ラグビー'},
 {'sequence': '[CLS] 太郎 は 、 陸上 部 の エース だ 。 [SEP]', 'score': 0.008638563565909863, 'token': 3124, 'token_str': '陸上'}]

「この部」と濁されてしまったものもありますが、概ね中高の部活動として妥当なものが並んでいますね。

おわりに

今回は、日本語のBERTを簡単に試してみる方法をご紹介しました。BERTも、とりあえず動かすというだけなら、数行のコードを書けば良いという時代になりました。 Masked Language Modelという枠組みが、ある程度上手くいっているということがお分かり頂けたと思います。 機会があれば、もう少し高度なタスクに取り組む方法もご紹介したいと思います。

参考