こんにちは、技術研究所の(ウタ)です。

 

この記事は「CRESCO Advent Calender 2019」16日目の記事です。

はじめに

クリスマスまであと僅かですね。みなさん、クリスマスに家族や大切な人に贈るプレゼントは準備できましたか。子供の欲しいものをさりげなく聞き出すのに苦労している方もいらっしゃるのではないでしょうか?

今年AI機械学習の分野では話題になった技術の一つに、BERTと呼ばれる自然言語処理に使われる汎用言語モデルがあります。

今回は、BERT日本語モデルをつかって、文中のプレゼントが入るであろう位置の単語を推測してみました。

BERTとは

BERTは、Googleが2018年10月に発表したモデルで、コードも公開されています。

このモデルやその改良版により、自然言語処理の多くのタスクでSOTA(State Of The Art)が達成されています。

BERT以前の言語モデルは前にある単語から後ろに続く単語を予測したり、文章の中で近い距離にある単語同士の関係を把握したりするだけでしたが、それに対してBERTは文章中の遠い距離にある単語同士の関係を把握したり、文脈を基に文章の各所にあるべき単語を予測したりできるようなったことが、精度の向上に寄与しているといわれています。

BERTの日本語モデル

BERTの事前学習には多大な計算コストが必要となりますが、公開されている事前学習済みモデルを利用することで、手軽に試すことができます。
事前学習済みのモデルはいくつかありますが、今回は下記サイトの京都大学 黒岩・河原研究室 が公開しているBERT日本語Pretrainedモデル を利用しました。
http://nlp.ist.i.kyoto-u.ac.jp/index.php?BERT%E6%97%A5%E6%9C%AC%E8%AA%9EPretrained%E3%83%A2%E3%83%87%E3%83%AB

約30日も事前学習かかったモデルを公開していただけるなんて、ありがたいですね。

環境構築のポイント

環境

今回準備した環境は下記のとおりです。

  • Ubuntu 18.04
  • Python 3.7

インストール

おおまかな流れは下記のとおりです。

  •  JUMAN++と関連ライブラリ等をインストールする
  • 京都大学の黒橋・河原研究室で公開されている学習済みBERTモデルをダウンロードする
  • GoogleのBERTリポジトリをクローンして、日本語用にカスタマイズする

今回は、通常版: Japanese_L-12_H-768_A-12_E-30_BPE.zip (1.6G)をダウンロードしました。

「BERT日本語Pretrainedモデル」のサイトに導入手順へのリンクが張ってありますので、そちらを参考するとインストールできると思います。

ポイントは、BERT日本語Pretrainedモデルで特有なツール、JUMAN++とそれに必要なpykup,Boostライブラリのインストールです。

JUMANN++

JUMAN++は形態素解析システムです。今回は入力される文をBERTで扱う単語の単位に分割するために利用しています。

JUMANL++は下記のリンクもしくは「BERT日本語Pretrainedモデル」からリンクを辿ってダウンロードします。

Jumannを使うために 必要なpyknpとBoostライブラリをインストールします。
pyknpのインストール以下のコマンドを打ちインストールします。

pip install pyknp

Boostライブラリ

JUMANN++の実行に必要なBoostライブラリは、下記のサイトからダウンロードしてビルドします。

Boostライブラリのビルド方法は下記を参考にしました。

今回はJUMANN で1.58以上が必要とのことなので、Old Boost Releasesのリンクをたどって1.58をダウンロードしてインストールしています。

必要なライブラリ等がそろったら下記のようにJUMAN++をコンパイルしてインストールします。

自分のホームディレクトリにインストールする場合には、configure コマンドの –prefixオプションでインストールする場所を指定してください。

wget "http://lotus.kuee.kyoto-u.ac.jp/nl-resource/jumanpp/jumanpp-1.02.tar.xz"
tar xJvf jumanpp-1.02.tar.xz
cd jumanpp-1.02
 ./configure --prefix=/juman_install_dir
(・・・中略・・・)
make
make install

BERT日本語モデルを使って、クリスマスプレゼントに欲しいものを推測してみる

インストールが終わったら、早速試してみましょう。

やっていることは「クリスマスプレゼントを聞き出す文章で、プレゼントに相当する位置の単語をBERTに推測させる」ということです。

下記のようなスクリプトを用意します。

import torch
from transformers import BertTokenizer, BertForMaskedLM, BertConfig
import numpy as np
config = BertConfig.from_json_file('/bert_install_dir/Japanese_L-12_H-768_A-12_E-30_BPE/bert_config.json')
model = BertForMaskedLM.from_pretrained('/bert_install_dir/Japanese_L-12_H-768_A-12_E-30_BPE/pytorch_model.bin', config=config)
bert_tokenizer = BertTokenizer('/bert_install_dir/Japanese_L-12_H-768_A-12_E-30_BPE/vocab.txt',
do_lower_case=False, do_basic_tokenize=False)
from pyknp import Juman
jumanpp = Juman()
from pyknp import Juman
jumanpp = Juman()
text = "サンタさんに何をお願いするの?私は*が欲しいってお願いするの。"
result = jumanpp.analysis(text)
tokenized_text = [mrph.midasi for mrph in result.mrph_list()]
print(tokenized_text)

