본문 바로가기

기타

블록 단위로 토크나이저 빌딩(Building a tokenizer, block by block)

  Tokenizers 라이브러리로 원하는 토크나이저(Tokenizer)를 구성할 수 있다. 기존 토크나이저를 이용하여 새로운 토크나이저를 빌드하는 것이다.

참고) Building a tokenizer, block by block - Hugging Face NLP Course

 

Building a tokenizer, block by block - Hugging Face NLP Course

The 🤗 Tokenizers library has been built to provide several options for each of those steps, which you can mix and match together. In this section we’ll see how we can build a tokenizer from scratch, as opposed to training a new tokenizer from an old o

huggingface.co

Quicktour — tokenizers documentation

 

Quicktour — tokenizers documentation

To illustrate how fast the 🤗 Tokenizers library is, let’s train a new tokenizer on wikitext-103 (516M of text) in just a few seconds. First things first, you will need to download this dataset and unzip it with: Post-processing We might want our token

huggingface.co

■ 토큰화(tokenization)은 다음과 같은 단계로 실행된다. 

- ① 정규화(Normalization)

- 공백이나 악센트 제거 등 필요하다가 여겨지는 모든 텍스트 정제 작업

- ② 사전 토큰화(Pre-Tokenization)

- 입력(input)을 단어(word)들로 분리

- ③ 모델(Unigram, WordPiece같은)을 통해 사전 토큰화된 단어들을 사용하여 토큰 시퀀스 생성

- ④ 후처리(특스 토큰 추가 등)

- 이러한 전체 프로세스를 나타내면 다음과 같다.

[출처] https://huggingface.co/learn/nlp-course/chapter6/8?fw=pt

cf) Tokenizers 라이브러리는 위의 각 개별 단계에 대한 여러 옵션을 제공한다. 

- normalizers에는 사용할 수 있는 모든 Normalizer 유형이 포함되어 있다. Normalizers 

- pre_tokenizers에는 사용할 수 있는 모든 PreTokenizer가 포함되어 있다. Pre-tokenizers

- models에는 BEE, WordPiece, Unigram같은 모델이 포함되어 있다. Models

- trainer에는 말뭉치(corpus)에서 모델을 훈련하는데 사용할 수 있는 Trainer들이 포함되어 있다. Trainers

- post_processors에는 후처리 작업을 위한 PostProcessor가 포함되어 있다. Post-processors

- decoders에는 토큰화 출력을 디코딩하는데 사용할 수 있는 Decoder들이 포함되어 있다. Components

- buliding blocks의 전체 list  Components

■ 이 예에서는 새로운 토크나이저를 학습하기 위해 WikiText-2 데이터셋을 통해 작은 텍스트 말뭉치를 사용한다.

from datasets import load_dataset

dataset = load_dataset("wikitext", name = "wikitext-2-raw-v1", split = "train")
dataset
```#결과#```
Dataset({
    features: ['text'],
    num_rows: 36718
})
````````````

- 36,718개의 텍스트가 담겨 있는 것을 확인할 수 있다.

■ 다음과 같은 get_training_corpus 함수는 1,000개의 텍스트 배치(batch)를 생성하는 생성자(generator)로, 토크나이저(tokenizer)를 학습하는데 사용할 것이다.

def get_training_corpus():
    for i in range(0, dataset.num_rows, 1000): # dataset.num_rows 대신 len(dataset)도 가능
        yield dataset[i : i+1000]["text"]

cf) 토크나이저는 로컬에 있는 텍스트 파일에서 직접 학습될 수도 있다. 로컬에서 사용하는 방법은 다음과 같이 모든 텍스트를 포함하는 텍스트 파일을 생성하면 된다.

 

 

1. Building a WordPiece tokenizer from scratch

■ Tokenizers 라이브러리로 토크나이저를 빌드하려면, 먼저 model을 사용하여 Tokenizer 객체를 인스턴스화한 다음, normalizer, pre_tokenizer, post_processor, decoder 속성을 원하는 값으로 설정한다.

■ 그러기 위해선 다음과 같이 Tokenizers 라이브러리의 하위 모듈들 normalizers, pre_tokenizers, models, trainer, post_processors, decoders, Tokenizer를 불러온다.

Tokenizers 라이브러리는 Tokenizer 클래스를 중심으로 normalizers, pre_tokenizers, models, trainer, post_processors, decoders가 기능별로 결합된 구성 요소(building blcoks)가 결합된 형태로 구축되어 있다.

