Skip to content

Instantly share code, notes, and snippets.

@Se-Hun
Forked from lovit/huggingface_konlpy.md
Created July 29, 2022 12:42
Show Gist options
  • Save Se-Hun/84d04ef5fcb404b88f1063ca10b3a95e to your computer and use it in GitHub Desktop.
Save Se-Hun/84d04ef5fcb404b88f1063ca10b3a95e to your computer and use it in GitHub Desktop.

Revisions

  1. @lovit lovit revised this gist Aug 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion huggingface_konlpy.md
    Original file line number Diff line number Diff line change
    @@ -9,7 +9,7 @@



    # huggingface tokenizers
    # Huggingface tokenizers

    ## dictionary-based vs subword tokenizers

  2. @lovit lovit revised this gist Aug 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion huggingface_konlpy.md
    Original file line number Diff line number Diff line change
    @@ -65,7 +65,7 @@ komoran.morphs(sent)
    | --- | --- | --- | --- | --- | --- |
    | 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` |
    | Sentencepiece BPE (SentencePieceBPETokenizer)^2 | char | BPE | | 어절 앞 `` 부착 | `vocab.json`, `merges.txt` |
    | Bert wordpiece (BertWordPieceTokenizer) | char | WordPiece | BertNormalizer | 어절 중간 subword 앞에 `##` 부착 | `vocab.txt` |

    - ^1: split_on_whitespace_only 기능 제공. 이를 이용하면 pre-tokenized 된 텍스트를 이용하여 학습하기에 용이
  3. @lovit lovit revised this gist Aug 27, 2020. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion huggingface_konlpy.md
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,7 @@ komoran.morphs(sent)
    - 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` |
  4. @lovit lovit created this gist Aug 27, 2020.
    575 changes: 575 additions & 0 deletions huggingface_konlpy.md
    Original file line number Diff line number Diff line change
    @@ -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', ')', '사태', '가', '심각', '하', 'ᄇ니다']
    ```