AI/구현

Seq2Seq의 Encoder

말하는 감자에요 2025. 5. 26. 15:34
728x90
반응형

지난 글에서는 Seq2Seq의 논문 리뷰를 통해 모델 구조와 기여 내용을 정리했습니다.

 

이번 글부터는 실제로 PyTorch를 사용해 Seq2Seq 모델을 직접 구현해보며, 영-한 번역기를 만드는 과정을 기록해보려 합니다.

 

오늘은 그 첫 번째 단계로, Seq2Seq 모델의 Encoder 부분을 구현해보겠습니다.


1. Seq2Seq

Seq2Seq 구조

 

Seq2Seq 모델은 입력 시퀀스를 고정된 벡터로 인코딩한 뒤, 이 벡터를 기반으로 출력 시퀀스를 생성하는 구조입니다.

 

이 구조는 기계번역, 텍스트 요약, 질의응답 등 다양한 자연어처리 $(NLP$) 과제에서 널리 활용됩니다.

구조 요약

  • Encoder: 입력 문장을 고정 길이의 벡터로 압축
  • Decoder: 해당 벡터를 기반으로 출력 문장을 생성

여기서 Encoder는 입력 문장의 의미를 최대한 잘 담아내는 표현$(벡터$)을 만들어야 하며,

이 점에서 AutoEncoder의 Encoder와 유사한 역할을 한다고 볼 수 있습니다.

 

✅ 즉, Encoder는 문장을 잘 번역하려면, 먼저 "잘 압축"해야 합니다!

 

아래 그림은 Encoder가 어떻게 동작하는지를 시각적으로 보여줍니다.

 

Encoder의 의미


2. Equation

2.1 Given dataset

즉, 각 입력과 출력 쌍은 길이가 다를 수 있습니다.


2.2 Get hidden states of encoder

 

모든 시간 단계의 hidden state를 모으면 다음과 같습니다:

 

 


2.2 Shapes


2.3 If we use bi-directional RNN,


3. Encoder 구현 코드

import torch
import torch.nn as nn
from torch.nn.utils.rnn import pack_padded_sequence as pack
from torch.nn.utils.rnn import pad_packed_sequence as unpack

class Encoder(nn.Module):
    
    def __init__(self, word_vec_size, hidden_size, n_layers=4, dropout_p=.2):
        super().__init__()
        
        # <https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html>
        self.rnn = nn.LSTM(
            word_vec_size,
            hidden_size // 2,
            num_layers=n_layers,
            dropout=dropout_p,
            bidirectional=True,
            batch_first=True
        )
    
    def forward(self, emb):
        # |emb| = (batch_size, length, word_vec_dim)
        
        if isinstance(emb, tuple):
            x, lengths = emb
            x = pack(x, lengths.tolist(), batch_first=True)
        
        else:
            x = emb
        
        y, h = self.rnn(x)
        # |y| = (batch_size, length, hidden_size)
        # |h[0]| = (num_layers * 2, batch_size, hidden_size // 2) -> final hidden state for last time-step in the sequence 
        # h는 (hidden_state, cell_State) -> final cell state for last time-step in the sequence
        
        if isinstance(emb, tuple):
            y, _ = unpack(y, batch_first=True)
        
        return y, h    

코드 설명

  • nn.LSTM
    • PyTorch의 LSTM 모듈입니다.
    • Encoder는 양방향(bidirectional=True) LSTM을 사용할 수 있기 때문에 사용했습니다.
    • hidden_size // 2인 이유는, encoder의 마지막 time-step의 hidden_state가 decoder에 들어가야 하는데, Encoder은 양방향, Decoder은 단방향이기 때문에 사이즈를 맞추고자 진행했습니다.
  • packed_padded_sequence 와 pad_packed_sequence예를 들어:이걸 batch로 만들면:이렇게 되면 RNN은 패딩된 0도 계산에 포함시켜야 해서 낭비가 생깁니다.pack_padded_sequence는 padding을 무시하고 실제 길이까지만 RNN이 계산하도록 바꿔줍니다.
    • x: ( batch_size, seq_len, feature_dim ) 형태의 텐서
    • lengths: 각 문장의 실제 길이 목록 (e.g., [3, 2])
    • 결과: PackedSequence 객체 (RNN이 패딩 없이 연산)
    ↩️ pad_packed_sequence
    from torch.nn.utils.rnn import pad_packed_sequence
    
    output, lengths = pad_packed_sequence(packed_output, batch_first=True)
    
    • output: ( batch_size, max_seq_len, hidden_size)
    • lengths: 각 문장의 실제 길이
  • RNN은 PackedSequence를 잘 처리하지만, 다시 일반 텐서로 돌려야 할 때는 pad_packed_sequence를 사용합니다.
  • from torch.nn.utils.rnn import pack_padded_sequence packed = pack (x, lengths, batch_first=True)
  • 해결: pack_padded_sequence
  • [[1, 2, 3], [4, 5, 0]] # ← padded
  • sent1 = [1, 2, 3] sent2 = [4, 5]
  • 보통 batch 단위로 문장을 처리합니다. 하지만 문장마다 길이가 다르기 때문에, 가장 긴 문장에 맞춰 나머지 문장에 패딩(padding)을 넣습니다.

다음 글에서는 Seq2Seq의 다른 구조를 구현해보도록 하겠습니다.

728x90
반응형