4장. Transformer Decoder와 작은 번역기
학습과 추론은 다르게 움직인다
학교 받아쓰기 연습과 실제 받아쓰기 시험을 견주어 봅니다. 연습(학습)에서는 선생님이 정답 문장을 다 알고 있으니, 문제지 한 장에 "1번: 첫 낱말은? 2번: 저는 다음은? …"을 한꺼번에 내고 동시에 채점합니다. 시험(추론)에서는 정답이 없습니다. 학생은 첫 낱말을 쓰고, 자기가 쓴 것을 보며 둘째 낱말을 씁니다. 앞 낱말을 쓰기 전에는 뒤 낱말을 쓸 수 없으니 차례로 갈 수밖에 없습니다.
그림 위쪽은 학습입니다. "안전 점검 보고서를 요약해 줘" 다섯 토큰이 이미 다 있으므로, 5×5 점수표에 가림판을 씌워 다섯 위치(Q₁~Q₅)를 한 번에 계산합니다. 각 행에서 "참고" 칸은 자기와 앞쪽, "MASK" 칸은 뒤쪽입니다. 그림 아래쪽은 학습(순환 대기 없음)과 생성(다음 토큰을 기다림)의 차이를 적은 상자입니다.
학습과 추론 견주기
| 구분 | 학습(Training) | 추론(Inference) |
|---|---|---|
| Decoder 입력 | 정답 문장을 한 칸 민 것(Teacher Forcing) | 지금까지 모델이 생성한 토큰 |
| 계산 방식 | 모든 위치를 한 번에 병렬로(마스크 사용) | 한 토큰씩 반복해 생성 |
| 끝나는 때 | 정답 길이만큼 | <끝> 이 나오거나 최대 길이에 닿을 때 |
| 이 장의 코드 | translation_trainer.train() | translation_model.generate(...) |
| 받아쓰기로 치면 | 정답을 보며 연습, 한꺼번에 채점 | 혼자서 한 낱말씩 받아쓰기 |
그렇다면 학습에서 Causal Mask 는 왜 필요할까요? 다섯 위치를 한 번에 계산하면 정답 문장 전체가 입력에 들어 있습니다. 가리지 않으면 위치 2가 위치 3의 정답을 미리 보고 베낄 수 있습니다. 그러면 학습 손실은 낮아도 실제 생성에서는 아무것도 못 하는 모델이 됩니다. 반대로 실제 생성은 왜 한 토큰씩일까요? 다음 입력에는 방금 고른 토큰이 들어가야 하는데, 그 토큰을 고르기 전에는 그다음을 계산할 재료가 없기 때문입니다.
짧은 예시
# 다 배운 모델을 "앞 낱말 → 다음 낱말" 사전으로 흉내 낸다(설명용 장난감)
next_word = {"<시작>": "저는", "저는": "학생", "학생": "입니다", "입니다": "<끝>"}
generated = ["<시작>"] # 추론은 시작 토큰 하나에서 출발한다
max_new_tokens = 10 # 끝 토큰이 안 나와도 여기서 멈춘다
for step in range(max_new_tokens):
# 모델은 지금까지 만든 것(generated)을 보고 다음 낱말 하나를 고른다
word = next_word[generated[-1]]
generated.append(word) # 고른 낱말을 입력 뒤에 붙인다(자기회귀)
print(step + 1, generated)
if word == "<끝>": # 종료 토큰이 나오면 멈춘다
break
# 결과:
# 1 ['<시작>', '저는']
# 2 ['<시작>', '저는', '학생']
# 3 ['<시작>', '저는', '학생', '입니다']
# 4 ['<시작>', '저는', '학생', '입니다', '<끝>']이 코드가 뜻하는 것
정답지 없이 혼자 받아쓰는 시험을 흉내 낸 코드입니다. 한 번 돌 때마다 낱말 하나를 고르고, 그 낱말을 붙인 줄로 다음 바퀴를 돕니다. 여기서 학생의 머릿속이 next_word, 답안지가 generated, 시험 시간 제한이 max_new_tokens 입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
generated = ["<시작>"] | 시작 토큰에서 출발 | 빈 답안지에 첫 칸만 |
next_word[generated[-1]] | 다음 낱말 고르기 | 방금 쓴 낱말을 보고 다음을 씀 |
generated.append(word) | 고른 낱말을 붙이기 | 쓴 것을 다시 읽을 거리로 |
if word == "<끝>": break | 종료 토큰에서 멈춤 | 마침표 찍고 손 들기 |
학습 때는 네 위치를 한 번에 계산했지만, 여기서는 네 바퀴를 돌아야 합니다. 실습 3의 generate() 가 이 반복을 대신합니다. 실제 모델은 사전이 아니라 Decoder 전체를 한 바퀴마다 다시 계산합니다.
주의할 점
- 추론에서 앞 낱말을 잘못 고르면 그 실수가 다음 입력이 되어 뒤까지 번질 수 있습니다. 학습 때는 늘 정답을 받았기 때문에, 모델은 이런 상황을 연습한 적이 없습니다.
- 생성은 토큰 수만큼 반복하므로 긴 답일수록 오래 걸립니다.
max_new_tokens로 상한을 둡니다.