アイソモカ

知の遊牧民の開発記録

BERT は毎朝味噌汁を作るか 【BertForMaskedLM】

BertMaskedLMで遊んでいて「毎朝あなたのために[MASK]を作ります。」で[MASK]部分に入る語を予測したら 1位が「詩」(確率0.078)、2位が「番組」(確率0.077) …ってなってて、えーそうなん???となった。Wikipediaで学習したBERTは味噌汁作れへんねや。

…と思ったら、これは「味噌汁」がボキャブラリに入ってないためでは?と…

いろいろ文脈を試しても「味噌汁」はどうして出なくて、「[MASK]汁」にしても「味噌」は出なくて、「味噌[MASK]」にすると「# # 汁」が出る。これはなに?

後で元の単語に復元できるよう、単語の先頭でないサブワードに「##」のような記号を付けることが多いので、「味噌汁」が1単語扱いで「味噌」と「##汁」というサブワードに分割されていたように取れます。

たしかにそうだ。

$ grep -n "味噌" BERT-base_mecab-ipadic-bpe-32k/vocab.txt
16023:味噌
$ grep -n "汁" BERT-base_mecab-ipadic-bpe-32k/vocab.txt
16504:汁
30629:##汁

「味噌汁」は、味噌 ##汁という2つのサブワードに分割される。

ということは、穴埋め問題を 毎 ##朝 あなた の ため に [MASK] ##汁 を 作り ます 。 の ように変更して、[MASK] に「味噌」が入ると予測されるかどうか?などしか確認できないな…

上で確認した数字は行番号。(行番号 - 1)がIDになるので、##汁のID 30628を直接指定して MaskedLM に入力しよう。

# bert3_maskedlm_misoshiru.py
import torch
from transformers import BertJapaneseTokenizer, BertForMaskedLM

tknz = BertJapaneseTokenizer.from_pretrained("cl-tohoku/bert-base-japanese")
ids = [2, 979, 28956, 6968, 5, 82, 7, 4, 30628, 11, 2580, 2610, 8, 3]
print(tknz.convert_ids_to_tokens(ids)) 
# ['[CLS]', '毎', '##朝', 'あなた', 'の', 'ため', 'に', '[MASK]', '##汁', 'を', '作り', 'ます', '。', '[SEP]']

mskpos = ids.index(tknz.mask_token_id)
print(mskpos) 
# 7

model = BertForMaskedLM.from_pretrained("cl-tohoku/bert-base-japanese")
ids_tensor = torch.LongTensor(ids).unsqueeze(0)
out = model(ids_tensor) # モデルの出力は要素が1つのタプル

topk = torch.topk(out[0][0][mskpos], k=5)
print(topk[0]) # 上位k個の値 
# tensor([17.4937, 14.9048, 14.6679, 13.3002, 12.9762], grad_fn=<TopkBackward>)
print(topk[1]) # 上位k個のindex
#tensor([16022,   883, 10102,  6792, 10117])
pred_tokens = tknz.convert_ids_to_tokens(topk[1])
print(pred_tokens)
#['味噌', '果', '豚', '灰', '墨']

よし、無事に「味噌」が1位にきた。

んー、ところで、topk[0] で出てる値ってなんなんですかね。loss? デモ版で出ている確率値(?)とはどうも違う…?