4장. Transformer Decoder와 작은 번역기
작은 영한 번역기의 설계도
요리 교실에서 처음부터 빵을 굽는다고 해 봅시다. 재료(문장쌍)를 계량하고, 반죽 크기(모델 크기)를 정하고, 오븐 온도와 시간(학습률과 epoch)을 맞춥니다. 이 장의 실습은 미리 구운 빵(사전학습 모델)을 사지 않고 밀가루부터 시작합니다. 그래서 맛(번역 품질)은 소박하지만, Decoder 가 어떻게 배우는지는 처음부터 끝까지 볼 수 있습니다.
그림의 다섯 걸음이 실습 1~3의 다섯 셀입니다. 데이터는 강사가 직접 만들어 Hugging Face Hub 에 공개한 영한 문장쌍(strongminsu/ko-en-structured-translations)입니다. 영어 en 이 입력, 한국어 ko 가 정답입니다. 다섯 셀은 변수를 차례로 넘겨받으므로 같은 노트북에서 위에서부터 실행합니다.
데이터 나누기
| 분할 | 문장쌍 수 | 쓰임 | 시험으로 치면 |
|---|---|---|---|
| train | 7,594 | 가중치 학습 | 문제집 |
| validation | 949 | 학습 중 손실 확인 | 모의고사 |
| test | 950 | 학습 뒤 새 문장 확인 | 실전 시험 |
모델 크기 손으로 세기
| 부분 | 계산 | 개수 |
|---|---|---|
| 단어 표(임베딩, 출력 Linear 와 함께 씀) | 61,790 × 256 | 15,818,240 |
| 위치 표(Encoder·Decoder 각각) | 32 × 256 × 2 | 16,384 |
| Encoder 층 하나 | Attention 263,168 + FFN 262,912 + Norm 1,024 | 527,104 |
| Decoder 층 하나 | Encoder 층 + Cross-Attention 263,168 + Norm 512 | 790,784 |
| 합계 | 15,818,240 + 16,384 + 527,104 × 2 + 790,784 × 2 | 18,470,400 |
Attention 하나는 Q·K·V·출력의 네 행렬이 각각 256 × 256 + 256(덧셈 칸)이므로 4 × 65,792 = 263,168 개입니다. FFN 은 256 → 512 → 256 이라 (256 × 512 + 512) + (512 × 256 + 256) = 262,912 개입니다. 합계 18,470,400 은 실습 2의 출력과 정확히 같습니다. 이 중 단어 표가 약 86% 를 차지합니다. 작은 모델일수록 사전 크기가 무게를 좌우합니다.
짧은 예시
import math
train_pairs = 7594 # 학습 문장쌍 수
batch_size = 32 # 한 단계에 처리하는 문장쌍 수
epochs = 10 # 문제집을 몇 바퀴 도는지
# math.ceil = 올림. 마지막 묶음은 32개보다 적어도 한 단계로 센다
steps_per_epoch = math.ceil(train_pairs / batch_size)
print("한 바퀴 단계 수:", steps_per_epoch) # 결과: 한 바퀴 단계 수: 238
print("전체 단계 수:", steps_per_epoch * epochs) # 결과: 전체 단계 수: 2380
vocab = 61790 # 토크나이저의 어휘 크기
# 아무것도 모를 때(모든 토큰에 같은 확률) 손실은 ln(어휘 크기)
print("처음 손실의 눈높이:", round(math.log(vocab), 2)) # 결과: 처음 손실의 눈높이: 11.03
# 손실 L 은 "e^L 개 후보 중에서 헤매는 정도"로 읽을 수 있다
print("검증 손실 3.19 →", round(math.exp(3.19), 1), "개 후보") # 결과: 검증 손실 3.19 → 24.3 개 후보이 코드가 뜻하는 것
학습을 돌리기 전에 "몇 걸음 걷게 될지"와 "손실 숫자가 얼마면 잘한 것인지"를 미리 가늠하는 코드입니다. 여기서 문제집 쪽수가 steps_per_epoch, 찍기만 할 때의 벌점이 math.log(vocab) 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
math.ceil(train_pairs / batch_size) | 7,594 ÷ 32 = 237.3 을 올림 | 문제집 한 바퀴는 238쪽 |
steps_per_epoch * epochs | 238 × 10 | 실습 2 출력의 2380/2380 |
math.log(vocab) | ln 61,790 ≈ 11.03 | 6만 개 중 아무거나 찍을 때의 벌점 |
math.exp(3.19) | e^3.19 ≈ 24.3 | 후보 24개 정도로 좁힌 셈 |
실습 2의 학습 표에서 첫 epoch 검증 손실은 4.80(후보 약 122개), 마지막은 3.19(후보 약 24개)입니다. 6만 개에서 24개 정도로 좁혔으니 많이 배웠지만, 아직 하나로 딱 고를 만큼은 아닙니다. 그래서 번역이 어색합니다.
주의할 점
- 실린 학습 출력은 RTX 3080 GPU 에서 약 52초 걸렸습니다. GPU 가 없어도 같은 코드가 CPU 로 돌지만 훨씬 오래 걸립니다(수십 분 이상). 수업 중에는
num_train_epochs를 줄여 먼저 흐름만 보아도 됩니다. - 손실과 번역 결과는 실행할 때마다, GPU 마다 조금씩 달라집니다. 이 교안의 숫자는 이 PC 에서 한 번 돌린 값입니다.