機械学習・自然言語処理の勉強メモ

学んだことのメモやまとめ

pytorch-transformersを触ってみる②

はじめに

前回はの入門ということで、QuikStartの内容を触ってみました。
kento1109.hatenablog.com

前回は英語でしたが、日本語のテキストを扱う場合はそのまま使うことは出来ません。
ということで、今回はpytorch-transformersで日本語のテキストを扱ってみようと思います。

Pretrained model

日本語でのPretrained modelとしては、京大の黒橋・河原研究室が公開しているものが有名です。
BERT日本語Pretrainedモデル - KUROHASHI-KAWAHARA LAB

このリソースを利用した既存のやってみたシリーズとしては以下などが参考となります。
Pytorchで日本語のbert学習済みモデルを動かすまで - Qiita
pytorchでBERTの日本語学習済みモデルを利用する - 文章埋め込み編 - Out-of-the-box

今回はこちらのリソースを活用して色々と触っていきます。

準備

ホームページより、Japanese_L-12_H-768_A-12_E-30_BPEをダウンロードします。
Pretrained modelは、Jumann++で形態素解析を行っているので、Jumann++をインストールしておきます。
また、Jumann++をpythonから利用するため、pyknp もインストールしておきます。

以下のようにpythonから形態素解析が出来れば準備OKです。

from pyknp import Juman
jumanpp = Juman()

text = "すもももももももものうち"
result = jumanpp.analysis(text)
print([mrph.midasi for mrph in result.mrph_list()])
# ['すもも', 'も', 'もも', 'も', 'もも', 'の', 'うち']

ただし、今回は触ってみることが目的なので、前処理の形態素解析は必ずしも必要でないです。

読み込み

前回は、以下のように文字列を指定してをロードしました。

model = BertModel.from_pretrained('bert-base-uncased')

今回は、ダウンロードしたpytorch_model.binを指定して読み込みます。
※学習は、BERT(BASE)と同じ設定 (12-layer, 768-hidden, 12-head)で行ったそうです。

model = BertModel.from_pretrained('bert-base-uncased')
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte

しかし、この場合UnicodeDecodeErrorが発生します。

上に挙げた記事はpytorch_pretrained_bertというライブラリを使っていたので、
少々勝手が異なるみたいです。

エラー箇所を見ると、

Constructs a `BertConfig` from a json file of parameters.

に関連する箇所が怪しかったので、以下のようにしてconfigclasssを引数に与えました。

config = BertConfig.from_json_file('Japanese_L-12_H-768_A-12_E-30_BPE/bert_config.json')
model = BertModel.from_pretrained('Japanese_L-12_H-768_A-12_E-30_BPE/pytorch_model.bin', config=config)

とりあえず、エラーは出ていないので問題なさそうです。

bertForMaskedLM

前回同様に、文の一部をMASKする問題を試してみます。

model = BertForMaskedLM.from_pretrained('Japanese_L-12_H-768_A-12_E-30_BPE/pytorch_model.bin', config=config)

この場合の出力は単語数となります。modelの最後の層を見てみると、

(decoder): Linear(in_features=768, out_features=32006, bias=False)

となっているのが分かります。

では、「僕は友達とサッカーをすることが好きだ」の文を形態素解析します。

from pyknp import Juman
jumanpp = Juman()
text = "僕は友達とサッカーをすることが好きだ"
result = jumanpp.analysis(text)
tokenized_text = [mrph.midasi for mrph in result.mrph_list()]
print(tokenized_text)
# ['僕', 'は', '友達', 'と', 'サッカー', 'を', 'する', 'こと', 'が', '好きだ']

となります。
続いて、BERT用の入力に整形し、「サッカー」をMASKしてみます。

tokenized_text.insert(0, '[CLS]')
tokenized_text.append('[SEP]')
masked_index = 5
tokenized_text[masked_index] = '[MASK]'
print(tokenized_text)
['[CLS]', '僕', 'は', '友達', 'と', '[MASK]', 'を', 'する', 'こと', 'が', '好きだ', '[SEP]']

日本語の辞書ファイルを読み込みます。

tokenizer = BertTokenizer("Japanese_L-12_H-768_A-12_E-30_BPE/vocab.txt",
                          do_lower_case=False, do_basic_tokenize=False)

一応、入力系列を確認してみます。

indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
tokens_tensor = torch.tensor([indexed_tokens])
print(tokens_tensor)
#tensor([[    2,  5020,     9, 10729,    12,     4,    10,    22,    30,    11,    30808,     3]])

ここまで来れば、言語は関係ありません。

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 = tokenizer.convert_ids_to_tokens(predicted_indexes.tolist())
print(predicted_tokens)
# ['話', '仕事', 'キス', 'ゲーム', 'サッカー']

5番目にサッカーと予測できています。その他も、MASKとして妥当そうです。

[SEP]は必要??

ここで、思ったのですが、単文の場合は末尾に[SEP]は必要なのでしょうか。
試しに[SEP]無しで予測した結果が以下の通りです。

print(predicted_tokens)
['話', '仕事', 'キス', '会話', 'セックス']

サッカーは挙がってきませんが、概ね結果は同じです。
(サッカーの代わりにセックスと予測したのはなかなか面白いですね。。)

ここから、[SEP]が予測結果に影響を与えていることが分かります。
また、[SEP]は付けておいた方が無難かと思います。(他では結果が異なるかもしれませんが・・)

おわりに

今回は、pytorch-transformersで日本語のテキストを触ってみました。
次回は、fine tuningで目的のタスクに応用してみたいと思います。