from tokenizers import (
    decoders,
    models,
    normalizers,
    pre_tokenizers,
    processors,
    trainers,
    Tokenizer,
)

■ 다음은 WordPiece model을 사용하여 Tokenizer를 만드는 코드이다.

tokenizer = Tokenizer(models.WordPiece(unk_token = "[UNK]"))

■ OOV word를 만났을 때, 이를 처리할 수 있도록 unk_token을 지정해야 한다.

- 이때, unk_token 외에 모델의 vocab과 각 단어의 최대 길이를 지정하는 max_input_chars_per_word를 설정할 수 있다.

-- max_input_chars_per_word = c이면, c보다 더 긴 단어를 분할한다.

-- 이 예에서는 모델을 학습할 것이므로 voacb에 대해서 설정하지 않는다.


1.1 토큰화 (파이프라인)의 첫 번째 단계 - 정규화(Normalization)

■ 토큰화의 첫 번째 단계는 정규화이다. 이 예에서는 다음과 같이 BertNormalizer를 사용한다. 

tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True)

- BertNormalizer의 파라미터에는 소문자를 사용할지에 대한 여부인 lowercase(기본값은 True) 를 포함하여

- 모든 악센트를 제거할지에 대한 여부인 strip_accents, 

- 모든 제어 문자를 제거하고 반복되는 공백을 단일 문자로 바꾸는 clean_text (기본값은 True)

- 한자(Chinese characters) 주위에 공백을 배치하는 handle_chinese_chars (기본값은 True)가 있다.

bert-base-uncased 토크나이저를 사용하려면 BertNormalizer를 사용하면 된다.

■ 단, 새로운 토크나이저를 빌드할 때, Tokenizers 라이브러리에 이미 구현된 노멀라이저(normalizer)에 접근할 수 없다. 

■ BERT 노멀라이저를 수동으로 직접 만드는 방법은 다음과 같이 'Sequence'를 사용하여 여러 노멀라이저를 구성하는 것이다.

tokenizer.normalizer = normalizers.Sequence(
    [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()]
)

tokenizer.normalizer
```#결과#```
Sequence(normalizers=[NFD(), Lowercase(), StripAccents()])
````````````

- 여기서 NFD 유니코드 노멀라이저를 사용하고 있다.

- NFD 유니코드 노멀라이저를 사용하지 않으면, StripAccents 노멀라이저가 악센트가 있는 문자를 제대로 인식하지 못해 해당 문자를 제거하지 못하기 때문이다.

print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?"))
```#결과#```
hello how are u?
````````````

- normalize_str( ) 메서드는 텍스트에 적용된 정규화 과정의 결과를 이렇게 직접 문자열로 반환해줌으로써 정규화 과정을 확인할 수 있게 해준다.

- 또 다른 예로, 다음과 같이 "café" \( \rihgtarrow \) cafe라면 악센트가 제거된 것으로 보아 StripAccents 노멀라이저가 포함되어 있음을 알 수 있다. 

original = "café"
normalized = tokenizer.normalizer.normalize_str(original)
print(f'원본: {original}')
print(f'정규화 후: {normalized}')
```#결과#```
원본: café
정규화 후: cafe
````````````

- normalize_str( )는 이렇게 정규화가 수행되는 방식을 확인하는데 사용할 수 있다.


1.2 토큰화 (파이프라인)의 두 번째 단계 - 사전 토큰화(pre-tokenization)

사전 토큰화 단계에서도, 다음과 같이 BertPreTokenizer( )처럼 사전 빌드된 것을 사용할 수 있다. 

tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer()

■ 또는 다음과 같이 처음부터 빌드할 수도 있다.

tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()

■ Whitespace는 사전 토크나이저(pre-tokenizer)로 '공백', '문자(character)', '숫자' 등 ' 밑줄 문자가 아닌 모든 문자'를 기준으로 텍스트를 분할한다.(=공백과 구두점으로 분할한다고 볼 수 있다.)

tokenizer.pre_tokenizer = pre_tokenizers.Whitespace()
tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```#결과#```
[('Let', (0, 3)),
 ("'", (3, 4)),
 ('s', (4, 5)),
 ('test', (6, 10)),
 ('my', (11, 13)),
 ('pre', (14, 17)),
 ('-', (17, 18)),
 ('tokenizer', (18, 27)),
 ('.', (27, 28))]
````````````

