번역 과정부터 Subsequent Mask까지 한 번에 이해하기
Transformer를 처음 공부할 때 Encoder, Decoder, Self-Attention, Mask 같은 개념이 한꺼번에 등장합니다.
특히 다음과 같은 코드를 보면
np.triu(...)
또는
scores.masked_fill(mask == 0, -1e9)
왜 이런 연산이 필요한지 바로 이해하기 어렵습니다.
사실 Mask를 이해하려면 먼저 Transformer가 번역 작업을 어떤 방식으로 수행하는지 이해해야 합니다.
이번 글에서는 다음 순서로 살펴보겠습니다.
입력 문장
↓
Encoder
↓
문장의 의미 표현
↓
Decoder
↓
출력 문장을 한 단어씩 생성
↓
미래 단어를 미리 보면 안 됨
↓
Mask 필요
1. Transformer는 원래 번역 모델로 등장
Transformer는 2017년 논문 Attention Is All You Need에서 제안되었습니다.
대표적인 예시는 기계 번역입니다.
예를 들어 영어 문장을 한국어로 번역한다고 가정해보겠습니다.
입력 문장
I love apples.
우리가 원하는 출력은 다음과 같습니다.
나는 사과를 좋아한다.
Transformer에서는 이 과정을 크게 두 부분으로 나눕니다.
Encoder
Decoder
간단하게 표현하면,
Encoder = 입력 문장을 이해하는 부분
Decoder = 출력 문장을 생성하는 부분
이라고 볼 수 있습니다.
2. 전체 번역 흐름부터 보기
Transformer의 번역 과정을 아주 단순화하면 다음과 같습니다.
I love apples
↓
Encoder
↓
입력 문장의 의미 정보
↓
Decoder
↓
나는 사과를 좋아한다
하지만 Decoder가
나는 사과를 좋아한다
라는 문장을 한 번에 통째로 만드는 것은 아닙니다.
실제로는 한 토큰씩 순차적으로 생성합니다.
예를 들어 다음과 같습니다.
<SOS>
↓
나는
↓
사과를
↓
좋아한다
↓
<EOS>
여기서 <SOS>는 문장의 시작을 의미하고,
<EOS>
는 문장의 끝을 의미합니다.
이 구조를 이해하면 Decoder Mask가 왜 필요한지도 자연스럽게 이해할 수 있습니다.
3. Encoder는 무엇을 하는가?
먼저 Encoder부터 살펴보겠습니다.
입력 문장이
I love apples
라고 하겠습니다.
문장은 먼저 Token으로 나뉩니다.
I
love
apples
각 단어는 Embedding Vector로 변환됩니다.
I → vector
love → vector
apples → vector
그리고 Encoder 내부의 Self-Attention을 통해 각 단어가 다른 단어들을 참고합니다.
예를 들어 love라는 단어를 이해할 때
I
love
apples
전체를 함께 참고할 수 있습니다.
즉,
love → I
love → apples
와 같은 관계를 학습할 수 있습니다.
4. Encoder의 Self-Attention
Encoder에서는 기본적으로 문장 전체를 이미 알고 있습니다.
입력 문장이
I love apples
이면 세 단어가 모두 동시에 주어진 상태입니다.
따라서 I가 love와 apples를 볼 수도 있고,
I → love
I → apples
apples 역시 앞에 있는 단어를 볼 수 있습니다.
apples → I
apples → love
즉 일반적인 Encoder Self-Attention에서는 다음과 같이 문장 전체를 참고할 수 있습니다.
Key
I love apples
Q I O O O
u love O O O
e apples O O O
r
y
여기서
O = Attention 가능
입니다.
Encoder의 목적은 출력 문장을 직접 생성하는 것이 아니라,
입력 문장의 문맥과 의미를 풍부한 Vector 형태로 표현하는 것입니다.
5. Encoder의 출력은 무엇인가?
Encoder를 통과한 후에는 각 입력 Token에 대한 문맥적 표현이 만들어집니다.
단순하게 표현하면 다음과 같습니다.
I → h1
love → h2
apples → h3
하지만 여기서 h1, h2, h3는 단순한 단어 Embedding이 아닙니다.
Self-Attention을 거쳤기 때문에 각각 주변 단어의 정보까지 포함합니다.
예를 들어 love의 표현에는
누가 사랑하는지 → I
무엇을 사랑하는지 → apples
같은 문맥 정보가 함께 들어갈 수 있습니다.
이 Encoder의 출력은 이후 Decoder가 번역 문장을 만들 때 사용됩니다.
6. Decoder는 무엇을 하는가?
이제 Decoder를 살펴보겠습니다.
Decoder의 역할은 Encoder가 이해한 입력 문장을 바탕으로 출력 문장을 생성하는 것입니다.
예를 들어 영어 문장
I love apples
를 한국어로 번역한다고 하겠습니다.
Decoder는 다음과 같이 동작합니다.
처음에는
<SOS>
만 입력됩니다.
그리고 첫 번째 단어를 예측합니다.
<SOS>
↓
나는
다음에는
<SOS> 나는
를 이용해서 다음 단어를 예측합니다.
<SOS> 나는
↓
사과를
그다음에는
<SOS> 나는 사과를
을 보고 다음 단어를 예측합니다.
<SOS> 나는 사과를
↓
좋아한다
마지막으로
<EOS>
를 생성하면 문장 생성이 끝납니다.
7. Decoder는 이전 단어를 이용해서 다음 단어를 예측
이 부분이 매우 중요합니다.
Decoder는 다음과 같이 작동합니다.
현재까지 생성된 단어
↓
다음 단어 예측
예를 들어,
나는
까지 있다면 다음 단어를 예측하고,
나는 사과를
까지 있다면 그다음 단어를 예측합니다.
즉 Decoder는 Autoregressive 방식으로 동작합니다.
이전 출력 → 다음 출력
구조입니다.
8. Decoder에는 두 종류의 Attention이 있다
Transformer Decoder에는 중요한 Attention이 두 종류 있습니다.
1. Masked Self-Attention
2. Encoder-Decoder Attention
두 개를 구분하는 것이 중요합니다.
9. Decoder의 Masked Self-Attention
첫 번째는 Decoder 내부에서 출력 문장끼리 수행하는 Self-Attention입니다.
예를 들어 번역 문장이
나는 사과를 좋아한다
라고 하겠습니다.
좋아한다를 예측할 때 Decoder는 이미 앞에서 생성한
나는
사과를
을 참고할 수 있습니다.
즉,
좋아한다 → 나는
좋아한다 → 사과를
와 같은 관계를 볼 수 있습니다.
하지만 중요한 제한이 하나 있습니다.
미래 단어를 미리 보면 안 됩니다.
이 문제 때문에 Mask가 등장합니다.
10. Encoder-Decoder Attention
Decoder에는 Encoder의 정보를 참고하는 Attention도 있습니다.
이를 흔히
Cross-Attention
또는
Encoder-Decoder Attention
이라고 합니다.
예를 들어 Decoder가
사과를
이라는 단어를 생성하려고 할 때 Encoder의
I
love
apples
중에서
apples
에 강하게 Attention을 줄 수 있습니다.
개념적으로 보면
Decoder Query
↓
Encoder의 Key / Value
구조입니다.
예를 들어,
사과를
↓
apples
라는 강한 관계를 학습할 수 있습니다.
11. Encoder와 Decoder를 함께 보면
전체 구조를 단순화하면 다음과 같습니다.
입력
I love apples
│
▼
┌──────┐
│ Encoder │
└──────┘
│
│ 문맥 정보
▼
┌──────┐
│ Decoder │
└──────┘
│
▼
나는 → 사과를 → 좋아한다
Decoder는 두 종류의 정보를 사용합니다.
① 지금까지 생성된 출력 단어
② Encoder가 만든 입력 문장의 정보
즉 다음 단어를 만들 때
이전에 생성한 한국어 단어
+
영어 원문의 의미
를 함께 참고합니다.
12. 그렇다면 왜 Mask가 필요한가?
이제 Mask가 필요한 이유를 살펴보겠습니다.
훈련할 때는 정답 문장을 이미 가지고 있습니다.
예를 들어 정답이
나는 사과를 좋아한다
라고 하겠습니다.
학습 효율을 위해 Decoder에 정답 문장의 여러 위치를 한꺼번에 넣어 계산할 수 있습니다.
그런데 아무런 제한 없이 Self-Attention을 하면 문제가 생깁니다.
예를 들어 나는 위치가
사과를
좋아한다
까지 미리 볼 수 있게 됩니다.
즉,
나는 → 미래 단어
를 참고하게 됩니다.
이것은 실제 생성 상황과 맞지 않습니다.
실제 추론 시점에서는 첫 단어를 생성할 때 미래의 정답을 알 수 없기 때문입니다.
13. 시험에서 답을 미리 보는 것과 같다
쉽게 비유하면 시험과 비슷합니다.
다음 문장을 완성한다고 하겠습니다.
나는 사과를 ______.
정답은
좋아한다
입니다.
그런데 모델이 문제를 풀기 전에 이미
정답: 좋아한다
를 볼 수 있다면 제대로 학습했다고 볼 수 없습니다.
Decoder 역시 마찬가지입니다.
현재 단어를 예측할 때
과거
현재
정보는 볼 수 있지만,
미래
정보는 보면 안 됩니다.
따라서 미래 영역을 가리는 장치가 필요합니다.
이것이 바로 Mask입니다.
14. Causal Mask / Subsequent Mask
Decoder에서 미래 정보를 차단하는 Mask를 보통 다음과 같이 부릅니다.
Causal Mask
Subsequent Mask
Look-Ahead Mask
이름은 조금 다르지만 핵심 목적은 같습니다.
현재 위치에서 미래 Token을 보지 못하게 하는 것입니다.
예를 들어 Token이 5개라면 다음과 같은 Mask를 만들 수 있습니다.
1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
1 1 1 1 0
1 1 1 1 1
여기서
1 = 볼 수 있음
0 = 볼 수 없음
이라고 하겠습니다.
15. Mask를 문장으로 이해하기
예를 들어 Decoder 입력이 다음과 같다고 하겠습니다.
<SOS> 나는 사과를 좋아한다
각 위치는 다음 범위까지만 볼 수 있습니다.
<SOS>
→ <SOS>
나는
→ <SOS>, 나는
사과를
→ <SOS>, 나는, 사과를
좋아한다
→ <SOS>, 나는, 사과를, 좋아한다
즉 각 위치는 자기 자신과 과거 위치까지만 볼 수 있습니다.
미래 위치는 차단됩니다.
표로 표현하면 다음과 같습니다.
Key
1 2 3 4 5
Query 1 O X X X X
2 O O X X X
3 O O O X X
4 O O O O X
5 O O O O O
이 삼각형 형태가 바로 Causal Mask의 핵심입니다.
16. np.triu()는 왜 등장하는가?
이제 다음 코드가 왜 등장하는지 이해할 수 있습니다.
np.triu(...)
NumPy의 np.triu()는 행렬에서 Upper Triangle, 즉 상삼각 부분을 남기는 함수입니다.
예를 들어
import numpy as np
arr = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
])
이 있다고 하겠습니다.
17. k=0
print(np.triu(arr, k=0))결과는 다음과 같습니다.
1 2 3
0 5 6
0 0 9
즉 주대각선을 포함한 위쪽 영역을 남깁니다.
18. k=1
Transformer Mask에서 특히 중요한 것은
k=1
입니다.
np.triu(arr, k=1)
을 사용하면
0 2 3
0 0 6
0 0 0
이 됩니다.
즉 현재 위치인 주대각선은 제외하고 미래 위치만 선택합니다.
이것이 Decoder Mask와 정확하게 연결됩니다.
현재 위치보다 오른쪽
=
미래 Token
이기 때문입니다.
19. Subsequent Mask 만들기
이제 실제 코드를 살펴보겠습니다.
import numpy as np
import torch
def subsequent_mask(size):
attn_shape = (1, size, size)
subsequent_mask = np.triu(
np.ones(attn_shape),
k=1
).astype('uint8')
return torch.from_numpy(
1 - subsequent_mask
)
예를 들어
size = 5
라고 하겠습니다.
20. 먼저 모든 값을 1로 만든다
np.ones((1, 5, 5))
결과는 다음과 같습니다.
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
21. np.triu(..., k=1)로 미래 위치만 선택한다
np.triu(
np.ones((1, 5, 5)),
k=1
)
결과는 다음과 같습니다.
0 1 1 1 1
0 0 1 1 1
0 0 0 1 1
0 0 0 0 1
0 0 0 0 0
현재 상태에서
1 = 미래 위치
0 = 현재 또는 과거 위치
입니다.
즉 np.triu(..., k=1)은
“어디가 미래인지 찾아내는 역할”
을 합니다.
22. 왜 1 - mask를 하는가?
다음 코드가 있습니다.
1 - subsequent_mask
기존 Mask가
0 1 1 1 1
0 0 1 1 1
0 0 0 1 1
0 0 0 0 1
0 0 0 0 0
이라면 값을 뒤집어서
1 0 0 0 0
1 1 0 0 0
1 1 1 0 0
1 1 1 1 0
1 1 1 1 1
로 만듭니다.
이제 의미가 다음처럼 바뀝니다.
1 = Attention 가능
0 = Attention 차단
훨씬 직관적인 Mask가 되었습니다.
23. 실제 Attention Score에 Mask 적용하기
Self-Attention에서는 기본적으로 다음 값을 계산합니다.
QKᵀ
그리고 Scale을 적용합니다.
QKᵀ / √d_k
이 값이 Attention Score입니다.
예를 들어 Score가 다음과 같다고 하겠습니다.
import torch
scores = torch.tensor([
[2.1, 3.5, 1.2, 4.3]
])
Mask가
mask = torch.tensor([
[1, 1, 0, 0]
])
이라면 현재 위치에서 앞의 두 Token만 볼 수 있다는 의미입니다.
24. masked_fill() 적용
PyTorch에서는 다음과 같이 Mask를 적용할 수 있습니다.
masked_scores = scores.masked_fill(
mask == 0,
-1e9
)
결과는 대략 다음과 같습니다.
2.1
3.5
-1000000000
-1000000000
즉 미래 위치의 Score를 매우 작은 값으로 바꿉니다.
25. 왜 -1e9를 사용하는가?
Attention Score 다음에는 Softmax를 적용합니다.
softmax(score)
Softmax에서는 매우 작은 음수 값을 넣으면 확률이 거의 0이 됩니다.
즉
softmax(-∞) ≈ 0
입니다.
따라서 미래 Token의 Attention 확률을 사실상 0으로 만들 수 있습니다.
예를 들어
2.1
3.5
-1e9
-1e9
에 Softmax를 적용하면 대략
0.1978
0.8022
0
0
처럼 됩니다.
즉 미래 두 위치는 완전히 무시됩니다.
핵심은 미래 Token을 실제로 행렬에서 삭제하는 것이 아니라,
Attention Score를 극단적으로 낮춰 Softmax 이후 확률을 0으로 만드는 것입니다.
26. Encoder에는 이런 Causal Mask가 필요 없을까?
일반적인 Transformer Encoder에서는 Decoder와 같은 Causal Mask가 필요하지 않습니다.
왜냐하면 Encoder는 입력 문장 전체를 이미 알고 있기 때문입니다.
예를 들어
I love apples
라는 문장이 주어지면 Encoder는 처음부터
I
love
apples
를 모두 볼 수 있습니다.
따라서 다음처럼 전체 Attention이 가능합니다.
O O O
O O O
O O O
반면 Decoder에서는
O X X
O O X
O O O
처럼 미래를 차단해야 합니다.
27. 하지만 Encoder에도 다른 종류의 Mask는 있다
여기서 주의할 점이 있습니다.
Encoder에 Mask가 전혀 없는 것은 아닙니다.
문장을 Batch로 처리할 때 길이가 다르면 보통 <PAD> Token을 추가합니다.
예를 들어
I love apples <PAD> <PAD>
처럼 만들 수 있습니다.
이때 <PAD>는 실제 문장 정보가 아니므로 Attention에서 제외해야 합니다.
이를 위해 사용하는 것이
Padding Mask
입니다.
즉 Transformer에서는 Mask를 크게 두 종류로 볼 수 있습니다.
Padding Mask
→ PAD Token을 무시
Causal Mask
→ Decoder가 미래 Token을 보지 못하게 함
둘은 목적이 다릅니다.
28. Encoder와 Decoder의 Mask 차이
간단히 정리하면 다음과 같습니다.
| 위치 | Mask | 목적 |
|---|---|---|
| Encoder Self-Attention | Padding Mask | PAD Token 무시 |
| Decoder Self-Attention | Padding Mask + Causal Mask | PAD 무시 + 미래 Token 차단 |
| Encoder-Decoder Attention | 주로 Encoder Padding Mask | Encoder의 PAD 위치 무시 |
특히 우리가 np.triu()로 만드는 Mask는
Decoder Self-Attention의 Causal Mask
에 해당합니다.
29. 학습할 때와 실제 번역할 때
Mask를 이해하려면 학습과 추론의 차이도 알아두면 좋습니다.
Training
훈련할 때는 정답 문장이 이미 존재합니다.
나는 사과를 좋아한다
하지만 모든 위치를 동시에 계산하면서도 각 위치가 미래 정답을 보지 못하도록 Mask를 사용합니다.
즉 병렬 계산은 하되,
정보 흐름은
과거 → 현재
까지만 허용합니다.
Inference
실제로 번역할 때는 정답 문장이 없습니다.
따라서 Decoder는 처음에 <SOS> 토큰을 입력으로 받고, 이후에는 이전에 생성된 토큰들을 기반으로 하나씩 다음 단어를 생성합니다.
<SOS>
↓
나는
↓
나는 사과를
↓
나는 사과를 좋아한다
↓
<EOS>
Input은 “모델에 들어가는 이전 토큰 시퀀스(누적된 입력)”이고, Output은 “그 입력을 바탕으로 모델이 예측하는 다음 토큰”입니다.
즉 실제 생성 과정은 다음과 같이 누적적으로 이루어집니다.
Step 1: input: <SOS> → output: 나는
Step 2: input: <SOS> 나는 → output: 사과를
Step 3: input: <SOS> 나는 사과를 → output: 좋아한다
Step 4: input: <SOS> 나는 사과를 좋아한다 → output: <EOS>
이처럼 Decoder는 매 단계마다 전체 이전 토큰 시퀀스를 입력으로 사용하여 다음 토큰을 예측합니다.
30. Transformer 번역 흐름 전체 정리
이제 전체 과정을 한 번에 연결해보겠습니다.
입력 문장:
I love apples
STEP 1. Encoder 입력
I / love / apples
각 Token을 Embedding으로 바꾸고 Self-Attention을 수행합니다.
STEP 2. Encoder가 문맥을 이해
I
love
apples
각 단어가 서로를 참고하면서 문맥적 표현을 만듭니다.
STEP 3. Decoder 시작
<SOS>
에서 시작합니다.
STEP 4. Decoder Masked Self-Attention
현재까지 생성된 단어끼리 Self-Attention을 수행합니다.
단,
미래 Token은 볼 수 없음
이라는 제한이 있습니다.
STEP 5. Cross-Attention
Decoder는 Encoder의 출력도 참고합니다.
Decoder
↓
Encoder 정보
예를 들어
사과를 ↔ apples
와 같은 관계를 학습할 수 있습니다.
STEP 6. 다음 Token 예측
Decoder가 다음 단어의 확률을 계산합니다.
나는
사과를
좋아한다
...
중 가장 적절한 Token을 선택합니다.
STEP 7. <EOS>까지 반복
<SOS>
→ 나는
→ 사과를
→ 좋아한다
→ <EOS>
문장 생성이 완료됩니다.
31. Mask를 이해하는 가장 중요한 그림
Decoder Self-Attention을 한 장으로 표현하면 다음과 같습니다.
볼 수 있는 범위
Token 1 ● × × × ×
Token 2 ● ● × × ×
Token 3 ● ● ● × ×
Token 4 ● ● ● ● ×
Token 5 ● ● ● ● ●
↑
미래는 차단
즉,
현재 Token
↓
과거 + 자기 자신은 볼 수 있음
미래는 볼 수 없음
입니다.
32. 한 줄 핵심 정리
Transformer 번역에서
Encoder
는 입력 문장 전체를 읽고 의미를 이해하고,
Decoder
는 Encoder의 정보를 참고하면서 출력 문장을 한 Token씩 생성합니다.
그리고 Decoder가 학습 중 미래의 정답 Token을 미리 보지 못하게 하기 위해
Causal Mask
를 사용합니다.
그 Mask를 만들 때
np.triu(
np.ones((1, size, size)),
k=1
)
은 미래 위치를 찾아내는 과정이고,
1 - subsequent_mask
는 이를 뒤집어
1 = 현재/과거 → 볼 수 있음
0 = 미래 → 볼 수 없음
형태의 Mask를 만드는 과정입니다.
결국 Transformer의 Mask는 단순한 행렬 연산이 아니라,
Decoder가 실제 문장 생성 방식과 동일하게 “과거만 보고 미래를 예측하도록” 만드는 핵심 장치입니다.

