PyTorchで京大BERT日本語Pretrainedモデルを使ってみよう
自然言語処理で注目を集めるBERT
Googleによって提案されたBERTは、自然言語処理のあらゆる分野へ流用が可能で、ますます注目を集めています。自然言語処理を学んでる方でしたら、一度は触ってみたいですよね!
今日は京大から公開されている、 PyTorch & BERT日本語Pretrainedモデル を使って、単語特徴ベクトルを取り出す方法を紹介します。
PyTorchでBERTを扱うには?
BERTは元々はGoogleによって提案されたもの。すなわち、元々のモデルはTensorFlowで実装されています。
そしたらPyTorchでBERTは使えない!?? などということはなく、 Hugging FaceのTransformersパッケージを使用することで、PyTorchでもBERTのモデルが扱えるようになっています。
今回は、Hugging FaceのTransformersを使用して、京大のBERT日本語Pretrainedモデルを呼び出して使ってみます。
特徴ベクトルの取得方法
それでは、BERTを使用して、特徴ベクトルを取得してみましょう。
入力となるテキストは『大きなのっぽの古時計を購入した。』とします。
1. まずはJUMANを使用して単語を分割します
まずは入力テキストをJUMANを使用して、単語単位に分割します。
from pyknp import Juman text = "大きなのっぽの古時計を購入した。" juman = Juman() result = juman.analysis(text) tokens = [mrph.midasi for mrph in result.mrph_list()] print("JUMAN tokens: ", tokens)
出力:
JUMAN tokens: ['大きな', 'のっぽの', '古', '時計', 'を', '購入', 'した', '。']
ここまではあくまで形態素解析器 JUMAN を使用して、単語を分割したのみです。まだBERTは出てきていませんね!
最近は深層学習を使用した JUMAN++ の精度がかなり上がっている印象です。JUMAN / JUMAN++ はKNPと併用することで、係り受け解析等、意義の解析が得意な形態素解析機となっています。
形態素解析器といえばMecabしか触ったことがないという方は、一度JUMAN++も触ってみると面白いかもしれませんね。
2. BERT用にトークナイズ
続いてBERT用に単語分割を行います。
何がBERT用なのか、まずはその出力結果を確認してみましょう。
from transformers import BertTokenizer, BertModel bert_model_path = "./Japanese_L-12_H-768_A-12_E-30_BPE_WWM" # for Tokenizer vocab_file_path = bert_model_path + "/vocab.txt" bert_tokenizer = BertTokenizer(vocab_file_path, do_lower_case=False, do_basic_tokenize=False) bert_tokens = bert_tokenizer.tokenize(" ".join(["[CLS]"] + tokens + ["[SEP]"])) print("BERT tokens: ", bert_tokens) token_ids = bert_tokenizer.convert_tokens_to_ids(bert_tokens) print("BERT token IDs: ", token_ids)
出力:
BERT tokens: ['[CLS]', '大きな', 'の', '##っぽ', '##の', '古', '時計', 'を', '購入', 'した', '。', '[SEP]'] BERT token IDs: [2, 522, 5, 28052, 422, 1179, 5424, 10, 1468, 20, 7, 3]
BERT用にトークナイズを行う際、文頭に '[CLS]' 、文末に '[SEP]' を挿入しています。こうすることでBERTの精度が上がるとされています。
もう一つ見過ごせないのが、下記の点ではないでしょうか。
- 「のっぽ」→「の」+「##っぽ」+「##の」
これは、単語をsubword化しています。学習時に単語の共通部分を見つけて、それをうまいこと学習していこうというのがこの技術です。英語では going を go + ing に分けるような、そんな具合です。
最後にBERT用にトークナイズしたものを、IDへ変換しています。元のJUMANの単語分割数が8個であったのに対し、BERT用にsubword化すると、10 + 2 (CLS + SEP) 個になるということですね。
このID(token_ids)がBERTのモデルの入力となります。
3. BERTのモデルで特徴ベクトルを抽出
それでは取得したIDを使用して、特徴ベクトルを抽出してみます。
import torch # BERT pre_trained model load bert_model = BertModel.from_pretrained(bert_model_path) tokens_tensor = torch.tensor(token_ids).unsqueeze(0) outputs, _ = bert_model(tokens_tensor) print(outputs[0], "\n (size: ", outputs[0].size(), ")")
出力:
tensor([[ 0.6360, 0.0840, 0.3619, ..., -0.8531, 0.1429, 0.3017], [-0.1458, 0.1711, 1.3058, ..., -0.7310, 0.0707, -0.2259], [-0.5004, 0.5406, 0.3450, ..., -0.0714, -0.7272, -0.5982], ..., [-0.2729, -0.3282, -0.0918, ..., -0.6031, 0.3227, -0.4769], [ 0.8811, 0.3096, 0.4068, ..., -0.2815, 0.6047, 0.0074], [ 0.9640, 0.3325, 0.4466, ..., -0.2990, 0.6311, 0.0432]], grad_fn=<SelectBackward>) (size: torch.Size([12, 768]) )
出力結果のベクトルサイズを見ると、12×768 のベクトルを取得できていることがわかります。
つまり先程BERT用に分割した12個 × 768のテンソルが特徴ベクトルとして抽出されました。
京大のBERT日本語Pretrainedモデルは、ひとつの単語(をsubword化したもの)に対して、768個のfloat型ベクトルを取得することができます。
ところでこれをどうするのか?
BERT日本語Pretrainedモデル……ということは、つまりこれは深層学習の前処理(正確には転移学習)の特徴量として使用することができます。
この後続として、別のモデルと組み合わせて使用することで、自然言語処理のあらゆる分野で活用することができるんですね!
実際使用してみようとすると・・・
正直言うと、ここまでの処理、めっちゃ遅いです! 使い方を間違えると、いつになっても学習が進まないとかいろいろ弊害が出てきます。
ちなみにどの部分が遅いのかと言うと、BERT日本語Pretrainedモデルを使用して特徴量を抽出する箇所ではなく、BERT用のトークナイズ処理部でかなりの時間を要していました。軽くソースコードを眺めてみたのですが、Pythonで長いfor文をぐるぐる回っているらしく・・・そりゃ遅いですね。。。
解決策としては、SentencePieceを使用してみるとか? そうすると京大のBERT日本語Pretrainedモデルが使用できなくなってしまい、いろいろ悩ましいですね。
あとは、、、これ、768個も本当にいる!???
(アノテーション等をするとかでなければもう少し少なくてもいい気がする……)
今回のソースコード(全文)
最後に、今回書いたソースコードをそのまま貼り付けておきます。
import torch from pyknp import Juman from transformers import BertTokenizer, BertModel text = "大きなのっぽの古時計を購入した。" bert_model_path = "./Japanese_L-12_H-768_A-12_E-30_BPE_WWM" # BERT pre_trained model load bert_model = BertModel.from_pretrained(bert_model_path) # for Tokenizer vocab_file_path = bert_model_path + "/vocab.txt" bert_tokenizer = BertTokenizer(vocab_file_path, do_lower_case=False, do_basic_tokenize=False) """ トークナイザー """ print("\n *** to Tokens ***") juman = Juman() result = juman.analysis(text) tokens = [mrph.midasi for mrph in result.mrph_list()] print("JUMAN tokens: ", tokens) bert_tokens = bert_tokenizer.tokenize(" ".join(["[CLS]"] + tokens + ["[SEP]"])) print("BERT tokens: ", bert_tokens) token_ids = bert_tokenizer.convert_tokens_to_ids(bert_tokens) print("BERT token IDs: ", token_ids) """ ベクトル取得 """ print("\n *** to Vector ***") tokens_tensor = torch.tensor(token_ids).unsqueeze(0) outputs, _ = bert_model(tokens_tensor) print(outputs[0], "\n (size: ", outputs[0].size(), ")")