cf) '공백'만을 기준으로 분할하려면 WhitespaceSplit 사전 토크나이저를 사용하면 된다. 

pre_tokenizer = pre_tokenizers.WhitespaceSplit() 
pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```#결과#```
[("Let's", (0, 5)),
 ('test', (6, 10)),
 ('my', (11, 13)),
 ('pre-tokenizer.', (14, 28))]
````````````

■ 사전 토크나이저도 노멀라이즈처럼 Sequence를 사용하여, 다음과 같이 여러 개의 사전 토크나이저들을 결합할 수 있다.

pre_tokenizer = pre_tokenizers.Sequence(
    [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] # '공백' 기준 + '구두점' 기준
)

pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.")
```#결과#```
[('Let', (0, 3)),
 ("'", (3, 4)),
 ('s', (4, 5)),
 ('test', (6, 10)),
 ('my', (11, 13)),
 ('pre', (14, 17)),
 ('-', (17, 18)),
 ('tokenizer', (18, 27)),
 ('.', (27, 28))]
````````````

■ 이제 다음 단계는 모델(model)을 통해 input들을 실행(학습)하는 것이다.


1.3 토큰화 (파이프라인)의 세 번째 단계 - model

1.에서 tokenizer = Tokenizer(models.WordPiece(unk_token = "[UNK]"))로 모델을 지정했었다. 이제 지정한 WordPiece 모델을 훈련해야 하며, 이를 위해서 WordPieceTrainer가 필요하다.

Tokenizers에서 trainer를 인스턴스화할 때 가장 중요한 것은 사용하려는 모든 특수 토큰(special token)을 전달해야 한다는 것이다. 그렇지 않으면 이들 토큰이 훈련 말뭉치(training corpus)에 없기 때문에 단어집합(vocabulary)에 추가되지 않는다.

■ 추가할 특수 토큰은 다음과 같다.

special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"]

■ 이제, 다음과 같이 trainer를 인스턴스화하면 된다.

trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens)

- 여기서 vocab_size와 special_tokens를 지정하는 것 외에도

- min_frequency로 토큰이 단어집합(vocabulary)에 포함되기 위한 최소 출현 빈도를 설정하거나

- continue_subword_prefix로 '##' 외의 다른 기호를 사용할 수 있다.

■ 앞에서 정의한 iterator인 get_training_corpus를 사용하여 모델을 학습시키려면 다음과 같이 하면 된다.

tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)

- 이렇게 함수(def get_training_cropus())를 지정하여 텍스트 배치(batch)를 생성할 수 있다.

- 이 예에서는 get_training_corpus( ) 함수를 통해 1,000개의 텍스트 배치를 생성한다. 즉, get_training_corpus( ) 같은 함수는 텍스트 배치(batch)를 생성하는 생성자(generator)이다.

cf) 텍스트 파일을 사용하여 토크나이저를 학습할 수도 있다.

tokenizer.model = models.WordPiece(unk_token="[UNK]")
tokenizer.train(["wikitext-2.txt"], trainer=trainer)

- 두 경우 모두 encode( ) 메서드를 호출하여 텍스트에서 토크나이저를 테스트할 수 있다.

encoding = tokenizer.encode("Let's test this tokenizer")
print(encoding.tokens)
```#결과#```
['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer']
````````````

■ 여기서 얻은 encoding은 토크나이저의 다양한 속성 ids, tokens, offsets, attention_mask, special_tokens_mask, overflowing, pad 등을 포함하는 Encoding 클래스의 객체이다.

print(dir(encoding))
```#결과#```
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', 
'__init_subclass__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__',
'__str__', '__subclasshook__', 'attention_mask', 'char_to_token', 'char_to_word', 'ids',
'merge', 'n_sequences', 'offsets', 'overflowing', 'pad', 'sequence_ids', 'set_sequence_id',
'special_tokens_mask', 'token_to_chars', 'token_to_sequence', 'token_to_word', 'tokens', 
'truncate', 'type_ids', 'word_ids', 'word_to_chars', 'word_to_tokens', 'words']
````````````

1.4 토큰화 (파이프라인)의 네 번째 단계 - 후처리(post-processing)

■ 토큰화 파이프라인의 마지막 단계는 후처리 단계이다. 

■ [CLS] 토큰을 시작 부분에 추가하고 [SEP] 토큰을 끝 부분에 추가해야 한다.

