STEP 09 / 18

하나로 조립: EncoderDecoder와 make_model

원본 노트북이 맨 처음에 보여 주는 코드가 이 단계의 EncoderDecoder다. 원본을 위에서부터 읽으면 아직 아무 부품도 모르는 상태에서 이 클래스를 만나서 어디부터 봐야 할지 막막하다. 여기서는 부품을 다 만든 뒤에 만나므로 각 줄이 무엇을 부르는지 이미 알고 있다.

  • EncoderDecoder: 부품 다섯 개(encoder, decoder, src_embed, tgt_embed, generator)를 들고 있는 껍데기다. encode로 원문을 memory로 만들고, decode로 memory와 지금까지의 번역문을 디코더에 넣는다.
  • Generator: 디코더 출력(512차원)을 사전 크기로 투영하고 log_softmax를 걸어 "다음 토큰이 무엇일지"의 로그 확률을 낸다.
  • make_model: 하이퍼파라미터(N=6, d_model=512, d_ff=2048, h=8, dropout=0.1)로 모델을 조립한다. 어텐션·FFN·PE를 하나씩 만들어 두고 copy.deepcopy로 필요한 곳마다 복제한다. 마지막에 2차원 이상인 파라미터를 전부 Xavier(Glorot) 균등분포로 초기화한다. 원본 주석대로 "This was important"다.

실행 결과에서 볼 것

  • 15단계에서 만들 사전 크기(독일어 8,316, 영어 6,384)로 조립하면 파라미터가 약 5,500만 개다. 디코더가 가장 크다.
  • encode → decode → generator를 지나며 모양이 (1, 9) → (1, 9, 512) → (1, 5, 512) → (1, 5, 6384)로 바뀐다.
  • 초기화된 가중치의 최댓값이 Xavier 이론 한계 $\sqrt{6/(512+2048)}$와 같다.

코드 변화

+61 −0 · 파일 전체 275줄

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

GitHub에서 보기 ↗
11import copy
22import math
33
44import torch
55import torch.nn as nn
6+from torch.nn.functional import log_softmax
67
78
89def attention(query, key, value, mask=None, dropout=None):
212213 for layer in self.layers:
213214 x = layer(x, memory, src_mask, tgt_mask)
214215 return self.norm(x)
216+
217+
218+class EncoderDecoder(nn.Module):
219+ """
220+ A standard Encoder-Decoder architecture. Base for this and many
221+ other models.
222+ """
223+
224+ def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
225+ super(EncoderDecoder, self).__init__()
226+ self.encoder = encoder
227+ self.decoder = decoder
228+ self.src_embed = src_embed
229+ self.tgt_embed = tgt_embed
230+ self.generator = generator
231+
232+ def forward(self, src, tgt, src_mask, tgt_mask):
233+ "Take in and process masked src and target sequences."
234+ return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)
235+
236+ def encode(self, src, src_mask):
237+ return self.encoder(self.src_embed(src), src_mask)
238+
239+ def decode(self, memory, src_mask, tgt, tgt_mask):
240+ return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)
241+
242+
243+class Generator(nn.Module):
244+ "Define standard linear + softmax generation step."
245+
246+ def __init__(self, d_model, vocab):
247+ super(Generator, self).__init__()
248+ self.proj = nn.Linear(d_model, vocab)
249+
250+ def forward(self, x):
251+ return log_softmax(self.proj(x), dim=-1)
252+
253+
254+def make_model(
255+ src_vocab, tgt_vocab, N=6, d_model=512, d_ff=2048, h=8, dropout=0.1
256+):
257+ "Helper: Construct a model from hyperparameters."
258+ c = copy.deepcopy
259+ attn = MultiHeadedAttention(h, d_model)
260+ ff = PositionwiseFeedForward(d_model, d_ff, dropout)
261+ position = PositionalEncoding(d_model, dropout)
262+ model = EncoderDecoder(
263+ Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
264+ Decoder(DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout), N),
265+ nn.Sequential(Embeddings(d_model, src_vocab), c(position)),
266+ nn.Sequential(Embeddings(d_model, tgt_vocab), c(position)),
267+ Generator(d_model, tgt_vocab),
268+ )
269+
270+ # This was important from their code.
271+ # Initialize parameters with Glorot / fan_avg.
272+ for p in model.parameters():
273+ if p.dim() > 1:
274+ nn.init.xavier_uniform_(p)
275+ return model

실행 결과

실행한 코드 demo.py 보기
1import torch
2
3from transformer import make_model, subsequent_mask
4
5torch.manual_seed(0)
6
7model = make_model(src_vocab=8316, tgt_vocab=6384) # 15단계에서 만들 독일어·영어 사전 크기
8print("EncoderDecoder의 다섯 부품")
9for name, m in model.named_children():
10 print(f" {name:10s} {sum(p.numel() for p in m.parameters()):>12,} {m.__class__.__name__}")
11print(f" {'합계':10s} {sum(p.numel() for p in model.parameters()):>12,}")
12print()
13
14model.eval()
15src = torch.randint(4, 8316, (1, 9))
16tgt = torch.randint(4, 6384, (1, 5))
17src_mask = torch.ones(1, 1, 9, dtype=torch.bool)
18memory = model.encode(src, src_mask)
19out = model.decode(memory, src_mask, tgt, subsequent_mask(5))
20logp = model.generator(out)
21print("src", tuple(src.shape), "→ encode →", tuple(memory.shape))
22print("tgt", tuple(tgt.shape), "→ decode →", tuple(out.shape))
23print("generator →", tuple(logp.shape), " (다음 토큰의 로그 확률)")
24print("확률 합:", [f"{v:.3f}" for v in logp.exp().sum(-1)[0].tolist()])
25print()
26w = model.encoder.layers[0].feed_forward.w_1.weight
27print(f"xavier_uniform_ 초기화: w_1 값 범위 ±{w.abs().max():.4f} (이론 한계 √(6/(512+2048)) = {(6 / (512 + 2048)) ** 0.5:.4f})")
EncoderDecoder의 다섯 부품
  encoder      18,915,328  Encoder
  decoder      25,225,216  Decoder
  src_embed     4,257,792  Sequential
  tgt_embed     3,268,608  Sequential
  generator     3,274,992  Generator
  합계           54,941,936

src (1, 9) → encode → (1, 9, 512)
tgt (1, 5) → decode → (1, 5, 512)
generator → (1, 5, 6384)  (다음 토큰의 로그 확률)
확률 합: ['1.000', '1.000', '1.000', '1.000', '1.000']

xavier_uniform_ 초기화: w_1 값 범위 ±0.0484  (이론 한계 √(6/(512+2048)) = 0.0484)