# 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)
)
```