|
|
@@ -0,0 +1,575 @@ |
|
|
# Huggingface |
|
|
- NLP 관련 다양한 패키지를 제공하고 있으며, 특히 언어 모델 (language models) 을 학습하기 위하여 세 가지 패키지가 유용 |
|
|
|
|
|
| package | note | |
|
|
| --- | --- | |
|
|
| transformers | Transformer 기반 (masked) language models 알고리즘, 기학습된 모델을 제공 | |
|
|
| tokenizers | transformers 에서 사용할 수 있는 토크나이저들을 학습/사용할 수 있는 기능 제공. transformers 와 분리된 패키지로 제공 | |
|
|
| nlp | 데이터셋 및 평가 척도 (evaluation metrics) 을 제공 | |
|
|
|
|
|
|
|
|
|
|
|
# huggingface tokenizers |
|
|
|
|
|
## dictionary-based vs subword tokenizers |
|
|
|
|
|
| 토크나이저 | 토큰 단위 | 미등록단어 가정 | vocab size | |
|
|
| --- | --- | --- | --- | |
|
|
| 사전 기반 | 알려진 단어/형태소 | 사전에 등록된 단어/형태소의 결합이라 가정, 필요시 형태소 분석 (형태 변형 가능) | unlimited | |
|
|
| subword | 알려진 subwords (substring) | subwords 의 조합이라 가정, str split 만을 이용 | limited | |
|
|
|
|
|
(코로나 뉴스 70,963 문장 + BertTokenizer) |
|
|
```python |
|
|
sent = '신종 코로나바이러스 감염증(코로나19) 사태가 심각합니다' |
|
|
bert_tokenizer.tokenize(sent) |
|
|
# '신종 코로나바이러스 감염증 ( 코로나19 ) 사태 ##가 심 ##각 ##합니다' |
|
|
|
|
|
komoran.morphs(sent) |
|
|
# 신종 코로나바이러스 감염증 ( 코로나 19 ) 사태 가 심각 하 ㅂ니다 |
|
|
``` |
|
|
|
|
|
| input eojeol | Mecab | Komoran | Bert + Covid news | |
|
|
| --- | --- | --- | --- | |
|
|
| 코로나가 | 코 로 나가 | 코로나 가 | 코로나 ##가 | |
|
|
| 코로나는 | 코로나 는 | 코로나 는 | 코로나 ##는 | |
|
|
| 코로나를 | 코로나 를 | 코로나 를 | 코로나 ##를 | |
|
|
| 코로나의 | 코로 나의 | 코로나 의 | 코로나 ##의 | |
|
|
|
|
|
- 사전 기반 토크나이저는 미등록단어를 맥락에 따라 잘못 분해할 가능성이 존재 |
|
|
- subword 토크나이저는 학습데이터에 자주 등장하는 substring 이라면 단어로 보존될 가능성이 높음 |
|
|
|
|
|
| input eojeol | Mecab | Komoran | Bert + Covid news | |
|
|
| --- | --- | --- | --- | |
|
|
| 심각합니다 | 심각 합니다 | 심각 하 ᄇ니다 | 심 ##각 ##합니다 | |
|
|
| 심각했다 | 심각 했 다 | 심각 하 었 다 | 심 ##각 ##했다 | |
|
|
| 심각하다고 | 심각 하 다고 | 심각 하 다고 | 심 ##각 ##하다 ##고 | |
|
|
| 심각하며 | 심각 하 며 | 심각 하 며 | 심 ##각 ##하며 | |
|
|
|
|
|
- 반대로 학습데이터에 자주 등장하지 않은 단어 `심각` 은 잘 인식되지 않음 |
|
|
- 반드시 단어로 보존하고 싶은 단어가 있다면 따로 등록이 필요 |
|
|
- 활용된 용언 `하다`가 다양한 형태로 표현 |
|
|
- `합니다`, `했다`, `하다 + 고`, `하며` |
|
|
- 반드시 원형으로 처리해야 하는 것은 아니나, 이를 원할 경우 선택할 수 없음 |
|
|
|
|
|
|
|
|
|
|
|
## tokenizers 구성 |
|
|
|
|
|
- `0.9.0.dev0` 버그가 있어서 `0.8.1` 기준으로 진 |
|
|
- BaseTokenizer 를 상속하는 네 가지 토크나이저를 제공 |
|
|
- Rust 로 구현된 코드를 Python 에서 이용할 수 있도록 도와주는 기능을 제공 |
|
|
- Python 에서 기능을 추가하기 어려움 |
|
|
- base 방식에 따라 WordpieceTrainer, BpeTrainer 를 이용 |
|
|
|
|
|
| tokenizer (class name) | unit | base | normalizer | boundary symbol | output | |
|
|
| --- | --- | --- | --- | |
|
|
| Byte-level BPE (ByteLevelBPETokenizer) | byte | BPE | [Unicode, Lowercase] | 어절 앞 `Ġ` 부착 | `vocab.json`, `merges.txt` | |
|
|
| Character-level BPE (CharBPETokenizer)^1 | char | BPE | [Unicode, BertNormalizer, Lowercase] | 어절 뒤 `</w>` 부착 | `vocab.json`, `merges.txt` | |
|
|
| Sentencepiece BPE (SentencePieceBPETokenizer) | char | BPE | | 어절 앞 `▁` 부착 | `vocab.json`, `merges.txt` | |
|
|
| Bert wordpiece (BertWordPieceTokenizer) | char | WordPiece | BertNormalizer | 어절 중간 subword 앞에 `##` 부착 | `vocab.txt` | |
|
|
|
|
|
- ^1: split_on_whitespace_only 기능 제공. 이를 이용하면 pre-tokenized 된 텍스트를 이용하여 학습하기에 용이 |
|
|
- ^2: add_prefix_space 기능 제공 |
|
|
|
|
|
|
|
|
|
|
|
## tokenizers.BaseTokenizer |
|
|
|
|
|
- 네 가지 토크나이저가 모두 상속, 대표적인 기능 |
|
|
- get_vocab() |
|
|
- add_tokens() |
|
|
- add_special_tokens() |
|
|
- normalize() |
|
|
- encode() / encode_batch() |
|
|
- decode() / decode_batch() |
|
|
- save() vs save_model() |
|
|
- tokenize() 기능이 없고, encode() 의 결과를 이용해야 함 |
|
|
- 네 가지 토크나이저들 모두 위의 기능을 상속하니, 각 토크나이저의 특징을 살펴보며 위의 기능들을 알아보자 |
|
|
|
|
|
|
|
|
## tokenizers.BertWordPieceTokenizer |
|
|
|
|
|
(`very_small_corpus.txt`) |
|
|
``` |
|
|
ABCDE ABC AC ABD |
|
|
DE AB ABC AF |
|
|
``` |
|
|
|
|
|
(training) |
|
|
```python |
|
|
bert_wordpiece_tokenizer = BertWordPieceTokenizer() |
|
|
bert_wordpiece_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 10, |
|
|
min_frequency = 1, |
|
|
limit_alphabet = 1000, |
|
|
initial_alphabet = [], |
|
|
special_tokens = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"], |
|
|
show_progress = True, |
|
|
wordpieces_prefix = "##", |
|
|
) |
|
|
``` |
|
|
|
|
|
(vocab check) |
|
|
```python |
|
|
vocab = bert_wordpiece_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
``` |
|
|
|
|
|
``` |
|
|
['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', 'a', 'b', 'c', 'd', 'e', |
|
|
'f', '##b', '##c', '##d', '##e', '##f'] |
|
|
``` |
|
|
|
|
|
(tokenization) |
|
|
```python |
|
|
encoding = bert_wordpiece_tokenizer.encode('ABCDE') |
|
|
print(encoding.tokens) # ['a', '##b', '##c', '##d', '##e'] |
|
|
print(encoding.ids) # [5, 13, 12, 14, 11] |
|
|
``` |
|
|
|
|
|
(vocab size 를 늘리고 initial alphabet 을 추가하면) |
|
|
```python |
|
|
bert_wordpiece_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 20, |
|
|
min_frequency = 1, |
|
|
initial_alphabet = ['g'], |
|
|
) |
|
|
vocab = bert_wordpiece_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
# ['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', 'a', 'b', 'c', 'd', 'e', |
|
|
# 'f', 'g', '##b', '##c', '##e', '##d', '##f', 'ab', 'abc', 'ac'] |
|
|
``` |
|
|
|
|
|
(encode_batch 함수) |
|
|
```python |
|
|
encodings = bert_wordpiece_tokenizer.encode_batch(['ABCDE', 'abcd']) |
|
|
encodings[0].tokens # ['abc', '##d', '##e'] |
|
|
``` |
|
|
|
|
|
(normalize 함수) |
|
|
```python |
|
|
# 0.9.0dev 에서 사라 |
|
|
bert_wordpiece_tokenizer.normalize('ABCDE') # 'abcde' |
|
|
``` |
|
|
|
|
|
(save / load) |
|
|
```python |
|
|
bert_wordpiece_tokenizer.save_model( |
|
|
directory = './', |
|
|
name = 'very_small_bertwordpiece' |
|
|
) |
|
|
|
|
|
# './very_small_bertwordpiece-vocab.txt' 에 vocab 생성 |
|
|
|
|
|
bert_wordpiece_tokenizer = BertWordPieceTokenizer( |
|
|
vocab_file = './very_small_bertwordpiece-vocab.txt' |
|
|
) |
|
|
bert_wordpiece_tokenizer.encode('ABCDE').tokens |
|
|
# ['[CLS]', 'abc', '##d', '##e', '[SEP]'] |
|
|
``` |
|
|
|
|
|
(without special tokens) |
|
|
```python |
|
|
bert_wordpiece_tokenizer.encode( |
|
|
'ABCDE', |
|
|
add_special_tokens=False |
|
|
).tokens |
|
|
# ['abc', '##d', '##e'] |
|
|
``` |
|
|
|
|
|
(two sentences pair) |
|
|
```python |
|
|
bert_wordpiece_tokenizer.encode( |
|
|
sequence = 'abcde', |
|
|
pair = 'abcd' |
|
|
).tokens |
|
|
# ['[CLS]', 'abc', '##d', '##e', '[SEP]', 'abc', '##d', '[SEP]'] |
|
|
``` |
|
|
|
|
|
(add_tokens 함수) |
|
|
```python |
|
|
bert_wordpiece_tokenizer.add_tokens(['lovit']) |
|
|
vocab = bert_wordpiece_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
# ['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', 'a', 'b', 'c', 'd', 'e', |
|
|
# 'f', 'g', '##b', '##c', '##e', '##d', '##f', 'ab', 'abc', 'ac', |
|
|
# 'lovit'] |
|
|
|
|
|
# 그러나 추가한 단어가 함께 저장되지는 않음. `vocab.txt` 에 직접 추가해도 |
|
|
bert_wordpiece_tokenizer.save('./', './very_small_bertwordpiece') |
|
|
``` |
|
|
|
|
|
## tokenizers.SentencePieceBPETokenizer |
|
|
|
|
|
(train) |
|
|
```python |
|
|
sentencepiece_tokenizer = SentencePieceBPETokenizer( |
|
|
add_prefix_space = True, |
|
|
replacement = '▁' |
|
|
) |
|
|
sentencepiece_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 20, |
|
|
min_frequency = 1, |
|
|
special_tokens = ['<unk>'], |
|
|
) |
|
|
vocab = sentencepiece_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
# ['<unk>', 'A', 'B', 'C', 'D', 'E', 'F', '▁', '▁A', '▁AB', |
|
|
# '▁ABC', 'DE', '▁DE', '▁AC', '▁AF', '▁ABD', '▁ABCDE'] |
|
|
``` |
|
|
- `0.8.1` -> `0.9.0dev` 에서 '\n' 이 글자로 들어옴 |
|
|
|
|
|
(without add_prefix_space) |
|
|
```python |
|
|
sentencepiece_tokenizer = SentencePieceBPETokenizer( |
|
|
add_prefix_space = False |
|
|
) |
|
|
sentencepiece_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 20, |
|
|
min_frequency = 1, |
|
|
special_tokens = ['<unk>'], |
|
|
) |
|
|
vocab = sentencepiece_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
# ['<unk>', 'A', 'B', 'C', 'D', 'E', 'F', '▁', '▁A', '▁AB', |
|
|
# 'DE', '▁ABC', 'AB', 'CDE', '▁AC', '▁AF', '▁ABD', 'ABCDE'] |
|
|
``` |
|
|
- `add_prefix_space=True` 는 문장의 첫글자가 공백이 아닐 경우 공백 추 |
|
|
- 위에서는 `▁ABCDE` 가, 아래에서는 `ABCDE` 가 unit 으로 학습됨. |
|
|
|
|
|
(save / load) |
|
|
```python |
|
|
sentencepiece_tokenizer.save_model('./', 'very_small_sentencepiece') |
|
|
# ['./very_small_sentencepiece-vocab.json', |
|
|
# './very_small_sentencepiece-merges.txt'] |
|
|
|
|
|
sentencepiece_tokenizer = SentencePieceBPETokenizer( |
|
|
vocab_file = './very_small_sentencepiece-vocab.json', |
|
|
merges_file = './very_small_sentencepiece-merges.txt' |
|
|
) |
|
|
sentencepiece_tokenizer.encode('ABCDE').tokens |
|
|
# ['▁ABC', 'DE'] |
|
|
``` |
|
|
- BPE 계열은 `merges.txt`, `vocab.json` 두 개의 파일 |
|
|
- `0.9.0dev` 에 `merges.txt` 파일 저장 과정 중 버그가 있음 |
|
|
|
|
|
## tokenizers.CharBPETokenizer |
|
|
|
|
|
(use BertPreTokenizer) |
|
|
```python |
|
|
charbpe_tokenizer = CharBPETokenizer(suffix='</w>') |
|
|
charbpe_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 15, |
|
|
min_frequency = 1 |
|
|
) |
|
|
charbpe_tokenizer.encode('ABCDE.ABC').tokens |
|
|
# ['AB', 'C', 'DE</w>', 'ABC</w>'] |
|
|
``` |
|
|
- BertPreTokenizer 는 space, punctuation 에서 분리 |
|
|
|
|
|
(use WhitespacePreTokenizer) |
|
|
```python |
|
|
charbpe_tokenizer = CharBPETokenizer( |
|
|
suffix='</w>', |
|
|
split_on_whitespace_only = True |
|
|
) |
|
|
charbpe_tokenizer.encode('ABCDE.ABC').tokens |
|
|
# ['AB', 'C', 'D', 'E', 'ABC</w>'] |
|
|
``` |
|
|
- 공백 기준으로만 어절을 분리 |
|
|
|
|
|
(미등록단어 제거하여 return) |
|
|
```python |
|
|
charbpe_tokenizer.encode('ABCDEFGH').tokens |
|
|
# ['AB', 'C', 'D', 'E', 'F'] |
|
|
``` |
|
|
|
|
|
## tokenizers.ByteLevelBPETokenizer |
|
|
|
|
|
```python |
|
|
# OpenAI GPT2 tokenizer |
|
|
bytebpe_tokenizer = ByteLevelBPETokenizer( |
|
|
add_prefix_space = False, |
|
|
lowercase = False, |
|
|
) |
|
|
bytebpe_tokenizer.train( |
|
|
files = [small_corpus], |
|
|
vocab_size = 1000, |
|
|
min_frequency = 1 |
|
|
) |
|
|
vocab = bytebpe_tokenizer.get_vocab() |
|
|
sorted(vocab, key=lambda x: vocab[x]) |
|
|
``` |
|
|
|
|
|
``` |
|
|
['!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', |
|
|
'5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
|
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', |
|
|
']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', |
|
|
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '¡', '¢', '£', '¤', '¥', '¦', |
|
|
'§', '¨', '©', 'ª', '«', '¬', '®', '¯', '°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', |
|
|
'¼', '½', '¾', '¿', 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', |
|
|
'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', 'à', 'á', 'â', 'ã', |
|
|
'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', |
|
|
'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć', 'ć', 'Ĉ', 'ĉ', 'Ċ', 'ċ', |
|
|
'Č', 'č', 'Ď', 'ď', 'Đ', 'đ', 'Ē', 'ē', 'Ĕ', 'ĕ', 'Ė', 'ė', 'Ę', 'ę', 'Ě', 'ě', 'Ĝ', 'ĝ', 'Ğ', 'ğ', |
|
|
'Ġ', 'ġ', 'Ģ', 'ģ', 'Ĥ', 'ĥ', 'Ħ', 'ħ', 'Ĩ', 'ĩ', 'Ī', 'ī', 'Ĭ', 'ĭ', 'Į', 'į', 'İ', 'ı', 'IJ', 'ij', |
|
|
'Ĵ', 'ĵ', 'Ķ', 'ķ', 'ĸ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'Ń', |
|
|
'ĠA', 'ĠAB', 'DE', 'ĠABC', 'AB', 'CDE', 'ĠAC', 'ĠAF', 'ĠABD', 'ABCDE'] |
|
|
``` |
|
|
- 33 - 323 까지 글자를 기본 알파벳으로 가지고 시작 |
|
|
|
|
|
```python |
|
|
bytebpe_tokenizer.encode('ABCDE ABC').tokens |
|
|
# ['ABCDE', 'ĠABC'] |
|
|
|
|
|
bytebpe_tokenizer.encode(' ABCDE ABC').tokens |
|
|
# ['ĠABC', 'DE', 'ĠABC'] |
|
|
``` |
|
|
|
|
|
|
|
|
## tokenizers 에서 학습한 토크나이저를 transformers 에서 이용하기 |
|
|
|
|
|
BertTokenizer 를 확인해보자. |
|
|
|
|
|
```python |
|
|
from transformers import BertTokenizer, GPT2Tokenizer |
|
|
|
|
|
transformers_bert_tokenizer = BertTokenizer( |
|
|
vocab_file = './tokenizers/BertWordPieceTokenizer/covid-vocab.txt' |
|
|
) |
|
|
sent_ko = '신종 코로나바이러스 감염증(코로나19) 사태가 심각합니다' |
|
|
print(f'tokenizers : {bert_wordpiece_tokenizer.encode(sent_ko).tokens}') |
|
|
print(f'transformers: {transformers_bert_tokenizer.tokenize(sent_ko)}') |
|
|
``` |
|
|
|
|
|
``` |
|
|
tokenizers : ['신종', '코로나바이러스', '감염증', '(', '코로나19', ')', '사태', '##가', '심', '##각', '##합니다'] |
|
|
transformers: ['신종', '코로나바이러스', '감염증', '(', '코로나19', ')', '사태', '##가', '심', '##각', '##합니다'] |
|
|
``` |
|
|
|
|
|
- 토크나이징의 결과가 동일하지만, 초/중/종성이 분해되어 있다. |
|
|
- 이는 unicode normalization 때문에 발생한 현상. |
|
|
|
|
|
```python |
|
|
from unicodedata import normalize |
|
|
|
|
|
print(normalize('NFKD', '가감')) # 가감 ; 출력 시 글자를 재조합해서 보여줌 |
|
|
print(len(normalize('NFKD', '가감'))) # 5 |
|
|
print(normalize('NFKC', normalize('NFKD', '가감'))) # 가감 |
|
|
print(len(normalize('NFKC', normalize('NFKD', '가감')))) # 2 |
|
|
``` |
|
|
|
|
|
```python |
|
|
def compose(tokens): |
|
|
return [normalize('NFKC', token) for token in tokens] |
|
|
|
|
|
print(f'tokenizers : {compose(bert_wordpiece_tokenizer.encode(sent_ko).tokens)}') |
|
|
print(f'transformers: {compose(transformers_bert_tokenizer.tokenize(sent_ko))}') |
|
|
``` |
|
|
|
|
|
``` |
|
|
tokenizers : ['신종', '코로나바이러스', '감염증', '(', '코로나19', ')', '사태', '##가', '심', '##각', '##합니다'] |
|
|
transformers: ['신종', '코로나바이러스', '감염증', '(', '코로나19', ')', '사태', '##가', '심', '##각', '##합니다'] |
|
|
``` |
|
|
|
|
|
GPT2Tokenizer 를 확인해보자. |
|
|
|
|
|
```python |
|
|
transformers_gpt2_tokenizer = GPT2Tokenizer( |
|
|
vocab_file = './tokenizers/ByteLevelBPETokenizer/covid-vocab.json', |
|
|
merges_file = './tokenizers/ByteLevelBPETokenizer/covid-merges.txt' |
|
|
) |
|
|
print(f'tokenizers : {byte_level_bpe_tokenizer.encode(sent_ko).tokens}') |
|
|
print(f'transformers: {transformers_gpt2_tokenizer.tokenize(sent_ko)}') |
|
|
``` |
|
|
|
|
|
``` |
|
|
tokenizers : ['ìĭłì¢ħ', 'Ġì½Ķë¡ľëĤĺë°ĶìĿ´ë٬ìĬ¤', 'Ġê°IJìĹ¼ì¦Ŀ', '(', 'ì½Ķë¡ľëĤĺ', '19', ')', 'ĠìĤ¬íĥľ', 'ê°Ģ', 'Ġìĭ¬', 'ê°ģ', 'íķ©ëĭĪëĭ¤'] |
|
|
transformers: ['ìĭłì¢ħ', 'Ġì½Ķë¡ľëĤĺë°ĶìĿ´ë٬ìĬ¤', 'Ġê°IJìĹ¼ì¦Ŀ', '(', 'ì½Ķë¡ľëĤĺ', '19', ')', 'ĠìĤ¬íĥľ', 'ê°Ģ', 'Ġìĭ¬', 'ê°ģ', 'íķ©ëĭĪëĭ¤'] |
|
|
``` |
|
|
|
|
|
# Customizing tokenizers |
|
|
|
|
|
```python |
|
|
compose(transformers_bert_tokenizer.tokenize('lovit 이란 이름은 인식을 안합니다')) |
|
|
# ['l', '##o', '##v', '##i', '##t', '이라', '##ᆫ', '이', '##름', '##은', '인', '##식을', '안', '##합니다'] |
|
|
``` |
|
|
- 데이터에 충분히 등장하지 않았더라도 특정 단어를 보존하고 싶다. |
|
|
- 기형태소 분석기와 혼합하여 BertTokenizer 를 이용하고 싶다. |
|
|
|
|
|
## KoNLPy 형태소 분석기를 preprocessor 로 이용하기 tokenizers 학습하기 |
|
|
|
|
|
### 방법 1. Normalizer 나 PreTokenizer 로 만들기 (실패) |
|
|
|
|
|
- Normalizer 와 PreTokenizer 는 Rust 코드를 Python 에서 이용할 수 있도록 도와주는 클래스로 상속이 되지 않음 |
|
|
|
|
|
```python |
|
|
from tokenizers.normalizers import Normalizer |
|
|
class KomoranNormalizer(Normalizer): |
|
|
def __init__(self): |
|
|
print('success') |
|
|
"type 'tokenizers.normalizers.Normalizer' is not an acceptable base type" |
|
|
``` |
|
|
|
|
|
```python |
|
|
from tokenizers.pre_tokenizers import PreTokenizer |
|
|
class KomoranPreTokenizer(PreTokenizer): |
|
|
def __init__(self): |
|
|
print('success') |
|
|
"type 'tokenizers.pre_tokenizers.PreTokenizer' is not an acceptable base type" |
|
|
``` |
|
|
|
|
|
### 방법 2. 학습데이터를 KoNLPy 로 미리 분해하여 tokenizers 학습 |
|
|
|
|
|
```python |
|
|
from konlpy.tag import Komoran, Mecab, Okt |
|
|
|
|
|
class KoNLPyPreTokenizer: |
|
|
def __init__(self, base_tokenizer): |
|
|
self.base_tokenizer = base_tokenizer |
|
|
def __call__(self, sentence): |
|
|
return self.pre_tokenize(sentence) |
|
|
def pre_tokenize(self, sentence): |
|
|
return ' '.join(self.base_tokenizer.morphs(sentence)) |
|
|
|
|
|
konlpy_pretok = KoNLPyPreTokenizer(Komoran()) |
|
|
print(konlpy_pretok(sent_ko)) |
|
|
# 신종 코로나바이러스 감염증 ( 코로나 19 ) 사태 가 심각 하 ㅂ니다 |
|
|
``` |
|
|
|
|
|
```python |
|
|
def prepare_pretokenized_corpus(raw_path, pretokenized_path, pretok): |
|
|
with open(raw_path, encoding='utf-8') as f: |
|
|
lines = [line.strip() for line in f] |
|
|
with open(pretokenized_path, 'w', encoding='utf-8') as f: |
|
|
for line in lines: |
|
|
f.write(f'{pretok(line)}\n') |
|
|
|
|
|
prepare_pretokenized_corpus( |
|
|
'../data/2020-07-29_covid_news_sents.txt', |
|
|
'../data/2020-07-29_covid_news_sents.komoran.txt', |
|
|
KoNLPyPreTokenizer(Komoran())) |
|
|
|
|
|
bert_wordpiece_tokenizer = BertWordPieceTokenizer() |
|
|
bert_wordpiece_tokenizer.train( |
|
|
files = ['../data/2020-07-29_covid_news_sents.komoran.txt'], |
|
|
vocab_size = 3000) |
|
|
bert_wordpiece_tokenizer.save_model( |
|
|
directory='./tokenizers/KomoranBertWordPieceTokenizer/', |
|
|
name='covid') |
|
|
``` |
|
|
|
|
|
```python |
|
|
for vocab, idx in bert_wordpiece_tokenizer.get_vocab().items(): |
|
|
if normalize('NFKD', '니다') in vocab: |
|
|
print(vocab, idx) |
|
|
``` |
|
|
|
|
|
``` |
|
|
ㅂ니다 1106 |
|
|
습니다 977 |
|
|
##니다 909 |
|
|
``` |
|
|
|
|
|
### custom 기능은 제공되지 않는가? |
|
|
|
|
|
- 올해 4월 examples 에 custom_pre_tokenizer 가 업로드 됨. [링크](https://github.com/huggingface/tokenizers/blob/master/bindings/python/examples/custom_pre_tokenizer.py) |
|
|
- 하지만 정상적으로 작동하지 않음을 확인. |
|
|
- 계속 버전업 중이니 이 기능을 제공해줄 것으로 기대 중 |
|
|
|
|
|
```python |
|
|
from tokenizers import pre_tokenizers |
|
|
|
|
|
class GoodCustom: |
|
|
def pre_tokenize(self, sentence): |
|
|
return sentence.split(" ") |
|
|
|
|
|
def decode(self, tokens): |
|
|
return ", ".join(tokens) |
|
|
|
|
|
good_custom = GoodCustom() |
|
|
good_pretok = pre_tokenizers.PreTokenizer.custom(good_custom) |
|
|
``` |
|
|
|
|
|
|
|
|
## tokenizers 와 transformers.PretrainedTokenizer 에 KoNLPy 형태소 분석기를 preprocessor 넣기 |
|
|
|
|
|
- 학습이 끝났다면 tokenizers.BertTokenizer 의 encode, encode_batch 함수만 수정하면 된다. |
|
|
|
|
|
```python |
|
|
class KoNLPyBertWordPieceTokenizer(BertWordPieceTokenizer): |
|
|
def __init__( |
|
|
self, |
|
|
konlpy_pretok, |
|
|
vocab_file: Optional[str] = None, |
|
|
...): |
|
|
super().__init__(...) |
|
|
self.konlpy_pretok = konlpy_pretok |
|
|
|
|
|
def encode(self, sequence, pair=None, ...): |
|
|
if sequence is None: |
|
|
raise ValueError("encode: `sequence` can't be `None`") |
|
|
|
|
|
sequence = self.konlpy_pretok(sequence) # <-- 추가 |
|
|
return self._tokenizer.encode(sequence, pair, is_pretokenized, add_special_tokens) |
|
|
|
|
|
def encode_batch(self, inputs, ...): |
|
|
if inputs is None: |
|
|
raise ValueError("encode_batch: `inputs` can't be `None`") |
|
|
|
|
|
# <-- 추가 |
|
|
input_iterator = tqdm(inputs, desc='konlpy pretok', total=len(inputs)) |
|
|
konlpy_pretok_inputs = [self.konlpy_pretok(sequence) for sequence in input_iterator] |
|
|
# --> |
|
|
return self._tokenizer.encode_batch(konlpy_pretok_inputs, is_pretokenized, add_special_tokens) |
|
|
|
|
|
konlpy_bert_wordpiece_tokenizer = KoNLPyBertWordPieceTokenizer( |
|
|
konlpy_pretok, |
|
|
vocab_file = './tokenizers/KomoranBertWordPieceTokenizer/covid-vocab.txt') |
|
|
``` |
|
|
|
|
|
```python |
|
|
class KoNLPyBertTokenizer(BertTokenizer): |
|
|
def __init__( |
|
|
self, |
|
|
konlpy_pretok, |
|
|
vocab_file, |
|
|
...): |
|
|
super().__init__(...) |
|
|
self.konlpy_pretok = konlpy_pretok |
|
|
|
|
|
def _tokenize(self, text): |
|
|
text = self.konlpy_pretok(text) # <-- 추가 |
|
|
split_tokens = [] |
|
|
if self.do_basic_tokenize: |
|
|
for token in self.basic_tokenizer.tokenize(text, never_split=self.all_special_tokens): |
|
|
|
|
|
# If the token is part of the never_split set |
|
|
if token in self.basic_tokenizer.never_split: |
|
|
split_tokens.append(token) |
|
|
else: |
|
|
split_tokens += self.wordpiece_tokenizer.tokenize(token) |
|
|
else: |
|
|
split_tokens = self.wordpiece_tokenizer.tokenize(text) |
|
|
return split_tokens |
|
|
|
|
|
konlpy_bert_tokenizer = KoNLPyBertTokenizer( |
|
|
konlpy_pretok, './tokenizers/KomoranBertWordPieceTokenizer/covid-vocab.txt') |
|
|
``` |
|
|
|
|
|
```python |
|
|
compose(bert_wordpiece_tokenizer.encode(sent_ko).tokens) |
|
|
# ['신종', '코로나바이러스', '감염증', '(', '코로나', '##1', '##9', ')', '사태', '##가', '심각', '##합', '##니다'] |
|
|
|
|
|
compose(konlpy_bert_wordpiece_tokenizer.encode(sent_ko, add_special_tokens=False).tokens) |
|
|
# ['신종', '코로나바이러스', '감염증', '(', '코로나', '19', ')', '사태', '가', '심각', '하', 'ᄇ니다'] |
|
|
|
|
|
compose(konlpy_bert_tokenizer.tokenize(sent_ko)) |
|
|
# ['신종', '코로나바이러스', '감염증', '(', '코로나', '19', ')', '사태', '가', '심각', '하', 'ᄇ니다'] |
|
|
``` |