Masking: 트랜스포머의 정보 차단 전략
Padding Mask와 Causal Mask(Look-Ahead Mask)가 어텐션 점수에 어떻게 적용되는지, 인코더와 디코더 각 위치에서 어떤 마스크를 사용하는지 코드와 함께 설명한다.
지난 글에서 Encoder-Decoder 전체 구조와 Cross-Attention이 소스와 타깃을 연결하는 방식을 살펴봤다. 트랜스포머의 어텐션이 올바르게 동작하려면 어느 위치의 정보를 볼 수 없게 막아야 하는지를 명시적으로 지정해야 한다. 이것이 마스킹(Masking)이다.
마스킹이 필요한 두 가지 이유
- 패딩 무시: 배치 처리를 위해 짧은 시퀀스를
<PAD>로 채우지만, 패딩 토큰은 의미가 없으므로 어텐션 계산에서 제외해야 한다. - 미래 차단: 디코더가 다음 토큰을 예측할 때 아직 생성되지 않은 토큰의 정보를 미리 보면 안 된다.
마스킹 메커니즘
어텐션 점수(Q·K^T / √d_k)에 마스크 값을 더한 뒤 Softmax를 적용한다. 차단할 위치에 −∞를 더하면 softmax(-∞) ≈ 0이 되어 해당 위치의 어텐션 가중치가 0에 수렴한다.
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V, mask=None):
d_k = Q.size(-1)
scores = (Q @ K.transpose(-2, -1)) / (d_k ** 0.5)
if mask is not None:
scores = scores + mask # -inf 덧셈
attn = F.softmax(scores, dim=-1)
return attn @ V, attn
Padding Mask
배치 내 각 시퀀스의 <PAD> 위치를 표시하는 불리언 텐서다. True인 열(Key)에 해당하는 어텐션 점수가 −∞가 된다.
def make_pad_mask(seq: torch.Tensor, pad_idx: int = 0) -> torch.Tensor:
# seq: (batch, seq_len)
# 반환: (batch, seq_len) — True이면 PAD
return seq == pad_idx
PyTorch의 nn.MultiheadAttention에서는 key_padding_mask 파라미터로 전달한다. True인 위치를 −∞로 처리한다.
Causal Mask (Look-Ahead Mask)
디코더의 Masked Self-Attention에서 위치 i가 i+1 이후를 볼 수 없게 막는 상삼각 행렬이다.
def make_causal_mask(sz: int) -> torch.Tensor:
# 상삼각(diagonal=1 이상)이 -inf, 하삼각이 0인 행렬
return torch.triu(
torch.full((sz, sz), float('-inf')),
diagonal=1,
)
# 예: sz=4
# [[ 0, -inf, -inf, -inf],
# [ 0, 0, -inf, -inf],
# [ 0, 0, 0, -inf],
# [ 0, 0, 0, 0]]
nn.MultiheadAttention에서 attn_mask 파라미터로 전달한다.
두 마스크의 시각적 비교
어디서 어떤 마스크를 쓰는가
| 위치 | Padding Mask | Causal Mask |
|---|---|---|
| Encoder Self-Attention | ✓ | ✗ |
| Decoder Masked Self-Attention | ✓ | ✓ |
| Decoder Cross-Attention | ✓ (소스 PAD) | ✗ |
| Decoder-only (GPT류) 생성 | ✓ | ✓ |
두 마스크를 동시에 적용할 때는 합산(mask = pad_mask + causal_mask)하거나, nn.Transformer의 tgt_mask와 tgt_key_padding_mask 파라미터에 각각 전달한다.
Teacher Forcing 학습에서의 마스킹
학습 시 정답 시퀀스 전체를 디코더에 한 번에 입력하더라도, Causal Mask 덕분에 위치 i의 예측에 i+1 이후 정답이 새어들지 않는다. 이것이 Teacher Forcing이 올바르게 동작하는 핵심 조건이다.
# 학습 루프에서의 마스크 생성 예시
tgt_len = tgt.size(1)
causal = make_causal_mask(tgt_len).to(device) # (tgt_len, tgt_len)
src_pad_mask = make_pad_mask(src).to(device) # (batch, src_len)
tgt_pad_mask = make_pad_mask(tgt[:, :-1]).to(device) # (batch, tgt_len)
logits = model(src, tgt[:, :-1],
tgt_mask=causal,
src_key_padding_mask=src_pad_mask,
tgt_key_padding_mask=tgt_pad_mask)
loss = F.cross_entropy(
logits.reshape(-1, vocab_size),
tgt[:, 1:].reshape(-1),
ignore_index=PAD_IDX,
)
정리
- Padding Mask:
<PAD>토큰을 어텐션에서 제외. 인코더·디코더 모두 사용. - Causal Mask: 미래 토큰 참조를 차단. 디코더 Self-Attention에서만 사용.
- 두 마스크 모두 −∞ 덧셈 + Softmax 조합으로 구현.
- 올바른 마스크 적용이 Teacher Forcing 학습과 자기 회귀 추론의 정확성을 보장.
지난 글: Encoder-Decoder 구조: 번역에서 요약까지
다음 글: BERT: 양방향 사전학습 언어 모델의 등장
읽어주셔서 감사합니다. 😊