Tokenize: 토큰화

국부은하군·2024년 10월 14일
0

NLP

목록 보기
1/1
post-thumbnail

Tokenization

토큰화(tokenization)는 자연어 처리(NLP)에서 텍스트를 의미 있는 단위로 분할하는 과정이다. 이러한 단위는 '토큰(token)'이라고 불리며, 토큰은 단어, 문장, 문단, 혹은 더 작은 의미 단위가 될 수 있다. 토큰화는 텍스트를 기계가 이해하고 처리할 수 있도록 만드는 첫 단계로서, 이후의 언어 모델링, 기계 학습 등에 중요한 역할을 한다.

토크나이제이션 방법에는 크게 단어 단위, 형태소 단위, 문자 단위, 서브워드(subword) 단위 토큰화 방법이 있다. 단어 단위 토크나이제이션은 텍스트를 단어별로 나누는 방식이며, 주로 띄어쓰기를 기준으로 구분된다. 형태소 단위 토크나이제이션은 단어의 의미적 최소 단위인 형태소로 분할하는 방식으로, 한국어처럼 형태소 분석이 중요한 언어에 유리하다. 문자 단위 토크나이제이션은 텍스트를 한 글자씩 분할하는 방식으로, 주로 아주 짧거나 구조적인 텍스트에서 사용된다. 서브워드 단위 토크나이제이션은 단어보다 작지만 의미를 가진 단위로 나누는 방식으로, BPE(Byte Pair Encoding)WordPiece와 같은 기법이 대표적이다. 이는 특히 새로운 단어에 대해 유연하게 대처할 수 있어 최근 널리 사용되고 있다.

토크나이제이션은 단순히 텍스트를 분리하는 작업이지만, 언어적 특성과 모델의 목적에 따라 그 방법이 달라질 수 있다. 예를 들어, 영어와 같이 띄어쓰기가 명확한 언어는 단어 단위 토크나이제이션이 비교적 간단하지만, 한국어와 같이 교착어적인 특성을 가진 언어는 형태소 분석이 중요한 요소가 된다. 또한, 딥러닝 기반의 언어 모델에서는 서브워드 단위 토크나이제이션이 자주 사용되는데, 이는 드문 단어나 미지의 단어에 대해 더 나은 일반화를 가능하게 하기 때문이다.

토크나이제이션의 결과는 이후의 자연어 처리 과정에서 매우 중요한 영향을 미친다. 각 토큰은 모델이 이해할 수 있는 형태로 변환되어야 하며, 이를 임베딩(embedding) 과정에서 벡터로 표현하게 된다. 따라서 좋은 토크나이제이션은 언어의 의미와 구조를 잘 반영하면서도 모델의 효율성을 높이는 방향으로 이루어져야 한다. 토크나이제이션은 자연어 처리의 첫 단계이자 매우 중요한 기초 작업으로, 이를 통해 텍스트 데이터의 의미를 최대한 유지하면서도 기계가 처리할 수 있는 형태로 변환하게 된다.

Tokenizing with KorQuAD

KorQuAD 데이터셋을 예시로 허깅페이스 AutoTokenizer를 이용해 토큰화과정을 진행한다.
KorQuAD 데이터셋이 궁금하다면, KorQuAD 데이터셋은 뭘까?에서 확인할 수 있다.

Tokenizer 불러오기

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("KLUE/roberta-large")

Only 토큰화

[Input]

example_text = dataset["train"][0]['question']
tokenized_txt = tokenizer.tokenize(example_text)
print(tokenized_txt)

[output]

>> ['바그너','##는', '괴테', '##의', '파우', '##스트', '##를', '읽', '##고', '무엇', '##을', '쓰', '##고', '##자', '했', '##는', '##가', '?']

'바그너는 괴테의 파우스트를 읽고 무엇을 쓰고자 했는가?' 라는 문장을 위와 같이 토크나이징하였다.

토큰화 & 인덱싱 & 어텐션 마스킹

[Input]

tokens = tokenizer(example_text)
print(tokens)

[output]