13行目のtextにクリスマスプレゼントに関連する文章をいれます。

上記のスクリプトを実行すると、JUMANで形態素解析します。これにより、文章は下記のように分割されます。

['サンタ', 'さん', 'に', '何', 'を', 'お', '願い', 'する', 'の', '?', '私', 'は', '*', 'が', '欲しい', 'って', 'お', '願い', 'する', 'の', '。']

次に、先頭に[CLS]トークン、文と文の間と終わりにに[SEP]トークンをそれぞれ挿入します。
BERTに推測させる単語(上記の例では”*”)の位置に[MASK]トークンを置きます。masked_indexにはその位置をいれます。

tokenized_text.insert(0, '[CLS]')
tokenized_text.insert(11, '[SEP]') # 複文対応
tokenized_text.append('[SEP]')
masked_index = 14
tokenized_text[masked_index] = '[MASK]'
print(tokenized_text)

これを実行すると下記のようになります。

['[CLS]', 'サンタ', 'さん', 'に', '何', 'を', 'お', '願い', 'する', 'の', '?', '[SEP]', '私', 'は', '[MASK]', 'が', '欲しい', 'って', 'お', '願い', 'する', 'の', '。', '[SEP]']

さらに、BERTが処理できるように辞書を用いてIDに変換します。

tokens = bert_tokenizer.convert_tokens_to_ids(tokenized_text)
tokens_tensor = torch.tensor([tokens])
print(tokens_tensor)

この出力は下記のようになります。これが、BERTに与えられる入力データになります。

tensor([[ 2, 6658, 1390, 8, 1152, 10, 273, 5701, 22, 5, 1566, 3, 1038, 9, 4, 11, 9482, 7003, 273, 5701, 22, 5, 7, 3]])

入力データがようやくできました。
この入力データの[MASK]の位置に当てはまる単語をBERTに推測させ、尤度の高い順に5つ出力させます。

model.eval()
tokens_tensor = tokens_tensor.to('cuda')
model.to('cuda')
with torch.no_grad():
outputs = model(tokens_tensor)
predictions = outputs[0]
_, predicted_indexes = torch.topk(predictions[0, masked_index], k=5)
predicted_tokens = bert_tokenizer.convert_ids_to_tokens(predicted_indexes.tolist())
print(predicted_tokens)

結果、下記が出力されました。

['何', '君', 'それ', 'あなた', '自分']

関係代名詞が多いですが、なんとなくそれらしい単語が返ってきました。

すこし、回りくどい聞き方をしてみます。

近所でサンタさんに会って何欲しいか聞かれたんだけど。何て言ったらいいかな?
私は、サンタさんに*が欲しいとお願いします。

形態素解析をかけてトークンをつけると下記になります。

['[CLS]', '近所', 'で', 'サンタ', 'さん', 'に', '会って', '何', '欲しい', 'か', '聞か', 'れた', 'んだ', 'けど', '。', '何て', 'いったら', 'いい', 'か', 'な', '?', '私', 'は', 'サンタ', 'さん', 'に', '[MASK]', 'が', '欲しい', 'って', 'お', '願い', 'する', 'の', '。', '[SEP]']

同様に[MASK]の部分に入る単語をBERTに推測させます。
結果、推測された5つの単語は下記にようになりました。

['友達', '何', '子供', '君', 'サイン']

幼子がサンタさんのサインを欲しがっている…そんな微笑ましい光景が浮かびます。

今度は単刀直入に聞いてみます。
同様に*の部分に入る単語をBERTに推測させます。

私はクリスマスプレゼントには*が欲しい!

形態素解析をかけてトークンをつけると下記になります。

['[CLS]', '私', 'は', 'クリスマス', 'プレゼント', 'に', 'は', '[MASK]', 'が', '欲しい', '!', '[SEP]']

これを、BERTに入力した結果は下記になりました。

['君', 'プレゼント', 'サイン', '星', '金']

ハートフルな感じが少し減って、代わりに物欲・金欲が顔を出してきました。
それにしても、クリスマスプレゼントに星が欲しいとは、ずいぶんスケールが大きい望みですね(笑)

おわりに

自然言語処理の汎用的なモデルとして利用が広がっているBERTを、日本語の事前学習済みモデルで手軽に試してみました。
今回は、文章の中の隠された単語を推測するタスクでしたが、BERTは前後の文章の関係性など様々な自然言語のタスクに利用できます。また、ファインチューニングを行って特定の知識領域における推測精度を高めたり、既存の自然言語モデルの前処理としてBERTを利用することで全体の精度を向上するなど、活用例が報告されています。
開発元のGoogleが自社の「Google検索」にBERTの言語モデルを組み込んだと発表しており、今後は実用的なシーンにおける活用が楽しみな技術です。