토크나이저
토크나이저는 자연어처리 파이프라인의 핵심 요소 중 하나입니다. 토크나이저의 역할은 텍스트를 모델이 처리할 수 있는 데이터로 변환하는 것입니다. 모델은 숫자만 처리할 수 있기 때문에 토크나이저는 텍스트 입력을 수치형 데이터로 변환해야 합니다. 이 장에서는 토큰화 파이프라인에서 정확히 어떤 일이 일어나고 있는지 알아볼 것입니다.
자연어처리 태스크에서 처리되는 데이터는 일반적으로 원시 텍스트입니다. 아래는 원시 텍스트의 예시입니다.
Jim Henson was a puppeteer
그러나 모델은 숫자만 처리할 수 있기 때문에 우리는 원시 텍스트를 숫자로 바꿀 방법을 찾아야 합니다. 그게 바로 토크나이저가 하는 일이며 이 문제를 해결할 수 있는 다양한 방법이 있습니다. 목표는 모델에 가장 적합하면서 간결한 표현을 찾는 것입니다.
토큰화 알고리즘의 몇 가지 예시를 살펴보고 당신이 토큰화에 대해 가지는 궁금증에 대한 해답을 찾아봅시다.
단어 기반 토큰화
가장 먼저 떠오르는 토큰화 유형은 단어 기반입니다. 몇 가지 규칙만으로도 설정 및 사용이 매우 쉽고 종종 괜찮은 결과를 출력합니다. 예를 들어, 아래 보이는 사진에서 목표는 원시 텍스트를 단어로 나누고 단어 각각에 대한 수치 표현을 찾는 것입니다.
텍스트를 나누는 방법은 다양합니다. 예를 들면 파이썬의 split()
함수를 이용해 공백 기준으로 텍스트를 나눌 수 있습니다.
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
['Jim', 'Henson', 'was', 'a', 'puppeteer']
구두점을 위한 규칙이 추가된 토크나이저도 있습니다. 이러한 종류의 토크나이저를 사용하면 꽤 큰 “단어 사전”을 얻을 수 있는데, 이 때 단어 사전은 우리가 가진 말뭉치(corpus) 내 존재하는 고유한 토큰 수에 의해 정의됩니다.
각 단어에는 0부터 시작해서 단어 사전 크기까지의 ID가 할당됩니다. 모델은 각 단어를 구별하기 위해 이 ID들을 사용합니다.
단어 기반 토크나이저로 언어를 완벽하게 처리하고 싶다면 언어의 각 단어에 대한 식별자가 있어야 하며, 이는 엄청난 양의 토큰을 생성할 것입니다. 예를 들어, 영어에 500,000개가 넘는 단어가 있다고 한다면, 각 단어에 입력 ID를 매핑시키기 위해 많은 ID를 추적해야 합니다. 게다가 “dog”와 같은 단어는 “dogs”처럼 다르게 표현되어 모델이 처음에 “dog”와 “dogs”가 유사하다는 것을 알 방법이 없을 것입니다. 모델은 두 단어를 관련이 없다고 인식할 것입니다. “run”과 “running” 같은 다른 유사한 단어에도 적용되는 것으로, 모델은 처음에 두 단어를 유사한 것으로 보지 않습니다.
마지막으로, 단어 사전에 없는 단어를 표현하기 위한 커스텀 토큰이 필요합니다. “unknown” 토큰으로 알려진 이 토큰은 ”[UNK]“이나 ”<UNK>“로 표현됩니다. 토크나이저가 unknown 토큰을 많이 만드는 것은 단어에 적합한 표현을 찾지 못해 정보를 잃어가고 있는 것이기 때문에 일반적으로 좋지 않은 신호입니다. 단어 사전을 생성할 때의 목표는 토크나이저가 단어를 unknown 토큰으로 가능한 한 적게 토큰화하는 것입니다.
Unknown 토큰 수를 줄이기 위한 한 가지 방법은 한 단계 더 들어가 문자 기반 토크나이저를 사용하는 것입니다.
문자 기반 토큰화
문자 기반 토크나이저는 텍스트를 단어가 아닌 문자 단위로 나눕니다. 이 방법은 두 가지 장점이 있습니다.
- 단어 사전이 간결해진다.
- 모든 단어는 문자로 이루어졌기 때문에 out-of-vocabulary (unknown) 토큰의 수가 훨씬 적다.
하지만 여기에서도 공백과 구두점에 대한 몇 가지 궁금증을 가질 수 있습니다.
이 방법 또한 완벽하지 않습니다. 이제 표현은 단어가 아닌 문자에 기반을 두고 있기 때문에 누군가는 문자의 의미가 적다고 주장할 수 있습니다. 각각의 단어는 그 자체로 큰 의미가 있지만 문자는 그렇지 않습니다. 그러나 이 점은 다시 언어에 따라 달라집니다. 예를 들어, 중국어에서는 각 문자가 라틴어보다 더 많은 정보를 전달합니다.
또 다른 고려 사항은 모델이 처리해야 하는 매우 많은 양의 토큰이 생기게 된다는 것입니다. 단어 기반 토크나이저에서 단어는 단 하나의 토큰인 반면, 문자로 변환하면 10개 이상의 토큰으로 쉽게 바뀔 수 있습니다.
장점만을 최대한 활용하기 위해 두 가지 방법을 결합한 세 번째 기법인 서브워드 토큰화를 사용할 것입니다.
서브워드 토큰화
서브워드 토큰화 알고리즘은 자주 사용되는 단어는 더 작은 서브워드로 나누면 안되지만, 희귀한 단어는 의미 있는 서브워드로 나눠야 한다는 규칙에 기반합니다.
예를 들면 “annoyingly”는 흔하지 않은 단어로 여겨질 수 있고 “annoying”과 “ly”로 분해할 수 있을 것입니다. 둘다 독립적인 서브워드로 자주 등장할 가능성이 있는 반면에 “annoyingly”는 “annoying”과 “ly”의 합성으로만 의미가 유지됩니다.
다음 예시는 서브워드 토큰화 알고리즘이 “Let’s do tokenization!”이라는 문장을 어떻게 토큰화하는지 보여줍니다.
서브워드는 많은 양의 의미론적 정보를 제공합니다. 위의 예시에서 “tokenization”은 의미론적인 정보를 갖는 두 개의 토큰 “token”과 “ization”으로 분할되었고 긴 단어를 두 단어만으로 표현할 수 있어 공간 효율적입니다. 이 방식을 통해 크기가 작은 단어 사전으로도 많은 토큰을 표현할 수 있고 unknown 토큰도 거의 없습니다.
서브워드 토큰화를 이용한 접근법은 터키어와 같은 교착어에서 유용합니다. 서브워드를 함께 묶어 길고 복잡한 단어를 형성할 수 있습니다.
더 알아보기!
다양한 토큰화 기법이 존재하는데 몇 가지만 나열해보겠습니다.
- GPT-2에서 사용된 Byte-level BPE
- BERT에서 사용된 WordPiece
- 여러 다언어 모델에서 사용되는 SentencePiece 또는 Unigram
API를 알아보기 위해 토크나이저가 작동하는 과정에 대해 충분히 알고 있어야 합니다.
불러오기 및 저장
토크나이저를 불러오고 저장하는 것은 모델에서 했던 것만큼 간단합니다. 사실, 모델과 동일하게 from_pretrained()
과 save_pretrained()
두 메서드에 기반합니다. 이 메서드들은 토크나이저가 사용하는 알고리즘(모델의 구조와 약간 유사)과 단어 사전(모델 가중치와 약간 유사)을 불러오거나 저장합니다.
BERT와 동일한 체크포인트로 학습된 BERT 토크나이저를 불러오는 것은 BertTokenizer
클래스를 사용하는 것만 제외하면 모델과 동일한 방식으로 수행됩니다.
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
AutoModel
과 마찬가지로 AutoTokenizer
클래스는 체크포인트 이름에 기반해 적절한 토크나이저 클래스를 가져오고 어떤 체크포인트와도 함께 직접 사용할 수 있습니다.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
이제 이전 섹션에서 본 것처럼 토크나이저를 사용할 수 있습니다.
tokenizer("Using a Transformer network is simple")
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
토크나이저 저장은 모델 저장과 동일합니다.
tokenizer.save_pretrained("directory_on_my_computer")
위 출력 결과에서 token_type_ids
는 제3단원에서 이야기할 것이고 attention_mask
키는 나중에 설명할 것입니다. 그 전에 먼저 input_ids
가 어떻게 만들어지는지 알아봅시다. 이를 위해 토크나이저의 중간 메서드를 들여다봐야 합니다.
인코딩
텍스트를 숫자로 바꾸는 것은 인코딩으로 알려져 있습니다. 인코딩은 토큰화, 토큰을 입력 ID로 바꾸는 두 단계에 걸쳐 수행됩니다.
첫 번째 단계는 이미 봤던 것처럼 텍스트를 흔히 토큰이라고 부르는 단어(또는 단어의 일부, 구두점 기호 등)로 나누는 것입니다. There are multiple rules that can govern that process, which is why we need to instantiate the tokenizer using the name of the model, to make sure we use the same rules that were used when the model was pretrained.
두 번째 단계는 생성된 토큰들을 숫자로 변환해 텐서로 만들고 모델로 넘겨주는 것입니다. 이 과정을 위해 토크나이저는 from_pretrained()
메서드로 인스턴스화할 때 다운로드한 단어 사전을 가지고 있습니다. 여기서도 모델이 사전학습될 때 사용한 것과 동일한 단어 사전을 사용해야 합니다.
두 단계를 더 잘 이해하기 위해, 단계별로 알아봅시다. Note that we will use some methods that perform parts of the tokenization pipeline separately to show you the intermediate results of those steps, but in practice, you should call the tokenizer directly on your inputs (as shown in the section 2).
토큰화
토큰화 과정은 토크나이저의 tokenize()
메서드를 통해 작동합니다.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)
print(tokens)
이 메서드의 출력 결과는 문자열 리스트이거나 토큰입니다.:
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
이 토크나이저는 서브워드 토크나이저입니다. 단어 사전으로 표현할 수 있는 토큰을 얻을 때까지 단어를 분할합니다. transformer
의 경우 transform
과 ##er
로 나눠집니다.
토큰에서 입력 ID까지
토크나이저의 convert_tokens_to_ids()
메서드를 이용해 토큰을 입력 ID로 변환합니다.
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
[7993, 170, 11303, 1200, 2443, 1110, 3014]
적절한 프레임워크의 텐서로 변환되고 나면 이 출력 결과는 이전 장에서 본 것처럼 모델 입력으로 사용될 수 있습니다.
✏️ 직접 해보세요! 2장에서 사용한 입력 문장(“I’ve been waiting for a HuggingFace course my whole life.”과 “I hate this so much!“)을 이용해 두 단계(토큰화와 입력 ID로의 변환)를 수행해보세요. 위에서 얻은 결과와 당신이 얻은 결과가 동일한지 확인해보세요!
디코딩
단어 사전의 인덱스로부터 문자열을 얻고 싶기 때문에 디코딩은 인코딩과 반대로 진행됩니다. 아래처럼 decode()
메서드를 이용할 수 있습니다.
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
'Using a Transformer network is simple'
decode
메서드는 인덱스를 토큰으로 바꿀 뿐만 아니라, 읽기 좋은 문장을 만들기 위해 같은 단어의 일부인 토큰을 그룹화합니다. 이 과정은 새 텍스트(프롬프트에서 생성된 텍스트 또는 번역이나 요약과 같은 시퀀스 간 문제)를 예측하는 모델을 쓸 때 매우 유용합니다.
이제 토크나이저가 수행하는 토큰화, ID로의 변환, ID를 다시 문자열로 변환하는 과정을 이해할 수 있어야 합니다. 그러나 이는 빙산의 일각입니다. 이어지는 섹션에서는 이 접근법의 한계를 알아보고, 이를 극복하는 방법을 알아볼 것입니다.