- [CLS]는 모든 문장의 가장 첫 번째 시작 부분(즉, 문장의 시작) 토큰으로 삽입된다. 그러므로 전체 시퀀스를 분류하는 일종의 분류 토큰이다..

- [SEP]는 seperate 토큰으로 두 개의 문장(첫 번재 문장과 두 번째 문장)를 나누기 위해(구별하기 위해) 사용한다. 

■ 한 쌍의 문장(문장이 두 개 있는 경우)에는 각 문장 뒤에 [SEP]를 붙인다. 

■ 이를 위해서 TemplateProcessor를 사용하지만, 먼저 단어집합(vocabulary)에서 [CLS] 및 [SEP] 토큰의 ID를 알아야 한다. 토큰 ID를 통해 모델이 문장 경계를 인식할 수 있기 때문이다. 

cls_token_id = tokenizer.token_to_id("[CLS]")
sep_token_id = tokenizer.token_to_id("[SEP]")

print(cls_token_id, sep_token_id)
```#결과#```
2 3
````````````

TemplateProcessor의 템플릿을 작성하려면, 단일 문장과 문장 쌍을 처리하는 방법을 지정해야 한다. 둘 다 사용하려는 특수 토큰을 작성한다. (이 예에서는 BERT 템플릿을 만들기 위해 단일 문장과 문자 쌍을 처리하는 방법을 지정한다.)

여기서는 다음과 같이 $A는 첫 번째(또는 단일)문장을, $B는 두 번째 문장(쌍을 인코딩하는 경우)을 나타낸다. 

그리고 콜론 뒤의 숫자는 해당 토큰의 유형 ID도 지정한다.

이렇게 하여 다음과 같이 고전적인 BERT 템플릿을 정의한다. 

tokenizer.post_processor = processors.TemplateProcessing(
    single=f"[CLS]:0 $A:0 [SEP]:0", # 단일 문장 템플릿
    pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", # 문장 쌍 템플릿
    special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)]
)

■ 특수 토큰의 ID를 전달해야 토크나이저가 이를 해당 ID로 적절하게 변환할 수 있다.

■ 이렇게 템플릿이 추가되면, 예를 들어 이전 예제의 결과는 다음과 같다.

encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```#결과#```
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]']
````````````

■ 2개의 문장을 입력하면 다음과 같은 결과가 나온다.

encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.")
print(encoding.tokens)
print(encoding.type_ids)
```#결과#```
['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
````````````

■ 마지막 단계는 다음과 같이 디코더를 포함하는 것이다.

tokenizer.decoder = decoders.WordPiece(prefix="##")

■ 이렇게 모든 단계를 거쳐 WordPiece 토크나이저를 완성했다.

위의 encoding을 이용해서 디코더를 테스트해 보면 다음과 같다.

tokenizer.decode(encoding.ids)
```#결과#```
"let ' s test this tokenizer... on a pair of sentences."
````````````

1.5 저장 및 로드

■ 이렇게 만든 토크나이저는 json 파일에 저장할 수 있다.

tokenizer.save("tokenizer.json")

from_file( ) 메서드를 사용해서 저장한 json 파일을 다시 로드하여 저장한 토크나이저를 불러올 수 있다.

new_tokenizer = Tokenizer.from_file("tokenizer.json")

Transformers에서 이렇게 직접 만든 토크나이저를 사용하려면 PreTrainedTokenizerFast로 래핑(wrapping)해야 한다.(트랜스포머 라이브러리에서 사용할 수 있는 형태로 변환해야 한다.)

PreTrainedTokenizerFast를 가지고 토크나이저를 래핑하려면, 새롭게 만든 토크나이저(new_tokenizer)를 tokenizer_object로 전달하거나 tokenizer_file로 저장한 토크나이저 파일을 전달할 수 있다.

중요한 점은 모든 특수 토큰을 수동으로 직접 설정해야 한다는 것이다. PreTrainedTokenizerFast 클래스는 tokenizer 객체로부터, 예를 들어 어떤 토큰이 마스터 토큰인지, 아니면 [CLS] 토큰인지를 추론할 수 없기 때문이다. 

from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=new_tokenizer,  # 직접 만든 토크나이저 객체
    unk_token="[UNK]",
    pad_token="[PAD]",
    cls_token="[CLS]",
    sep_token="[SEP]",
    mask_token="[MASK]"
)

■ BertTokenizerFast같은 특정 토크나이저 클래스를 사용하는 경우, 기본적으로 지정된 토큰과 다른 특수 토큰만 지정하면 된다.