>> {'input_ids': [101, 9318, 78136, 70162, 11018, 8905, 119351, 10459, 9901, 89108, 101825, 9642, 11664, 9294, 119137, 10622, 9511, 11664, 13764, 9965, 11018, 11287, 136, 102], 
	'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
    'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

단일 문장이기 때문에 token_type_ids은 모두 0으로, Encoder-Only 모델일 경우에는 attention_mask가 모두 1로 설정된다.

Decode Tokens

[Input]

original_text = tokenizer.decode(tokenizer(example_text)['input_ids'])
print(original_text)

[output]

>> [CLS] 바그너는 괴테의 파우스트를 읽고 무엇을 쓰고자 했는가? [SEP]

Tokenizer가 자동으로 CLS 토큰과 SEP 토큰을 추가해주는 것을 알 수 있다.

입력 토큰이 너무 길다면

데이터 둘러보기

[Input]

examples = dataset['train'][:1]
print(examples)

[output]

>> {'id': ['6566495-0-0'], 
	'title': ['파우스트_서곡'], 
    'context': ['1839년 바그너는 괴테의 파우스트을 처음 읽고 그 내용에 마음이 끌려 이를 소재로 해서 하나의 교향곡을 쓰려는 뜻을 갖는다. 이 시기 바그너는 1838년에 빛 독촉으로 산전수전을 다 걲은 상황이라 좌절과 실망에 가득했으며 메피스토펠레스를 만나는 파우스트의 심경에 공감했다고 한다. 또한 파리에서 아브네크의 지휘로 파리 음악원 관현악단이 연주하는 베토벤의 교향곡 9번을 듣고 깊은 감명을 받았는데, 이것이 이듬해 1월에 파우스트의 서곡으로 쓰여진 이 작품에 조금이라도 영향을 끼쳤으리라는 것은 의심할 여지가 없다. 여기의 라단조 조성의 경우에도 그의 전기에 적혀 있는 것처럼 단순한 정신적 피로나 실의가 반영된 것이 아니라 베토벤의 합창교향곡 조성의 영향을 받은 것을 볼 수 있다. 그렇게 교향곡 작곡을 1839년부터 40년에 걸쳐 파리에서 착수했으나 1악장을 쓴 뒤에 중단했다. 또한 작품의 완성과 동시에 그는 이 서곡(1악장)을 파리 음악원의 연주회에서 연주할 파트보까지 준비하였으나, 실제로는 이루어지지는 않았다. 결국 초연은 4년 반이 지난 후에 드레스덴에서 연주되었고 재연도 이루어졌지만, 이후에 그대로 방치되고 말았다. 그 사이에 그는 리엔치와 방황하는 네덜란드인을 완성하고 탄호이저에도 착수하는 등 분주한 시간을 보냈는데, 그런 바쁜 생활이 이 곡을 잊게 한 것이 아닌가 하는 의견도 있다.'], 
    'question': ['바그너는 괴테의 파우스트를 읽고 무엇을 쓰고자 했는가?'], 
    'answers': [{'text': ['교향곡'], 'answer_start': [54]}]}

복수의 데이터는 각 feature의 list로 주어진다.

토큰화하기

모델이 긴 문맥에서 답을 찾을 때, 원본 텍스트의 연속성을 유지하기 위해 문장을 여러 시퀀스로 나누게 된다. 이 과정에서 "질문(question)"과 "문맥(context)"을 한 번에 토큰화하며, 토큰의 총 길이가 설정된 최대 길이(max_length)를 초과하면 "문맥" 부분만 잘라낸다. 이 방법을 only_second라 하며, 잘라낸 토큰들은 return_overflowing_tokens 옵션을 통해 별도의 시퀀스로 반환된다.

stride는 두 시퀀스가 자연스럽게 이어지도록 일정 부분을 겹치게 만드는 역할을 한다. 이렇게 하면 답이 두 시퀀스의 경계에 걸려 있더라도 겹친 부분을 통해 문맥의 흐름이 이어져, 답을 찾기 쉬워진다.

[Input]

tokenized_examples = tokenizer(
        examples["question"],
        examples["context"],
        truncation="only_second",  # Truncate to max_length. This will only truncate the second sequence of a pair.
        max_length=250, # The maximum length of the combined string of the question, context, and special tokens.
        stride=50, # The length of the overlapping sequence when the context is split because it's too long.
        return_overflowing_tokens=True, # Whether or not to return overflowing token sequences.
        return_offsets_mapping=True,  # Whether or not to return (char_start, char_end) for each token.
        padding="max_length",
    )
    
print(tokenized_examples)

[output]

>> {'input_ids': [[0, 27982, 2259, 21310, 2079, 11994, 3791, 2138, ...], 
					[0, 27982, 2259, 21310, 2079, 3791, ..., 1, 1, 1, 1]], 
    'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...], 
    					[0, 0, 0, ..., 1, 1, 1, 1, 1, 1, ..., 0, 0, 0, 0]], 
    'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...], 
    					[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ..., 0, 0, 0, 0, 0, 0]], 
    'offset_mapping': [[(0, 0), (0, 3), (3, 4), (5, 7), (7, 8), (8, 10), ...], 
    					[(0, 0), (0, 3), (3, 4), (7, 8), ..., (0, 0), (0, 0)]], 
    'overflow_to_sample_mapping': [0, 0]}

input_ids: 두번째 시퀀스의 마지막 빈공간은 pading token으로 대체되었다.
token_type_ids: 앞부분의 "question"과 padding은 0으로 "context"는 1로 설정하여 두 가지 문장타입 사이에 seperate token을 배치할 수 있도록 하였다.
attention_mask: padding token은 attention 대상에서 제외한다.
offset_mapping: 토큰이 원본 텍스트 내에서 차지하는 위치
overflow_to_sample_mapping: 여러 시퀀스가 하나의 문맥에서 나왔음을 나타낸다.

profile
생각, 기술, 회고 등 다양한 분야를 기록합니다.

0개의 댓글