SentencePieceでの日本語分かち書きをTransformersのパイプラインに組み込む

背景

PyTorchでHugging Face Transformersを使って自然言語処理を行うとき,文章をモデルに入力するためにはまず単語単位に分かち書き (tokenize) しなければなりません.

この前処理が思ったよりもやっかいなのです.

事前学習済みのモデルをTransformers公式から提供されているものから選んでしまえば,ここはあまり問題になりません.Transformers付属のtokenizerを使って一発で分かち書きできるからです.

実際,東北大からTransformersを通じて日本語BERT事前学習済みモデルが公開されて久しいので,日本語BERTモデルを使うのはだいぶ楽になりました.

huggingface.co

しかし,別の事前学習済みの日本語BERTモデルで,Transformersのプラットフォームに載っていないものはいくつか存在します.

これらのモデルを使う場合,分かち書きの際には Transformers 付属の tokenizer がそのまま使えないため,SentencePiece,MeCab,Juman++などを駆使してパイプラインをその都度書き直さなければなりませんでした.

しかし,Transformers のアップデートが進むにつれて分かち書き処理の整備もかなり進んできた印象があります.そこで,Yohei Kikuta さん提供の日本語BERTモデルの分かち書き (本来はSentencePieceを使う) を Transformers 仕様に書き直せないかどうかを検討してみました.

github.com

動作環境

  • Ubuntu 16.04.7 LTS
  • Python 3.8.10
  • transformers 4.6.1
  • tokenizers 0.10.3
  • sentencepiece 0.1.95

方法

すでにYohei Kikuta さん提供の日本語BERTモデルはダウンロード済みであるものとします.

①まず同梱されている SentencePiece モデルを,sentencepiece ライブラリではなく tokenizers ライブラリで読み込みます:

from tokenizers.implementations import SentencePieceUnigramTokenizer
from tokenizers.processors import BertProcessing
from transformers import PreTrainedTokenizerFast

spm_tokenizer = SentencePieceUnigramTokenizer.from_spm(
    'path/to/model/wiki-ja.model'
)

②読み込んだモデルをさらに transformers ライブラリの PreTrainedTokenizerFast に渡すと,これだけで transformers 仕様の分かち書き器に変換できます.ただ単に分かち書きしたいだけならこれだけで完成です.

transformers_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=spm_tokenizer,
    unk_token = '<unk>',
    bos_token = '<s>',
    eos_token = '</s>',
    cls_token = '[CLS]',
    sep_token = '[SEP]',
    pad_token = '[PAD]',
    mask_token = '[MASK]',
)

# BertTokenizers などと同じ使い方ができるようになる
print(
    transformers_tokenizer.batch_encode_plus(
        ['吾輩は猫である。', '名前はまだ無い。'], padding=True
    )
)
>>> {'input_ids': [[9, 5361, 31082, 11, 4324, 27, 8], [9, 1515, 6389, 8404, 8, 3, 3]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0]]}

この分かち書き器は,SentencePiece特有の以下の機能をすでに備えています:

transformers_tokenizer.decode([9, 5361, 31082, 11, 4324, 27, 8])
>>> '吾輩は猫である。'

③②の状態で,すでにLSTMやCNNに入力するには十分な機能が備わっています. しかし,BERTに使うとなるともう一息,'[CLS]', '[SEP]' などの特殊トークンを自動的に付加して欲しくなります.
そこで,先ほどの spm_tokenizer にBERT入力形式を返すような機能を追加しましょう:

spm_tokenizer.post_processor = BertProcessing(
    cls=("[CLS]", spm_tokenizer.token_to_id('[CLS]')),
    sep=("[SEP]", spm_tokenizer.token_to_id('[SEP]'))
)

④③をふたたび PreTrainedTokenizerFast に渡すと,今度は完全にBERT仕様の分かち書き器が完成します:

bert_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object = spm_tokenizer,
    unk_token = '<unk>',
    bos_token = '<s>',
    eos_token = '</s>',
    cls_token = '[CLS]',
    sep_token = '[SEP]',
    pad_token = '[PAD]',
    mask_token = '[MASK]',
)

print(
    bert_tokenizer.batch_encode_plus(
        ['吾輩は猫である。', '名前はまだ無い。'], padding=True
    )
)
>>> {'input_ids': [[4, 9, 5361, 31082, 11, 4324, 27, 8, 5], [4, 9, 1515, 6389, 8404, 8, 5, 3, 3]], 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 0, 0]]}

最後に

SentencePiece を用いた分かち書きは transformers 仕様に変換できることが分かりました.
Mecab,Juman++,sudachi などの他の分かち書き器であっても基本的に同様の変換はできそうです.機会があれば試してみます.