자연어 처리를 위해서는 토큰화(Tokenization)를 이해해야 한다. 문장을 기계가 이해할 수 있는 형태로 바꾸기 위해서는 먼저 문장을 잘게 쪼개는 작업이 필요하다.
이 글에서는 토큰화의 개념, 목적, 다양한 기법들을 정리해 보았다. 실질적으로 어떤 방식으로 기계가 텍스트를 다루는지 이해하기 위한 흐름에 중점을 두고 작성하였다.
토큰화는 텍스트를 의미 있는 단위로 나누는 작업이다.
이 단위를 '토큰(token)'이라고 한다. 일반적으로는 단어 수준으로 나누지만 때로는 형태소, 음절, 심지어 문자 수준까지 세분화하기도 한다. 기계 학습 모델이 문장을 처리할 수 있게 만드는 첫 번째 전처리 단계로서, 이후 임베딩, 분류, 번역, 생성 등의 작업을 위해 필수적으로 수행된다.
예를 들어, "그녀는 나와 밥을 먹는다" 라는 문장이 주어졌을 때
같은 문장이라도 어떤 기준으로 나누느냐에 따라 단어 수는 달라진다.
즉, 토큰의 정의는 토큰화 방식에 따라 결정된다.
이처럼 토큰화는 텍스트를 해석하고 처리하는 방식을 좌우하고, 문장 속 의미 단위의 경계를 설정하는 중요한 작업이다.
자연어는 비정형적이며 복잡한 구조를 가진다. 사람이 사용하는 언어는 문법적 오류, 은유, 생략, 반복, 신조어 등 다양한 변형이 가능하고, 의미도 문맥에 따라 달라진다. 따라서 기계가 이를 이해하려면 먼저 문장을 일정한 규칙에 따라 잘게 나누어 구조화된 형태로 바꾸는 과정이 필요하다.
토큰화는 단어의 경계, 문법적 구조, 의미적 관계 등을 파악하는 기반이 된다. 특히, 통계적 기법이나 딥러닝 모델에서는 토큰화된 단위가 임베딩의 기본 단위가 되기 때문에, 성능에 직접적인 영향을 미친다.
OOV 문제에 대해서는 뒤에서 간단하게 다뤄보았다.
가장 기본적인 방식으로, .split() 함수를 이용해 단어를 공백 기준으로 분리한다. 영어처럼 띄어쓰기가 명확한 언어에서는 비교적 잘 작동하지만, 영어조차도 구두점이나 복합어 처리에 한계가 있다.
corpus = "in the days that followed i learned to spell ..."
tokens = corpus.split()
print("Tokens:", tokens)
간단하게 토큰화를 수행할 수 있다는 장점이 있지만,
형태 변화(e.g., day vs days), 구두점 처리, 복합 단어 인식 실패, 의미 단위 단절 등의 문제가 존재한다.
한국어는 교착어이자 형태소 변화가 많은 언어다. 그렇기 때문에 공백 기준 토큰화로는 조사나 어미 분리가 어려워 엉뚱한 결과가 나온다.
형태소는 '의미를 가지는 최소 단위'이며 이를 단위로 쪼개는 것이 형태소 기반 토큰화이다.
e.g., 오늘도 공부만 한다 → [오늘, 도, 공부, 만, 한다]
주요 도구: KoNLPy
from konlpy.tag import Mecab
mecab = Mecab()
print(mecab.pos("자연어처리는 매우 흥미로운 분야입니다."))
분석기 선택 기준
공백 기반 또는 형태소 기반 방식은 미리 정의된 단어 사전에 기반하여 토큰을 생성한다. 하지만 현실의 데이터는 새로운 단어, 신조어, 외래어가 끊임없이 등장하므로 사전에 없는 단어는 <unk>(unknown token)으로 치환하게 된다.
"코로나바이러스는 2019년 12월 중국 우한에서 처음 발생한 뒤..."
→ "<unk>는 2019년 12월 중국 <unk>에서..."
<unk> 토큰으로 치환되는 상황은 모델이 단어를 인식하지 못하게 되어, 의미 손실이나 예측 오류를 일으킬 수 있다. 이를 OOV(Out-Of-Vocabulary) 문제라고 한다.
OOV 문제는 모델이 학습할 때는 본 적이 없는 단어나 문장이 테스트 데이터에 등장하여 발생하는 문제로, 핵심 단어가 치환되면 문장의 의미 전달은 실패하게 된다.
이러한 OOV 문제를 완화하기 위해 단어를 더 작은 의미 단위로 분해하는 Subword 기반 접근법이 제안되었다. 대표적인 Subword 기반 접근에는 WordPiece Model과 SentencePiece가 있다.
Subword는 단어보다 작은 단위로 구성된 의미 단위를 뜻한다.
Subword는 주로 의미 있는 접두사, 어근, 접미사 등의 조합으로 구성되며, 희귀 단어도 하위 단위로 분해해 처리함으로써 OOV 문제를 효과적으로 완화할 수 있다.
예를 들어, unhappiness라는 단어를 un, happi, ness와 같이 더 작은 단위로 분해할 수 있다. 이처럼 전체 단어가 아니라 단어의 일부분(sub)만으로도 의미를 구성할 수 있기 때문에 Subword 단위로 텍스트를 다루면 희귀 단어를 효과적으로 처리할 수 있다.
Subword 기반 토큰화는 사전에 없던 단어도 이미 학습된 하위 단위의 조합으로 표현할 수 있게 해 주어 OOV 문제 해결에 매우 효과적이다.
BPE는 원래는 데이터 압축을 위해 만들어진 방식이다. 자주 등장하는 문자 쌍을 하나의 단위로 묶어 사전을 구성하는 방식인데 이를 자연어처리에 적용하면 단어를 더 작은 의미 단위로 나눠 처리할 수 있고, OOV 문제를 줄일 수 있다.
아래의 간단한 예시로 BPE 알고리즘을 이해할 수 있다.
aaabdaaabac
→ ZabdZabac (Z=aa)
→ ZYdZYac (Y=ab)
→ XdXac (X=ZY)
BPE를 통해 희귀 단어를 문자 단위로 분해하여 처리할 수 있고, 토큰 조합만으로 대부분의 단어 표현 가능하다는 점도 큰 이점으로 작용한다. 또한, 병합 횟수에 따라 vocabulary 크기를 조절할 수 있어 Subword 기반 모델이 사용하는 사전을 효율적으로 설계하는 데 활용된다.
(레포 링크)
아래 논문에서 제공해 주는 예제로 동작 방식을 구현해 보았다.
Google에서 제안한 WordPiece는 BPE의 변형으로 BERT, RoBERTa 등에서 사용된다. 단어 조합의 가능도(likelihood)를 기반으로 가장 자연스러운 형태의 토큰을 만든다는 점에서 더 향상된 접근이다.
_ 기호로 표시하여 토큰 경계를 명시함_you, _are, _teacherWordPiece는 특히 조사, 어미 변화가 심한 한국어나 일본어에서 매우 효과적이다. 형태소 분석기 없이도 높은 정확도를 보이고, 언어 독립적인 접근이라는 점에서 장점이 크다.
SentencePiece는 전처리 없이 원시 문장을 그대로 학습하여 subword 토큰을 생성하는 방식이다.
SentencePiece는 띄어쓰기를 유지하지 않고, 공백을 특수 문자 ▁로 치환해 문장 구조를 보존하며 다국어와 비정형 텍스트에 강하다. 특히 한국어, 일본어 등에서도 별도의 형태소 분석기 없이 사용할 수 있다.
soynlp는 비지도 학습 기반으로 단어 경계를 인식하는 한국어 특화 토크나이저다. 단어의 다음 글자가 등장할 확률을 계산해 단어 경계를 추정하고, 사전이 필요 없다.
예를 들어 보면
트, 트와, 트와이, 트와이스
각각 다음 글자의 등장 확률을 비교하여 최적 경계를 결정하게 된다.
지금까지 토큰화가 무엇인지와 그 필요성으로 시작하여 토큰화의 다양한 방식과 한국어에 특화된 접근까지 정리해 보았다.
토큰화는 문장을 기계가 이해할 수 있는 단위로 나누는 과정이고, 방식에 따라 분석 결과가 완전히 달라질 수 있다는 것을 알 수 있었다.
자연어처리는 사람의 언어를 기계가 이해할 수 있도록 바꾸는 작업이인데, 토큰화를 어떻게 처리하느냐에 따라 이후 모든 모델의 성능과 방향성이 달라질 수 있게 된다.
결국 완벽한 토크나이저는 없고, 목적과 언어에 맞는 선택이 중요하겠다.