はじめに
東大が公開しているUTH-BERTは診療録を用いて事前学習しており、医療テキストの様々なタスクを解くにはとても優秀な事前学習モデルです。
また基本となるBERTモデルで学習していることもあり、日本語医療テキストのベースラインとして重宝させて頂いています。
動機
元ソースのtensorflowに基づく方法はうまいことトークナイズしてくれる(あたりまえ)。
一方で、色々な事前学習モデルで検討したいので、pytorchのhuggingfaceのtransformersライブラリを利用したスクリプトに統一したい。
とはいえ、他の記事を参考にしてtransformersでモデルを読み込んで利用しようとすると、元ソースの様にトークナイズできない。
元ソース
Original text
2002 年夏より重い物の持ち上げが困難になり,階段の昇りが遅くなるなど四肢の筋力低下が緩徐に進行した.2005 年 2 月頃より鼻声となりろれつが回りにくくなった.また,食事中にむせるようになり,同年 12 月に当院に精査入院した。
After pre-processing
2002年夏より重い物の持ち上げが困難になり、階段の昇りが遅くなるなど四肢の筋力低下が緩徐に進行した.2005年2月頃より鼻声となりろれつが回りにくくなった.また、食事中にむせるようになり、同年12月に当院に精査入院した。
After tokenization
['2002年', '夏', 'より', '重い', '物', 'の', '持ち上げ', 'が', '困難', 'に', 'なり', '、', '階段', 'の', '[UNK]', 'が', '遅く', 'なる', 'など', '四肢', 'の', '筋力低下', 'が', '緩徐', 'に', '進行', 'し', 'た', '.', '2005年', '2', '月頃', 'より', '鼻', '##声', 'と', 'なり', 'ろ', '##れ', '##つ', 'が', '回り', '##にく', '##く', 'なっ', 'た', '.', 'また', '、', '食事', '中', 'に', 'むせる', 'よう', 'に', 'なり', '、', '同年', '12月', 'に', '当', '院', 'に', '精査', '入院', 'し', 'た', '。']
とりあえず動いてしまう単純なコード
model_checkpoint = "./UTH_BERT_BASE_512_MC_BPE_WWM_V25000"
tokenizer = BertJapaneseTokenizer.from_pretrained(model_checkpoint)
BertJapaneseTokenizerにvocab.txtとconfig.jsonが入ったフォルダのパスを指定のみ
2002年,##夏,##よ,##り,##重,##い,##物,##の,##持ち,##上げ,##が,##困難,##になり,、,階段,##の,##昇,##り,##が,##遅,##く,##なる,##な,##ど,##四肢,##の,##筋力低下,##が,##緩,##徐,##に,##進行,##した,.,2005年,##2,##月,##頃,##よ,##り,##鼻,##声,##と,##なり,##ろ,##れ,##つ,##が,##回,##り,##にく,##く,##な,##っ,##た,.,また,、,食事,##中,##に,##むせる,##よ,##う,##になり,、,同年,##12,##月,##に,##当,##院,##に,##精,##査,##入院,##した,。
前処理は公式どおりにしたもののサブワード多すぎだし、NEologdと万病辞書を使っていない。
他の記事を参考にして修正
model_checkpoint = "./UTH_BERT_BASE_512_MC_BPE_WWM_V25000"
tokenizer = BertJapaneseTokenizer.from_pretrained(
model_checkpoint,
word_tokenizer_type="mecab",
mecab_kwargs={
"mecab_dic": None,
"mecab_option": "-u /hogehoge/fugafuga/MANBYO_201907_Dic-utf8.dic -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd",
}
)
形態素解析をMeCabに指定し、デフォのipadicじゃなくてNEologdとユーザー辞書に万病辞書を指定。
辞書指定は絶対パスとし、NEologdはUbuntuに入れるとここに入ってた。
[UNK],夏,より,重い,物,の,持ち上げ,が,困難,に,なり,、,階段,の,[UNK],が,遅く,なる,など,四肢,の,筋力低下,が,緩徐,に,進行,し,た,[UNK],[UNK],[UNK],頃,より,鼻,##声,と,なり,ろ,##れ,##つ,が,回り,##にく,##く,なっ,た,[UNK],また,、,食事,中,に,むせる,よう,に,なり,、,同年,[UNK],に,当,院,に,精査,入院,し,た,。
あれま、数字関連が未知語に...
万病辞書を指定しないと筋力低下
が筋力,低下
に分割されてしまうが、NEologdは指定の有無で結果は変わらず。
修正
調べてみても先人達の記録が見つからないので、transformersのソースコードを読んで辿る事に...
- BertJapaneseTokenizerは、
word_tokenizer_type="mecab"
によってtransformers.MecabTokenizer
で形態素解析している -
transformers.MecabTokenizer
のtokenize
で何やらNFKC正規化とlowerで小文字化を制御している様子 -
__init__
はnormalize_text=True
となっている。NFKC正規化はしてもらわなくていいので原因はこいつか。
def tokenize(self, text, never_split=None, **kwargs):
"""Tokenizes a piece of text."""
if self.normalize_text:
text = unicodedata.normalize("NFKC", text)
never_split = self.never_split + (never_split if never_split is not None else [])
tokens = []
for word in self.mecab(text):
token = word.surface
if self.do_lower_case and token not in never_split:
token = token.lower()
tokens.append(token)
return tokens
BertJapaneseTokenizer
からMecabTokenizer
の引数normalize_text
にFalse
を渡したいので、mecab_kwargs
に追記したらよさそうなので、以下に修正。
ついでに、モデルの最大トークン数が事前情報にないのでmodel_max_length=512
で指定
model_checkpoint = "./UTH_BERT_BASE_512_MC_BPE_WWM_V25000"
tokenizer = BertJapaneseTokenizer.from_pretrained(
model_checkpoint,
word_tokenizer_type="mecab",
mecab_kwargs={
"mecab_dic": None,
"normalize_text": False,
"mecab_option": "-u /hogehoge/fugafuga/MANBYO_201907_Dic-utf8.dic -d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd",
},
model_max_length=512
)
公式の例どおりにトークナイズできました。
2002年,夏,より,重い,物,の,持ち上げ,が,困難,に,なり,、,階段,の,[UNK],が,遅く,なる,など,四肢,の,筋力低下,が,緩徐,に,進行,し,た,.,2005年,2,月頃,より,鼻,##声,と,なり,ろ,##れ,##つ,が,回り,##にく,##く,なっ,た,.,また,、,食事,中,に,むせる,よう,に,なり,、,同年,12月,に,当,院,に,精査,入院,し,た,。
厳密には、人名だったら@@N
に置換とか、本家どおりではないです。
BertJapaneseTokenizerじゃなくて統合化されたAutoTokenizerに対応させる
AutoTokenizerは様々なモデルに対して自動的に振り分けてくれて便利だが、UTH-BERTのパス指定だとBertJapaneseTokenizerに渡してくれないので想定通りに動かない。
問題はconfig.jsonの設定が不足していること。
config.jsonの修正
{
"attention_probs_dropout_prob": 0.1,
"hidden_act": "gelu",
"hidden_dropout_prob": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"max_position_embeddings": 512,
"num_attention_heads": 12,
"num_hidden_layers": 12,
"type_vocab_size": 2,
"vocab_size": 25000,
"model_type": "bert",
"tokenizer_class": "BertJapaneseTokenizer"
}
要は、"model_type": "bert"
と"tokenizer_class": "BertJapaneseTokenizer"
を追記するだけ。