STEP 08 / 18

디코더: 원문을 보면서, 미래는 보지 않고

그림 오른쪽이 디코더다. 인코더 층과 다른 점은 서브층이 하나 더 있다는 것이다.

  1. masked self-attention: 지금까지 만든 번역 토큰끼리 본다. tgt_mask로 미래를 가린다.
  2. source attention(encoder-decoder attention): query는 디코더에서, key와 value는 인코더 출력 memory에서 가져온다. "다음 단어를 고르려면 원문의 어디를 봐야 하나?"를 계산하는 자리다. 코드에서 self.src_attn(x, m, m, src_mask)다.
  3. FFN

Decoder는 Encoder와 똑같이 N개를 쌓고 마지막에 LayerNorm을 건다. 차이는 memory, src_mask, tgt_mask를 함께 넘긴다는 것뿐이다.

실행 결과에서 볼 것

  • 디코더 층은 서브층이 3개라 인코더보다 파라미터가 많다(약 2,520만 개).
  • 4, 5번째 입력 토큰만 바꾸면 1~3번째 출력은 차이가 정확히 0이다. 미래를 볼 수 없다는 뜻이다.
  • 마스크를 빼면 1번째 출력까지 바뀐다. 마스크가 없으면 모델이 뒤에 올 정답을 엿보고 학습하게 된다.

코드 변화

+33 −0 · 파일 전체 214줄

이전 단계의 transformer.py와 비교했다. 초록 줄이 새로 생긴 줄, 빨간 줄이 사라진 줄이다.

GitHub에서 보기 ↗
179179 for layer in self.layers:
180180 x = layer(x, mask)
181181 return self.norm(x)
182+
183+
184+class DecoderLayer(nn.Module):
185+ "Decoder is made of self-attn, src-attn, and feed forward (defined below)"
186+
187+ def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
188+ super(DecoderLayer, self).__init__()
189+ self.size = size
190+ self.self_attn = self_attn
191+ self.src_attn = src_attn
192+ self.feed_forward = feed_forward
193+ self.sublayer = clones(SublayerConnection(size, dropout), 3)
194+
195+ def forward(self, x, memory, src_mask, tgt_mask):
196+ "Follow Figure 1 (right) for connections."
197+ m = memory
198+ x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))
199+ x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))
200+ return self.sublayer[2](x, self.feed_forward)
201+
202+
203+class Decoder(nn.Module):
204+ "Generic N layer decoder with masking."
205+
206+ def __init__(self, layer, N):
207+ super(Decoder, self).__init__()
208+ self.layers = clones(layer, N)
209+ self.norm = LayerNorm(layer.size)
210+
211+ def forward(self, x, memory, src_mask, tgt_mask):
212+ for layer in self.layers:
213+ x = layer(x, memory, src_mask, tgt_mask)
214+ return self.norm(x)

실행 결과

실행한 코드 demo.py 보기
1import copy
2
3import torch
4
5from transformer import (
6 Decoder, DecoderLayer, MultiHeadedAttention, PositionwiseFeedForward, subsequent_mask,
7)
8
9torch.manual_seed(0)
10
11d_model, N = 512, 6
12c = copy.deepcopy
13attn = MultiHeadedAttention(8, d_model)
14ff = PositionwiseFeedForward(d_model, 2048, 0.1)
15decoder = Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), 0.1), N).eval()
16
17print(f"디코더 층 {len(decoder.layers)}개, 층마다 서브층 {len(decoder.layers[0].sublayer)}개 (self-attn, src-attn, FFN)")
18print(f"디코더 파라미터 수: {sum(p.numel() for p in decoder.parameters()):,} ← 어텐션이 하나 더 있어 인코더보다 크다")
19print()
20
21memory = torch.randn(1, 7, d_model) # 인코더 출력 (원문 토큰 7개)
22src_mask = torch.ones(1, 1, 7, dtype=torch.bool)
23y = torch.randn(1, 5, d_model) # 지금까지 만든 번역 토큰 5개
24out = decoder(y, memory, src_mask, subsequent_mask(5))
25print("디코더 입력", tuple(y.shape), "+ memory", tuple(memory.shape), "→ 출력", tuple(out.shape))
26print()
27
28print("뒤쪽 토큰(4, 5번째)만 바꾸면 앞쪽 출력은?")
29y2 = y.clone()
30y2[:, 3:] = torch.randn(1, 2, d_model)
31out2 = decoder(y2, memory, src_mask, subsequent_mask(5))
32diff = (out - out2).abs().amax(dim=(0, 2))
33for i, d in enumerate(diff.tolist()):
34 print(f" {i + 1}번째 토큰 출력 차이: {d:.1e}")
35print(" → 미래를 가리는 마스크 덕분에 앞 3개는 전혀 바뀌지 않는다")
36print()
37
38print("마스크 없이 돌리면?")
39out3 = decoder(y, memory, src_mask, None)
40out4 = decoder(y2, memory, src_mask, None)
41print(f" 1번째 토큰 출력 차이: {(out3 - out4)[:, 0].abs().max().item():.1e} ← 미래를 엿봤다")
디코더 층 6개, 층마다 서브층 3개 (self-attn, src-attn, FFN)
디코더 파라미터 수: 25,225,216  ← 어텐션이 하나 더 있어 인코더보다 크다

디코더 입력 (1, 5, 512) + memory (1, 7, 512) → 출력 (1, 5, 512)

뒤쪽 토큰(4, 5번째)만 바꾸면 앞쪽 출력은?
  1번째 토큰 출력 차이: 0.0e+00
  2번째 토큰 출력 차이: 0.0e+00
  3번째 토큰 출력 차이: 0.0e+00
  4번째 토큰 출력 차이: 4.1e+00
  5번째 토큰 출력 차이: 3.3e+00
  → 미래를 가리는 마스크 덕분에 앞 3개는 전혀 바뀌지 않는다

마스크 없이 돌리면?
  1번째 토큰 출력 차이: 5.8e-01  ← 미래를 엿봤다