3장. Attention 과 Transformer, GPT 직접 구현
토큰과 토크나이저
택배 상자에 물건을 담을 때, 큰 물건은 나눠 담고 작은 물건은 모아 담습니다. 모델도 글을 통째로 받지 못해서 알맞은 크기의 조각으로 잘라 받습니다. 이 조각이 토큰이고, 자르는 도구가 토크나이저입니다.
"배송이 늦어요"를 세 가지 크기로 잘랐습니다. 왼쪽 글자 단위는 조각의 종류가 적지만 글이 길어지고, 가운데 낱말 단위는 조각이 적지만 처음 보는 낱말이 생기면 다루지 못합니다. 오른쪽 서브워드 단위는 그 사이입니다. 여기서 물건이 글, 상자가 토큰입니다.
세 가지 자르는 법
| 단위 | "배송이 늦어요" | 어휘 크기 | 좋은 점 | 아쉬운 점 |
|---|---|---|---|---|
| 글자 | 배 / 송 / 이 / 빈칸 / 늦 / 어 / 요 | 작다(수백~수천) | 모르는 글자가 거의 없다 | 글이 길어져 계산이 많다 |
| 낱말 | 배송이 / 늦어요 | 아주 크다 | 글이 짧다 | 처음 보는 낱말을 못 다룬다 |
| 서브워드 | 배 / 송이 / 늦 / 어요 | 중간(수만) | 둘의 장점을 섞었다 | 자르는 규칙을 따로 배워야 한다 |
실습 1~4의 미니 GPT 는 이해하기 쉬운 글자 단위를 쓰고, 실습 5의 번역 모델은 서브워드 단위를 씁니다.
글자 토크나이저 만들기
글자 단위 토크나이저는 파이썬 사전 두 개면 됩니다.
text = "배송이 늦어요"
chars = sorted(set(text)) # 나오는 글자를 모아 정렬한 것이 어휘
stoi = {c: i for i, c in enumerate(chars)} # 글자 → 번호
itos = {i: c for c, i in stoi.items()} # 번호 → 글자
ids = [stoi[c] for c in text] # 인코딩: 글을 번호 목록으로
print(chars)
print(ids)
print("".join(itos[i] for i in ids)) # 디코딩: 번호를 다시 글로어휘는 [' ', '늦', '배', '송', '어', '요', '이'] 일곱 글자이고, 문장은 [2, 3, 6, 0, 1, 4, 5] 가 됩니다. 번호를 다시 글자로 바꾸면 원래 문장이 그대로 돌아옵니다. 번호의 크기에는 뜻이 없습니다. "늦"이 1, "배"가 2 인 것은 정렬 순서일 뿐입니다.
코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
sorted(set(text)) | 글에 나오는 글자를 겹치지 않게 모아 정렬한다. 이 목록이 토크나이저의 어휘다 |
stoi | "string to integer"의 줄임말이다. 글자를 넣으면 번호가 나오는 사전이다 |
itos | "integer to string"의 줄임말이다. stoi 를 뒤집어 번호를 넣으면 글자가 나오는 사전이다 |
ids = [stoi[c] for c in text] | 인코딩이다. 글을 모델이 받을 번호 목록으로 바꾼다 |
"".join(itos[i] for i in ids) | 디코딩이다. 번호 목록을 다시 글로 되돌린다 |
이 코드에는 PyTorch 가 나오지 않습니다. 토크나이저는 신경망이 아니라 글과 번호를 오가는 번호표일 뿐입니다. stoi, itos 라는 이름은 실습 1과 실습 3, 4에서도 그대로 씁니다.
주의할 점
- 같은 글이라도 토크나이저마다 토큰 수가 다릅니다. LLM 의 요금과 입력 길이 제한은 글자 수가 아니라 토큰 수로 셉니다.
- 학습에 쓴 토크나이저와 쓸 때의 토크나이저는 반드시 같아야 합니다. 번호표가 바뀌면 모델은 엉뚱한 글자를 읽습니다.