# huggingface (BERT編) ## 過去回 [第1回:NLP Outline](https://hackmd.io/uD2hEISAShqVHD6IRtceUw) [第2回:Word2Vec](https://hackmd.io/T2QJsuwsROSQluklguDOow) [第3回:Doc2Vec](https://hackmd.io/LIT2hp_jQ56Dvn-imOVemg) [第4回:Seq2Seq](https://hackmd.io/UGLHXO6-RweY_Bys2Dp4mA) [第5回:Seq2Seq Attention](https://hackmd.io/D8xqAyHjRte3A0oENtLXeQ) [第6回:Transformer](https://hackmd.io/DcxZgK3uShiuh2kt76xeUA) [第7回:BERT](https://hackmd.io/o4Kl2oHRTnWJeqThsJ1-nQ) ## 初めに BERTをpython で使う際のメモ. 自分以外がわかるようにまとめるつもりではない。 ## Language Models in python 言語モデルが統合管理されている package は huggingface である。 https://github.com/huggingface/transformers huggingfaceで扱えるモデルたち↓ https://huggingface.co/models ## Install GitHub から git clone して pip install ``` git clone https://github.com/huggingface/transformers.git cd transformers pip install -e . ``` ## Usage (Simple Prediction) 日本語 Bert を例とする ### Download Pre-trained Weight and Tokenizer ```python= >>> import transformers as trf >>> >>> num_labels = 2 >>> model = trf.BertForSequenceClassification.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', num_labels=num_labels) >>> tokenizer = trf.BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking') ``` ### Tokenize and Convert to ids ```python= >>> tokenizer.tokenize("今日は天気です") ['今日', 'は', '天気', 'です'] >>> tokenizer.convert_tokens_to_ids(['今日', 'は', '天気', 'です']) [3246, 9, 11385, 2992] >>> tokenizer.vocab OrderedDict([ ('[PAD]', 0), ('[UNK]', 1), ('[CLS]', 2), ('[SEP]', 3), ('[MASK]', 4), ('の', 5), ('、', 6), ('に', 7), ('。', 8), ('は', 9), ('た', 10), ('を', 11), ('で', 12), ('と', 13), ('が', 14), ('し', 15), ('て', 16), ('1', 17), ('な', 18), ('年', 19), ('れ', 20), ('い', 21), ('あ', 22), ('(', 23), (')', 24), ('2', 25), ('さ', 26), ('こ', 27), ('も', 28), ('か', 29), ('##する', 30), ('ある', 31), ('日', 32), ('いる', 33), ('する', 34), ('・', 35), ('「', 36), ('月', 37), ('」', 38), ('19', 39), ('から', 40), ('20', 41), ('大', 42), ('ア', 43), ('そ', 44), ('こと', 45), ('##して', 46), ('ま', 47), ('3', 48), ('や', 49), ('として', 50), ('中', 51), ('一', 52), ('人', 53), ('よ', 54), ('ス', 55), ('によ', 56), ('4', 57), ('なっ', 58), ('その', 59), ('ら', 60), ('-', 61), ('れる', 62), ('『', 63), ('など', 64), ('』', 65), ('フ', 66), ('シ', 67), ('##リー', 68), ('同', 69), ('この', 70), ('出', 71), ('時', 72), ('お', 73), ('地', 74), ('だ', 75), ('5', 76), ('行', 77), ('201', 78), ('国', 79), ('ない', 80), ('的', 81), ('ため', 82), ('後', 83), ('られ', 84), ('発', 85), ('200', 86), ('##ール', 87), ('イ', 88), ('##ラン', 89), ... ``` tokenizer.vocab に登録されている単語のid に置き換える ### Simple Prediction ```python= >>> model(input_ids=torch.Tensor([[3246, 9, 11385, 2992]]).to(torch.long)) (tensor([[ 0.2957, -0.0324, -0.0040, -0.3999, -0.0595, 0.1411, 0.0603, 0.1955, -0.0751, -0.0064]], grad_fn=<AddmmBackward>),) ``` model の出力は tuple(output, loss) という形式で、何故か loss も同時に計算してくれる ※BertForSequenceClassification の class の場合 ## Usage (with torchtext) pytorch だと torchtext を使ってモデルを組むのが一般的な感じ torchtext は、先頭の[CLS]や、[PAD]埋め処理などを自動的にやってくれるライブラリ. ※個人的には使いづらい... ### Create Instance ```python= >>> import torchtext >>> TEXT = torchtext.data.Field( tokenize=tokenizer.tokenize, sequential=True, use_vocab=True, lower=False, include_lengths=True, batch_first=True, fix_length=128, init_token='[CLS]', eos_token='[SEP]', pad_token='[PAD]', unk_token='[UNK]' ) ``` ### Build Vocab ```python= >>> TEXT.vocab # instance生成時はエラー Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Field' object has no attribute 'vocab' >>> TEXT.build_vocab([]) >>> TEXT.vocab <torchtext.vocab.Vocab object at 0x7f38d4893358> >>> TEXT.vocab.stoi # string to id の意味 defaultdict(None, {'[UNK]': 0, '[PAD]': 1, '[CLS]': 2, '[SEP]': 3}) >>> TEXT.vocab.stoi = tokenizer.vocab # torknizer の vocab で入れ替え >>> TEXT.vocab.stoi OrderedDict([('[PAD]', 0), ('[UNK]', 1), ('[CLS]', 2), ('[SEP]', 3), ('[MASK]', 4), ('の', 5), ('、', 6), ('に', 7), ('。', 8), ('は', 9), ('た', 10), ('を', 11), ('で', 12), ('と', 13), ('が', 14), ('し', 15), ('て', 16), ('1', 17), ('な', 18), ('年', 19), ('れ', 20), ('い', 21), ('あ', 22), ('(', 23), (')', 24), ('2', 25), ('さ', 26), ('こ', 27), ('も', 28), ('か', 29), ('##する', 30), ('ある', 31), ('日', 32), ('いる', 33), ('する', 34), ('・', 35), ('「', 36), ('月', 37), ('」', 38), ('19', 39), ('から', 40), ('20', 41), ('大', 42), ('ア', 43), ('そ', 44), ('こと', 45), ('##して', 46), ('ま', 47), ... ``` ### To Token Ids ```python= >>> TEXT.process([TEXT.tokenize("今日は晴れです")]) (tensor([[ 2, 3246, 9, 16577, 2992, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), tensor([6])) >>> TEXT.vocab.stoi["今日"] 3246 >>> TEXT.vocab.stoi["は"] 9 >>> TEXT.vocab.stoi["晴れ"] 16577 >>> TEXT.vocab.stoi["です"] 2992 >>> TEXT.vocab.stoi["[CLS]"] 2 >>> TEXT.vocab.stoi["[SEP]"] 3 >>> TEXT.vocab.stoi["[PAD]"] 0 ``` torchtext を使えば、[PAD]埋めもしてくれて、Tensorまで変換してくれる ## Usage (Fine-tuning) DataFrameベースのFine-tuning用のクラス(NLPNN)を作成してみた。 https://github.com/kazukingh01/kkpackages/blob/v1.0.1/kkpackage/kkpackage/lib/kknnnlp.py#L63 ### Install 以下と、適当に足りない package を追加 ``` pip install "git+https://github.com/kazukingh01/kkpackages.git@v1.0.1#egg=kkpackage&subdirectory=kkpackage/" ``` ### Training 訓練スクリプト ```python= import torch import pandas as pd import numpy as np # locla packages from kkpackage.lib.kknnnlp import NLPNN class MyNLPNN(NLPNN): @classmethod def process_label(cls, _input): return _input.to(torch.long) @classmethod def process_data_train_aft(cls, _input): return [_input[0], ] @classmethod def process_data_valid_aft(cls, _input): return [_input[0], ] if __name__ == "__main__": df = pd.DataFrame( [ ["今日は晴れです。", 1], ["今日は曇りです。", 0], ["今日は雨です。", 0], ["明日は晴れです。", 1], ["明日は晴天です。", 1], ["昨日は曇りでした。", 0], ["今日は曇り後晴れです。",1], ["明日は曇り後晴れです。",1], ["今日は曇り後雨です。", 0], ], columns=["data", "label"] ) model = MyNLPNN( 2, mtype="cls", fine_tuning_type="full", loss_funcs=[torch.nn.CrossEntropyLoss()], optimizer=torch.optim.SGD, optim_params={"lr":0.001, "weight_decay":0}, # train parameter epoch=100, batch_size=1, batch_size_valid=1, valid_step=1 ) model.to_cuda() model.train(df) ``` ### Predicte 推論 ```python= _, dataloader = model.create_df_dataloader(df, 1, 1) prob, ans = model.predict_proba(dataloader=dataloader, is_label=True, is_softmax=True) ``` ## Usage (Add Token) Token を追加してからの fine-tuning. 下記を参照. https://github.com/huggingface/transformers/issues/1413 Embedding Layer を追加できる。追加した初期WeightはRandom ```python= >>> model = trf.BertForSequenceClassification.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking', num_labels=2) >>> tokenizer = trf.BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking') >>> len(tokenizer) 32000 >>> tokenizer.add_tokens(["晴天", "曇り後晴れ"]) 2 >>> tokenizer.added_tokens_encoder {'晴天': 32000, '曇り後晴れ': 32001} >>> len(tokenizer) 32002 >>> model.bert.embeddings BertEmbeddings( (word_embeddings): Embedding(32000, 768, padding_idx=0) (position_embeddings): Embedding(512, 768) (token_type_embeddings): Embedding(2, 768) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) >>> model.resize_token_embeddings(len(tokenizer)) Embedding(32002, 768) >>> model.bert.embeddings BertEmbeddings( (word_embeddings): Embedding(32002, 768) (position_embeddings): Embedding(512, 768) (token_type_embeddings): Embedding(2, 768) (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True) (dropout): Dropout(p=0.1, inplace=False) ) ```