from transformers import BertTokenizerFast

wrapped_tokenizer = BertTokenizerFast(tokenizer_object=new_tokenizer)


2. Building a BPE tokenizer from scratch

■ 이번에는 GPT-2 토크나이저를 빌드해 보겠다. 위의 BERT 토크나이저 예시처럼, 먼저 다음과 같이 Tokenizer를 초기화하는 것으로 시작한다. 이때, BPE 모델을 사용한다.

tokenizer = Tokenizer(models.BPE())

■ 기존에 단어집합(vocabulary)가 있는 경우, 모델을 해당 단어집합(vocabulary)으로 초기화할 수 있다.

■ 이 예에서는 처음부터 학습할 것이므로 그럴 필요가 없다.

■ 또한 GPT-2는 byte level BPE를 사용하기 때문에 unk_token을 지정할 필요가 없다.

■ 그리고 GPT-2는 노멀라이즈를 사용하지 않으므로 해당 단계를 건너뛰고 사전 토큰화(pre-tokeniation) 단계부터 시작한다.

■ 다음과 같이 사전 빌드된 ByteLevel( )을 사용한다.

tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)

- ByteLevel에 추가한 옵션은 문장 시작 부분에 공백을 추가하지 않도록 하는 옵션이다. 

■ 다음은 훈련할 모델을 설정하는 단계이다. GPT-2의 경우 유일한 특수 토큰은 end-of-text 토큰이다.

trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"])
tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)

- 여기서도 위의 WordPieceTrainer처럼 vocab_size, special_tokens, min_frequency를 지정할 수 있다.

- 또한, </w>와 같은 단어 끝 접미사(end-of-word suffix)가 있는 경우에 end_of_word_suffix를 설정할 수 있다.

- 그리고, 마찬가지로 텍스트 파일에 대해서 학습할 수 있다.

tokenizer.model = models.BPE()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)

■ 샘플 텍스트로 토큰화 테스트를 해본 결과는 다음과 같다.

encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```#결과#```
['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.']
````````````

■ 다음과 같이 GPT-2 토크나이저에 바이트 수준(byte-level) 후처리를 적용한다.

tokenizer.post_processor = processors.ByteLevel(trim_offsets=False)

- trim_offsets 옵션은 후처리기(post-processor)가 'Ġ'로 시작하는 토큰의 오프셋을 그대로 두어야 함을 나타낸다.

- 이렇게 하면 오프셋의 시작은 단어의 첫 번째 문자가 아니라 단어 앞의 공백을 가리킨다. (공백은 토큰의 일부이기 때문이다.)

- 위의 인코딩한 결과를 살펴보자. 여기서 'Ġtest'는 인덱스 4에 있는 토큰입니다.

sentence = "Let's test this tokenizer."
encoding = tokenizer.encode(sentence)
start, end = encoding.offsets[4]
sentence[start:end]
```#결과#```
' test'
````````````

■ 마지막으로 바이트 수준(byte-level) 디코더를 추가한다.

tokenizer.decoder = decoders.ByteLevel()

■ 디코더가 제대로 작동하는지 다음과 같이 확인할 수 있다.

tokenizer.decode(encoding.ids)
```#결과#```
"Let's test this tokenizer."
````````````

■ 이제 토크나이저를 만드는 것이 끝났으므로 이전처럼 토크나이저를 저장하고, PreTrainedTokenizerFast 또는 GPT2TokenizerFast를 이용하여 래핑할 수 있다. 

from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<|endoftext|>",
    eos_token="<|endoftext|>",
)

또는

from transformers import GPT2TokenizerFast

wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer)


3. Building a Unigram tokenizer from scratch

■ 이번에는 XLNet 토크나이저를 빌드한다. 이번에는 Unigram 모델로 Tokenizer를 초기화한다. 

tokenizer = Tokenizer(models.Unigram())

■ 여기서도 단어집합이 있는 경우 모델을 단어집합으로 초기화할 수 있다.

■ 정규화(normalization)를 위해 XLNet은 다음과 같은 몇 가지 대체 규칙(replacements)을 사용한다. (대체 규직은 SentencePiece에서 나온 것)

from tokenizers import Regex

tokenizer.normalizer = normalizers.Sequence(
    [
        normalizers.Replace("``", '"'),
        normalizers.Replace("''", '"'),
        normalizers.NFKD(),
        normalizers.StripAccents(),
        normalizers.Replace(Regex(" {2,}"), " "),
    ]
)

