아래는 Transformer 아키텍처의 핵심 개념과 구조를 정리한 설명입니다. --- ## 1. 개요 - **논문 및 동기** - “Attention Is All You Need” (Vaswani et al., 2017)에서 제안 - RNN/LSTM 기반 시퀀스 모델의 병렬 처리 한계와 장거리 의존성 학습의 어려움을 해결하기 위해 등장 - **핵심 아이디어** - 순차적으로 처리되지 않고, 전체 시퀀스를 한 번에 처리하면서도 각 위치 간의 의존성을 모델링 - **셀프 어텐션(self-attention)** 메커니즘을 중심으로 설계 --- ## 2. 주요 구성 요소 1. **Scaled Dot-Product Attention** - 입력으로 Query(Q), Key(K), Value(V)를 받아 각각의 스코어를 계산 - 수식: Attention(Q,K,V)=softmax ⁣(QKTdk) V \text{Attention}(Q,K,V) = \mathrm{softmax}\!\Bigl(\frac{QK^T}{\sqrt{d_k}}\Bigr)\,V - √dk 스케일링을 통해 내적 값의 크기를 조절하여 학습 안정화 2. **Multi-Head Attention** - 여러 개(헤드)의 어텐션을 병렬로 수행한 뒤 결과를 합쳐 다양한 표현 학습 - 수식: MultiHead(Q,K,V)=Concat(head1,…,headh) WO \text{MultiHead}(Q,K,V) = \mathrm{Concat}(\text{head}_1, …, \text{head}_h)\,W^O - 각 헤드는 서로 다른 선형 투영 WiQ,WiK,WiVW_i^Q, W_i^K, W_i^V를 사용 3. **Position-wise Feed-Forward Network (FFN)** - 각 시퀀스 위치마다 독립적으로 적용되는 2-layer MLP - 수식: FFN(x)=max⁡(0,xW1+b1) W2+b2 \mathrm{FFN}(x) = \max(0, xW_1 + b_1)\,W_2 + b_2 4. **Positional Encoding** - 순서를 알 수 없는 어텐션에 위치 정보를 제공 - 사인·코사인 함수를 이용한 고정 인코딩: PE(pos,2i)=sin⁡(pos100002i/dmodel),PE(pos,2i+1)=cos⁡(pos100002i/dmodel) \begin{aligned} PE_{(pos,2i)} &= \sin\bigl(\tfrac{pos}{10000^{2i/d_\text{model}}}\bigr),\\ PE_{(pos,2i+1)} &= \cos\bigl(\tfrac{pos}{10000^{2i/d_\text{model}}}\bigr) \end{aligned} 5. **Residual Connection & Layer Normalization** - 각 서브층(sublayer) 앞뒤로 skip-connection 및 LayerNorm 적용 - 안정된 학습과 그라디언트 소실 방지 --- ## 3. 인코더(Encoder) 구조 각 인코더 레이어는 다음이 순차적으로 쌓여 있음(총 N개 레이어): 1. **Multi-Head Self-Attention** 2. **Add & Norm** 3. **Feed-Forward Network** 4. **Add & Norm** ![인코더 블록 구조](https://miro.medium.com/max/700/1*2kyh0wb9V4Jk74fvgFUYGQ.png) _(이미지 출처: 논문 설명 참조)_ --- ## 4. 디코더(Decoder) 구조 인코더와 유사하나, 디코더는 두 종류의 어텐션을 추가로 사용: 1. **Masked Multi-Head Self-Attention** - 미래 토큰을 보지 않도록 마스킹 2. **Multi-Head Attention over Encoder Outputs** - 인코더의 출력을 Key/Value로 사용해 디코딩 시 인코더 정보 참조 3. **Feed-Forward Network** 각 레이어마다 Add & Norm 적용, 총 N개 레이어 --- ## 5. 장점 및 한계 |장점|한계| |:--|:--| |- 완전 병렬 처리 가능 → 학습 속도 대폭 향상- 긴거리 의존성 학습에 강함- 다양한 변형 모델(BERT, GPT 등) 기반|- 시퀀스 길이에 비례해 메모리·연산량 급증- 짧은 입력에도 고비용으로 비효율적일 수 있음| --- ## 6. 활용 사례 - **자연어처리**: 기계번역, 요약, 질문응답 (e.g. BERT, GPT 시리즈) - **비전**: Vision Transformer(ViT) - **생물정보학**: 단백질 시퀀스 예측(AlphaFold의 Transformer 모듈) --- --- ## 코드 구현 ```python import math import torch import torch.nn as nn import torch.optim as optim # 위치 인코딩 모듈 class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=5000): super().__init__() pe = torch.zeros(max_len, d_model) # (max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1).float() # (max_len, 1) div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) # 짝수 인덱스 pe[:, 1::2] = torch.cos(position * div_term) # 홀수 인덱스 pe = pe.unsqueeze(0) # (1, max_len, d_model) self.register_buffer('pe', pe) def forward(self, x): # x: (batch_size, seq_len, d_model) x = x + self.pe[:, :x.size(1)] return x # Transformer 기반 Seq2Seq 모델 class TransformerSeq2Seq(nn.Module): def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, nhead=8, num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048, dropout=0.1, max_len=5000): super().__init__() self.d_model = d_model # 임베딩 + 위치 인코딩 self.src_tok_emb = nn.Embedding(src_vocab_size, d_model) self.tgt_tok_emb = nn.Embedding(tgt_vocab_size, d_model) self.positional_encoding = PositionalEncoding(d_model, max_len) # PyTorch 제공 Transformer 모듈 self.transformer = nn.Transformer(d_model=d_model, nhead=nhead, num_encoder_layers=num_encoder_layers, num_decoder_layers=num_decoder_layers, dim_feedforward=dim_feedforward, dropout=dropout) # 출력 어휘로 매핑 self.generator = nn.Linear(d_model, tgt_vocab_size) def forward(self, src, tgt, src_mask=None, tgt_mask=None, src_padding_mask=None, tgt_padding_mask=None, memory_key_padding_mask=None): """ src: (S, N) 시퀀스 길이 S, 배치 크기 N tgt: (T, N) masks: PyTorch Transformer API에 맞춘 mask들 """ # 1) 임베딩 + 스케일링 + 위치 인코딩 src_emb = self.src_tok_emb(src) * math.sqrt(self.d_model) # (S, N, d_model) tgt_emb = self.tgt_tok_emb(tgt) * math.sqrt(self.d_model) # (T, N, d_model) src_emb = self.positional_encoding(src_emb) tgt_emb = self.positional_encoding(tgt_emb) # 2) Transformer memory = self.transformer.encoder(src_emb, src_key_padding_mask=src_padding_mask) output = self.transformer.decoder(tgt_emb, memory, tgt_mask=tgt_mask, tgt_key_padding_mask=tgt_padding_mask, memory_key_padding_mask=memory_key_padding_mask) # 3) 출력 생성 logits = self.generator(output) # (T, N, tgt_vocab_size) return logits def encode(self, src, src_mask=None, src_padding_mask=None): src_emb = self.positional_encoding(self.src_tok_emb(src) * math.sqrt(self.d_model)) return self.transformer.encoder(src_emb, src_key_padding_mask=src_padding_mask) def decode(self, tgt, memory, tgt_mask=None, tgt_padding_mask=None, memory_key_padding_mask=None): tgt_emb = self.positional_encoding(self.tgt_tok_emb(tgt) * math.sqrt(self.d_model)) return self.transformer.decoder(tgt_emb, memory, tgt_mask=tgt_mask, tgt_key_padding_mask=tgt_padding_mask, memory_key_padding_mask=memory_key_padding_mask) # --- 사용 예제 스케치 --- def generate_square_subsequent_mask(sz): """Decoder가 미래 토큰을 참조하지 못하도록 마스킹 생성""" mask = (torch.triu(torch.ones(sz, sz)) == 1).transpose(0, 1) mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0)) return mask # 하이퍼파라미터 SRC_VOCAB_SIZE = 10000 TGT_VOCAB_SIZE = 10000 SEQ_LEN = 32 BATCH_SIZE = 16 # 모델, 옵티마이저, 손실함수 model = TransformerSeq2Seq(SRC_VOCAB_SIZE, TGT_VOCAB_SIZE) optimizer = optim.Adam(model.parameters(), lr=1e-4) criterion = nn.CrossEntropyLoss(ignore_index=0) # 패딩 토큰 0으로 가정 # 더미 데이터 src = torch.randint(1, SRC_VOCAB_SIZE, (SEQ_LEN, BATCH_SIZE)) tgt_input = torch.randint(1, TGT_VOCAB_SIZE, (SEQ_LEN, BATCH_SIZE)) tgt_output = torch.randint(1, TGT_VOCAB_SIZE, (SEQ_LEN, BATCH_SIZE)) # 마스크 tgt_mask = generate_square_subsequent_mask(SEQ_LEN) # 학습 루프 (단일 스텝 예시) model.train() optimizer.zero_grad() logits = model(src, tgt_input, tgt_mask=tgt_mask) # (T, N, V) -> (T*N, V) / (T, N) -> (T*N) loss = criterion(logits.view(-1, TGT_VOCAB_SIZE), tgt_output.view(-1)) loss.backward() optimizer.step() print(f"Training step done. Loss: {loss.item():.4f}") ``` --- ### 코드의 주요 포인트 1. **PositionalEncoding**: 순서 정보를 부여하기 위해 사인&코사인 함수를 사용했습니다. 2. **nn.Transformer**: PyTorch의 내장 모듈로, 인코더·디코더 블록을 손쉽게 쌓을 수 있습니다. 3. **마스킹**: 디코더가 미래 정보를 보지 않도록 `generate_square_subsequent_mask`를 사용해 인위적 마스크를 생성합니다. 4. **모델 사용 흐름**: - `src`/`tgt_input` → 임베딩 + 위치 인코딩 → Transformer → `generator` → 로짓 → 손실 계산 - 실제 생산 환경에선 소스·타겟 패딩 마스크도 함께 전달해야 합니다.