자연어를 벡터로: 텍스트 전처리, Subword 토큰화, 벡터화
모델은 문자를 직접 다루지 못합니다. 문장이 모델에 들어가려면 전처리 → 토큰화 → 벡터화를 거쳐 숫자 벡터가 되어야 합니다. 이 글에서는 그 과정을 단계별로 정리합니다.
1. 신경망 기초
MLP (Multi-Layer Perceptron)
MLP는 다음 과정을 층마다 반복합니다.
- 입력값과 가중치를 곱합니다.
- 곱한 값을 모두 더하고 편향(bias)을 더합니다.
- 활성화 함수를 적용합니다.
- 가중치: 층과 층 사이의 연결 강도이며, 모델이 학습하는 파라미터입니다.
- 활성화 함수: 입력을 다음 층으로 얼마나 전달할지 결정하는 비선형 함수입니다.
역전파 (Backpropagation)
출력값과 실제 레이블의 차이(Loss)를 줄이기 위해, 각 파라미터가 Loss에 미친 영향(기울기)을 연쇄법칙으로 출력층부터 거꾸로 계산합니다. 계산된 기울기로 파라미터를 실제로 수정하는 것은 옵티마이저(경사하강법 등)의 역할입니다.
2. 텍스트 전처리
전처리는 일반적으로 다음 순서로 진행됩니다.
수집 → cleaning → normalization → tokenization → 불용어 제거 → 정수 인코딩 → padding / truncation → batch 구성
- corpus: 학습에 사용하는 텍스트 데이터 묶음입니다.
- 불용어 제거는 문장을 단어 단위로 나눈 뒤에 수행합니다. 나누어져 있어야 어떤 단어가 불용어인지 판단할 수 있기 때문입니다.
단어사전 (Vocabulary)
단어사전은 모델이 사용할 토큰을 미리 등록해 둔 목록입니다. 여기서 두 단계를 구분해야 합니다.
- 토큰화: 문장을 토큰으로 나눕니다. →
나는 / 밥을 / 먹었다 - 정수 인코딩: 토큰을 사전 번호로 바꿉니다. →
나는→1024,밥을→17,먹었다→542
세상의 모든 단어를 사전에 담을 수는 없으므로, 사전의 크기와 구성을 잘 정하는 것이 중요합니다. 최근에는 형태소보다 subword 단위로 나누는 방식이 주로 쓰입니다.
3. Subword 토큰화
Subword 토큰화는 단어를 더 작은 단위로 나누되, 자주 등장하는 패턴은 한 덩어리로 유지합니다.
- 처음 보는 단어도 이미 아는 조각들의 조합으로 표현할 수 있습니다.
- 이를 통해 OOV(Out of Vocabulary, 사전에 없는 단어) 문제를 완화합니다.
- 예:
안녕하세요 → 안녕 / 하 / 세 / 요
대표적인 방법은 BPE, WordPiece, Unigram LM 세 가지입니다.
BPE (Byte Pair Encoding)
자주 함께 등장하는 문자 쌍을 반복해서 합쳐 토큰을 만듭니다.
- 모든 단어를 문자 단위로 분해합니다.
- 가장 자주 등장하는 인접 쌍을 찾습니다.
- 그 쌍을 하나의 토큰으로 병합합니다.
- 원하는 사전 크기에 도달할 때까지 2~3을 반복합니다.
WordPiece (bottom-up)
BPE처럼 쌍을 병합하지만, 기준이 빈도가 아니라 합쳤을 때 데이터 전체의 확률(likelihood)을 가장 크게 높이는 쌍입니다.
score(a, b) = freq(ab) / (freq(a) × freq(b))
모든 쌍의 score를 계산해 가장 높은 쌍을 병합합니다. 각각 따로 나올 때보다 함께 나올 때 의미가 큰 쌍이 우선 선택됩니다.
Unigram Language Model (top-down)
앞의 두 방법과 반대로, 큰 후보 사전에서 시작해 불필요한 토큰을 지워 나갑니다.
- 단어를 나누는 여러 방법을 확률로 계산합니다.
lower → P(low)P(er),P(l)P(o)P(w)P(e)P(r), …
- 지워도 전체 손실이 가장 적게 늘어나는 토큰, 즉 덜 중요한 토큰부터 제거합니다.
- 원하는 사전 크기가 될 때까지 반복합니다.
WordPiece vs Unigram LM: 새 문장을 나누는 방식
학습이 끝난 뒤 새 문장을 분할할 때도 두 방법은 다르게 동작합니다.
| WordPiece | Unigram LM | |
|---|---|---|
| 방법 | Greedy longest-match-first: 왼쪽부터 사전에 있는 가장 긴 토큰을 잘라냄 | Viterbi: 확률 곱이 최대인 분할을 찾음 |
| 결과 | 항상 하나로 정해진 분할 | 가능한 분할이 여러 개이며 각각 확률을 가짐 |
| 샘플링 | 불가 | Subword regularization 가능: 학습 중 분할을 확률적으로 바꿔 데이터 증강 효과를 냄 |
4. 벡터화 (Vectorization)
토큰을 벡터로 바꾸는 방법은 크게 네 가지로 정리할 수 있습니다.
| 방법 | 핵심 아이디어 | 의미 관계 표현 |
|---|---|---|
| BoW | 단어 등장 횟수 | ✕ |
| TF-IDF | 등장 횟수 + 단어 중요도 가중치 | ✕ |
| Word2Vec | 주변 단어로 의미 학습 | ○ |
| Embedding | 학습 가능한 벡터 테이블 | ○ |
BoW (Bag of Words)
문장을 단어별 등장 횟수로 표현합니다.
- 문장:
나는 밥을 먹었는데 너는 밥을 먹었니? - 단어 순서:
[나는, 밥을, 먹었는데, 너는, 먹었니] - 벡터:
[1, 2, 1, 1, 1](밥을만 2회 등장)
벡터의 각 자리는 단어 하나에 대응하고, 값은 그 단어가 등장한 횟수입니다. 단순하지만 단어 순서가 사라지고 의미 관계를 담지 못한다는 한계가 있습니다.
TF-IDF
한 문서 안에서 어떤 단어가 얼마나 중요한지를 나타내는 가중치입니다.
- TF: 한 문서에 자주 나오는 단어는 그 문서에서 중요합니다.
- IDF: 하지만 모든 문서에 흔하게 나오는 단어는 중요하지 않습니다.
값이 클수록 중요한 단어이며, 원래 범위가 01로 정해져 있지는 않습니다(L2 정규화를 적용하면 01 사이가 됩니다). BoW와 마찬가지로 단어 사이의 의미 관계는 표현하지 못합니다.
Word2Vec
"단어의 의미는 주변 단어가 결정한다"는 가정에서 출발합니다. 비슷한 문맥에 등장하는 단어는 비슷한 벡터를 갖게 됩니다.
vec(왕) − vec(남자) + vec(여자) ≈ vec(여왕)
vec(서울) − vec(한국) + vec(일본) ≈ vec(도쿄)
성별이나 수도 같은 관계가 벡터 공간에서 일정한 방향으로 나타납니다. 단어 임베딩을 학습하는 초기의 대표적인 방법이며, CBOW와 Skip-gram 두 가지 방식이 있습니다.
Embedding
자연어 → 토큰 → 정수 인덱스 → 임베딩 테이블 lookup → 벡터
정수 인덱스로 임베딩 테이블에서 해당 행을 꺼내 벡터로 사용합니다. 이 테이블 값도 모델과 함께 학습되는 파라미터입니다.
헷갈리기 쉬운 점
- 불용어 제거는 토큰화 다음 단계입니다.
- 토큰에 번호를 붙이는 것은 토큰화가 아니라 정수 인코딩입니다.
- 역전파는 기울기를 계산하고, 파라미터 수정은 옵티마이저가 담당합니다.
- WordPiece는 score가 가장 높은 쌍을 병합합니다.
- Unigram은 지워도 손실이 적게 늘어나는 토큰부터 제거합니다.
RAG와의 연결
- 청크 크기를 토큰 수로 정하는 이유: 모델은 글자가 아닌 토큰 단위로 입력을 받기 때문입니다.
- 한국어와 subword: 한국어는 조사와 어미가 붙어 단어 형태가 매우 다양합니다. 사전에 없는 단어가 많이 생기므로 subword 토큰화가 특히 중요합니다.
- 하이브리드 검색: TF-IDF/BoW는 키워드 검색(BM25)의 기반이고, Embedding은 벡터 검색의 기반입니다. 두 방식을 함께 쓰는 것이 하이브리드 검색입니다.