■ 위의 대체 규칙은 "and"를 "로 대체하고, 둘 이상의 공백 시퀀스를 단일 공백으로 대체하며, 토큰화할 텍스트의 악센트를 제거한다. 

■ SentencePiece 토크나이저에 사용할 사전 토크나이저는 Metaspace이다. 

tokenizer.pre_tokenizer = pre_tokenizers.Metaspace()

■ 샘플 텍스트에 대한 사전 토큰화를 적용해 보면

tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!")
```#결과#```
[("▁Let's", (0, 5)),
 ('▁test', (5, 10)),
 ('▁the', (10, 14)),
 ('▁pre-tokenizer!', (14, 29))]
````````````

다음은 모델(model) 학습이다. XLNet에는 몇 가지 특별한 토큰들이 있다. 

special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]special_tokens = ["<cls>", "<sep>", "<unk>", "<pad>", "<mask>", "<s>", "</s>"]

trainer = trainers.UnigramTrainer(
    vocab_size=25000, special_tokens=special_tokens, unk_token="<unk>"
)

tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer)

■ UnigramTrainer에서 빼먹지 말아야 할 중요한 인수는 unk_token이다. 

■ 또한, 토큰을 제거하는 각 단계에 대한 shrinking_factor(기본값 0.75) 또는 주어진 토큰의 최대 길이를 지정하기 위한 max_piece_length(기본값 16) 등과 같은 Unigram 알고리즘에 특화된 추가 매개변수들을 전달할 수도 있다.

■ 이 토크나이저 역시 다음과 같이 텍스트 파일에 대해서도 학습할 수 있다.

tokenizer.model = models.Unigram()
tokenizer.train(["wikitext-2.txt"], trainer=trainer)

■ 샘플 테스트에 대한 토큰화 결과를 확인해 보자.

encoding = tokenizer.encode("Let's test this tokenizer.")
print(encoding.tokens)
```#결과#```
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.']
````````````

■ XLNet의 특징은 토큰 타입 ID가 2인 <cls> 토큰을 문장 끝에 추가한다는 것이다. 결과적으로 좌측 패딩이다. 

■ BERT와 같이 템플릿을 사용하여 모든 특수 토큰과 토큰 타입 ID를 처리할 수 있지만, 그전에 먼저 <cls> 및 <sep> 토큰의 ID를 가져와야 한다.

cls_token_id = tokenizer.token_to_id("<cls>")
sep_token_id = tokenizer.token_to_id("<sep>")
print(cls_token_id, sep_token_id)
```#결과#```
0 1
````````````

■ 템플릿은 다음과 같은 형태로 구현된다.

tokenizer.post_processor = processors.TemplateProcessing(
    single="$A:0 <sep>:0 <cls>:2",
    pair="$A:0 <sep>:0 $B:1 <sep>:1 <cls>:2",
    special_tokens=[("<sep>", sep_token_id), ("<cls>", cls_token_id)],
)

■ 이제 한 쌍의 문장에 대한 인코딩이 제대로 작동하는지 테스트할 수 있다.

encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!")
print(encoding.tokens)
print(encoding.type_ids)
```#결과#```
['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '<sep>', '▁', 'on', '▁', 'a', '▁pair', '▁of', '▁sentence', 's', '!', '<sep>', '<cls>']
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2]
````````````

마지막으로 Metaspace 디코더를 추가한다.

tokenizer.decoder = decoders.Metaspace()

■ 마찬가지로 완성된 토크나이저를 저장하고 Transformers 내에서 사용하려면 PreTrainedTokenizerFast 또는 XLNetTokenizerFast로 래핑할 수 있다.

  PreTrainedTokenizerFast를 사용할 때 한 가지 주의해야 할 점은 특수 토큰 지정과 더불어 Transformers 라이브러리에 왼쪽을 채우도록 다음과 같이  padding_side="left"를 추가해야 한다.

from transformers import PreTrainedTokenizerFast

wrapped_tokenizer = PreTrainedTokenizerFast(
    tokenizer_object=tokenizer,
    bos_token="<s>",
    eos_token="</s>",
    unk_token="<unk>",
    pad_token="<pad>",
    cls_token="<cls>",
    sep_token="<sep>",
    mask_token="<mask>",
    padding_side="left",
)

또는 

from transformers import XLNetTokenizerFast

wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer)