Post

[Transformer 원리] Day 1: 토큰화와 임베딩 - 텍스트를 벡터로 바꾸기

[Transformer 원리] Day 1: 토큰화와 임베딩 - 텍스트를 벡터로 바꾸기

이 글은 AI(Claude)의 도움을 받아 작성하고, 작성자가 검토·편집했습니다.

서론: LLM의 안을 들여다보기

ChatGPT 같은 LLM은 마법처럼 보이지만, 내부는 명확한 수학 연산의 연속이다. 그 핵심 구조가 2017년 “Attention Is All You Need”에서 나온 Transformer다. 이 시리즈는 토큰화부터 추론 최적화까지, Transformer가 텍스트를 처리하는 전 과정을 5일에 걸쳐 따라간다. 첫날은 모든 것의 출발점 — 텍스트를 모델이 다룰 수 있는 숫자로 바꾸는 단계다.

1. 왜 토큰화가 필요한가

신경망은 텍스트가 아니라 숫자만 다룬다. 텍스트를 어떻게 쪼개 숫자로 만들지가 첫 결정이다.

1
2
3
4
5
6
7
8
9
방법 1: 문자 단위 ("h","e","l","l","o")
  ✅ 어휘 작음  ❌ 시퀀스가 너무 길어짐, 의미 단위 약함

방법 2: 단어 단위 ("hello", "world")
  ✅ 의미 단위  ❌ 어휘 폭발(수백만 단어), 신조어·오타 처리 불가

방법 3: 서브워드 (BPE 등) ← 현대 LLM의 선택
  "tokenization" → "token" + "ization"
  ✅ 어휘 크기와 표현력의 균형, 미등록 단어도 조각으로 처리

2. BPE (Byte Pair Encoding)

가장 널리 쓰이는 서브워드 알고리즘. 자주 함께 나오는 바이트 쌍을 반복 병합한다.

1
2
3
4
5
6
7
8
학습 과정:
  초기: 모든 문자를 개별 토큰으로
  반복: 가장 빈번한 인접 쌍을 하나로 병합
    "l","o","w" ... "lo"가 자주 나옴 → "lo" 병합
    "lo","w" → "low" 병합 ...
  → 빈도 높은 단어는 통째로, 드문 단어는 조각으로 남음

결과 어휘(vocab): 보통 3만~10만 개 토큰
1
2
3
4
5
6
7
8
9
# tiktoken으로 실제 토큰화 확인 (OpenAI 계열)
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")

ids = enc.encode("Transformer는 강력하다")
print(ids)               # [12345, 678, ...] 정수 ID 시퀀스
print([enc.decode([i]) for i in ids])  # 각 ID가 어떤 조각인지

# 영어는 보통 1단어≈1토큰, 한국어는 글자당 여러 토큰이 되기도

토큰 수가 곧 비용·컨텍스트 한도와 직결되므로, 토큰화 효율은 실무에서 중요하다.

3. 토큰 ID에서 임베딩 벡터로

토큰 ID는 그저 사전의 색인 번호일 뿐, 의미가 없다. 임베딩이 각 ID를 의미를 담은 고차원 벡터로 바꾼다.

1
2
3
4
5
6
7
임베딩 행렬: [어휘 크기 V] × [차원 d]
  예: V=100,000, d=4096
  토큰 ID 12345 → 행렬의 12345번째 행 = 4096차원 벡터

이 벡터들은 학습으로 결정됨:
  의미가 비슷한 토큰("king", "queen")은 벡터 공간에서 가까워짐
  유명한 관계: vec("king") - vec("man") + vec("woman") ≈ vec("queen")
1
2
3
4
import torch.nn as nn
embedding = nn.Embedding(num_embeddings=100_000, embedding_dim=4096)
# 토큰 ID 텐서 → 임베딩 벡터 (학습 가능한 파라미터)
vectors = embedding(token_ids)   # shape: [시퀀스 길이, 4096]

4. 순서 정보가 사라지는 문제

Transformer의 어텐션(Day 2)은 토큰들을 동시에 본다. RNN처럼 순서대로 읽지 않는다. 그래서 “고양이가 개를 쫓다”와 “개가 고양이를 쫓다”를 구분하지 못한다 — 순서 정보가 없기 때문이다.

1
2
3
해법: 위치 정보를 임베딩에 더한다 (Positional Encoding)
  최종 입력 = 토큰 임베딩 + 위치 임베딩
  → 같은 단어라도 위치가 다르면 다른 벡터가 됨

5. 위치 인코딩의 방식들

1
2
3
4
5
6
7
8
9
10
1. 사인파 위치 인코딩 (원논문):
   위치마다 서로 다른 주파수의 sin/cos 값을 부여
   ✅ 학습 불필요, 임의 길이로 외삽 가능

2. 학습된 위치 임베딩 (BERT, GPT 초기):
   위치도 학습 파라미터로 → 단순하나 최대 길이 고정

3. RoPE (회전 위치 인코딩) — 현대 LLM의 대세:
   벡터를 위치에 따라 회전시켜 "상대적 위치"를 자연스럽게 인코딩
   ✅ 긴 컨텍스트 확장에 유리 (LLaMA, 최신 모델 다수가 채택)
1
2
3
4
5
6
7
8
# 사인파 위치 인코딩의 핵심 아이디어
import numpy as np
def positional_encoding(pos, d):
    pe = np.zeros(d)
    for i in range(0, d, 2):
        pe[i]   = np.sin(pos / 10000 ** (i / d))
        pe[i+1] = np.cos(pos / 10000 ** (i / d))
    return pe   # 각 위치가 고유한 패턴을 가짐

6. 정리: 입력 파이프라인

1
2
3
4
5
6
7
8
9
"안녕하세요" (텍스트)
   ↓ 토큰화 (BPE)
[15496, 23821] (토큰 ID)
   ↓ 임베딩 룩업
[[0.2, -0.5, ...], [0.8, 0.1, ...]] (의미 벡터, d=4096)
   ↓ + 위치 인코딩
[[0.3, -0.4, ...], [0.9, 0.3, ...]] (위치까지 담긴 최종 입력)
   ↓
[Transformer 블록들] ← Day 2~3

이 최종 벡터 시퀀스가 어텐션 레이어로 들어간다. “텍스트 → 의미와 위치를 담은 벡터”가 Day 1의 전부다.

7. Day 1 체크리스트

  1. 신경망이 숫자만 다루므로 토큰화가 첫 단계임을 이해했다.
  2. 서브워드(BPE)가 어휘 크기와 표현력의 균형점인 이유를 안다.
  3. 토큰 ID가 임베딩 행렬을 통해 의미 벡터로 변환됨을 파악했다.
  4. 어텐션이 순서를 모르기에 위치 인코딩이 필요함을 이해했다.
  5. 사인파·학습·RoPE 위치 인코딩의 차이를 구분했다.

다음 편 예고

입력 벡터가 준비됐다. 이제 Transformer의 심장이 등장한다. Day 2에서는 셀프 어텐션(self-attention) — 각 토큰이 다른 모든 토큰을 “주목”해 문맥을 파악하는 메커니즘을 Query·Key·Value로 풀어낸다.

This post is licensed under CC BY 4.0 by the author.