AI 개발 공부 공간

AI, 머신러닝, 딥러닝, Python & PyTorch, 실전 프로젝트

딥러닝/딥러닝: 자연어 처리

Seq2Seq 개념

qordnswnd123 2025. 2. 28. 12:02

1. Seq2Seq란

Seq2Seq(Sequence to Sequence) 모델은 자연어 처리(NLP) 분야에서 널리 사용되는 모델로, 입력 시퀀스를 받아 출력 시퀀스를 생성하는 데 사용됩니다.
Seq2Seq 모델의 핵심 아이디어는 두 개의 RNN(Recurrent Neural Network) 또는 LSTM(Long Short-Term Memory) 네트워크를 연결하여 입력 시퀀스를 처리하고 출력 시퀀스를 생성하는 것입니다.
모델의 구성은 아래와 같습니다.

1) 인코더(Encoder)

인코더는 입력 시퀀스를 받아 고정된 길이의 컨텍스트 벡터(context vector)로 인코딩합니다.
2) 디코더(Decoder)
디코더는 인코더의 컨텍스트 벡터를 받아 출력 시퀀스를 생성합니다.
3) 컨텍스트 벡터(Context Vector)
컨텍스트 벡터는 입력 시퀀스의 정보를 요약하여 단일 벡터로 표현한 것입니다.


2. 인코더(Encoder)

2.1. 인코더란?

인코더는 Seq2Seq(Sequence to Sequence) 모델의 핵심 구성 요소 중 하나로, 입력 시퀀스를 받아 고정된 길이의 벡터로 인코딩하는 역할을 합니다. 이 고정된 길이의 벡터를 컨텍스트 벡터(context vector)라고 하며, 디코더의 초기 은닉 상태로 사용됩니다. 인코더는 일반적으로 RNN(Recurrent Neural Network), LSTM(Long Short-Term Memory), 또는 GRU(Gated Recurrent Unit) 네트워크로 구성됩니다.

 

2.2 인코더의 구조

임베딩 층(Embedding Layer): 입력 시퀀스의 각 토큰을 고정된 길이의 임베딩 벡터로 변환합니다.


RNN/LSTM/GRU 층: 임베딩 벡터를 순차적으로 입력 받아 은닉 상태를 업데이트합니다. 각 시간 단계에서 네트워크는 이전 은닉 상태(hidden state)와 현재 입력 임베딩 벡터를 사용하여 새로운 은닉 상태를 계산합니다.


2.3. 인코더의 장점

효율적인 정보 압축 입력 시퀀스의 정보를 고정된 길이의 벡터로 압축하여 디코더로 전달할 수 있습니다.
다양한 네트워크 구성 가능: RNN, LSTM, GRU 등 다양한 네트워크를 사용할 수 있어 다양한 응용에 적합합니다.

 

3. 디코더(Decoder)

3.1. 디코더란?

디코더는 인코더에서 생성된 고정된 길이의 컨텍스트 벡터를 받아 출력
시퀀스를 생성하는 역할을 합니다. 디코더는 일반적으로 RNN(Recurrent Neural Network), LSTM(Long Short-Term Memory), 또는 GRU(Gated Recurrent Unit) 네트워크로 구성됩니다.
디코더는 컨텍스트 벡터(Context Vector)와 이전의 출력 값을 입력으로 받습니다. 이전의 출력 값은 예측할 단어의 이전 단어 입니다. 디코더는 컨텍스트 벡터로 전체적인 맥락을 이해하고, 이전의 출력 값을 를 이용해 다음 단어를 예측합니다.

 

3.2. 디코더의 구조


입력 임베딩 층(Embedding Layer): 디코더는 이전의 출력 토큰을 입력 으로 받아 고정된 길이의 임베딩 벡터로 변환하는 과정을 수행합니다. 이 임베딩 층은 토큰의 원-핫 인코딩 형태를 더 밀집된 벡터 형태로 매핑 하여, 효율적인 특성 표현을 가능하게 합니다.
재귀적 신경망 층(RNN/LSTM/GRU 층): 변환된 임베딩 벡터와 이전의 은닉 상태를 결합하여 새로운 은닉 상태를 계산합니다. 이 층은 RNN, LSTM 또는 GRU와 같은 다양한 형태의 재귀적 신경망을 사용할 수 있 습니다. 각 시간 단계마다 이 과정이 반복되어, 시퀀스 내 각 요소에 대한 컨텍스트가 포착됩니다.
출력 생성 층(Output Layer): 최종적으로, 새로운 은닉 상태를 사용하여 다음 출력 토큰을 예측합니다. 이 층은 일반적으로 소프트맥스 함수를 사용하여, 다음 토큰의 확률 분포를 계산하고, 이 중 가장 확률이 높은 토 큰을 선택합니다. 이는 디코더가 다음 시퀀스 요소를 예측하는 데 중요한 역할을 합니다.


3.3. 디코더의 장점

순차적 출력 생성: 입력 시퀀스를 처리한 컨텍스트 벡터를 기반으로 출력 시퀀스를 순차적으로 생성할 수 있습니다.
다양한 네트워크 구성 가능: 디코더는 RNN, LSTM, GRU 등 다양한 네트 워크를 사용할 수 있어 다양한 응용에 적합합니다.

유연한 입력 처리: 디코더는 이전 출력 토큰을 입력으로 받아 처리하므로, 시퀀스 길이에 관계없이 유연하게 동작할 수 있습니다.


4. 인코더 실습

import torch
import torch.nn as nn
import torch.nn.functional as F


class Encoder(nn.Module):
    def __init__(self, input_dim, emb_dim, hidden_dim, n_layers, dropout):
        '''
        input_dim: 단어의 개수
        emb_dim: 임베딩 차원
        hidden_dim: gru의 h의 차원
        n_layers: gru의 층의 수
        dropout: dropout 비율
        '''
        super().__init__()
        self.embedding = nn.Embedding(input_dim, emb_dim)  # 임베딩
        self.dropout = nn.Dropout(dropout)  # 드롭아웃
        self.gru = nn.GRU(input_size=emb_dim, # GRU
             hidden_size=hidden_dim,
             num_layers=n_layers,
             batch_first=True,
             dropout=dropout) 

    def forward(self, src):
        '''
        src: 인코더에 입력되는 문장
        '''
        embedded = self.dropout(self.embedding(src))  # 임베딩 층, 드롭아웃 적용
        outputs, hidden = self.gru(embedded)  # GRU 층

        return outputs, hidden  # 인코더 출력
# 하이퍼파라미터 정의
INPUT_DIM = 10000 # 단어사전의 단어 수
EMB_DIM = 3 # 임베딩 차원
HID_DIM = 5 # GRU의 히든 레이어의 차원
N_LAYERS = 2 # GRU 층의 수
DROPOUT = 0.5

# 인코더 모델 객체 생성
encoder = Encoder(INPUT_DIM, EMB_DIM, HID_DIM, N_LAYERS, DROPOUT)

# 테스트용 텐서
input_seq = torch.tensor([[1, 2, 3, 4, 5]], dtype=torch.long)

# Encode the input sequence
encoder_output, hidden = encoder(input_seq)

print("Encoder input shape:", input_seq.shape) # (단어 개수, 임베딩 차원 수)
print(hidden.shape) # ()
print(hidden)
Encoder input shape: torch.Size([1, 5])
torch.Size([2, 1, 5])
tensor([[[-0.2267, -0.1762, -0.0028, -0.4779,  0.5971]],

        [[-0.3766,  0.1256, -0.0306, -0.4325, -0.0520]]],
       grad_fn=<StackBackward0>)

5. 디코더 실습

import torch.nn.functional as F

class Decoder(nn.Module):
    def __init__(self, output_dim, emb_dim, hidden_dim, n_layers, dropout):
        '''
        output_dim: 출력 단어의 개수 (타겟 어휘 크기)
        emb_dim: 임베딩 차원
        hidden_dim: GRU의 h의 차원
        n_layers: GRU의 층의 수
        dropout: 드롭아웃 비율
        '''
        super().__init__()
        self.output_dim = output_dim # 단어사전 단어 개수
        self.embedding = nn.Embedding(output_dim, emb_dim)
        self.gru = nn.GRU(input_size=emb_dim,
                          hidden_size=hidden_dim,
                          num_layers=n_layers,
                          batch_first=True,
                          dropout=dropout)
        self.fc_out = nn.Linear(hidden_dim, output_dim)
        self.dropout = nn.Dropout(dropout)

    def forward(self, trg, hidden):
        '''
        trg: 타겟 언어의 이전단어
        hidden: 컨텍스트 벡터
        '''
        trg = trg.unsqueeze(1)  # 입력 차원 증가: [batch_size, 1]
        embedded = self.dropout(self.embedding(trg))  # 드롭아웃 적용 임베딩
        output, hidden = self.gru(embedded, hidden)  # GRU 실행
        prediction = self.fc_out(output.squeeze(1))  # 선형 레이어를 통한 로짓 계산
        prediction = F.softmax(prediction, dim=1)  # 소프트맥스 적용

        return prediction, hidden
INPUT_DIM = 10000
OUTPUT_DIM = 10000
EMB_DIM = 3
HID_DIM = 5
N_LAYERS = 2
DROPOUT = 0.5

# 테스트용 텐서
input_seq = torch.tensor([[1, 2, 3, 4, 5]], dtype=torch.long)
current_token = torch.tensor([101], dtype=torch.long)
print('input_seq', input_seq.shape) # 시퀀수의 수, 단어의 수
print('current_token', current_token.shape) # 단어의 수

encoder = Encoder(INPUT_DIM, EMB_DIM, HID_DIM, N_LAYERS, DROPOUT)
decoder = Decoder(OUTPUT_DIM, EMB_DIM, HID_DIM, N_LAYERS, DROPOUT)

# Decode the encoded output
_, context_vector = encoder(input_seq)
prediction, hidden = decoder(current_token, context_vector)

print(prediction.shape) # 단어 개수, 단어사전의 전체 단어 수
print(prediction)
input_seq torch.Size([1, 5])
current_token torch.Size([1])
torch.Size([1, 10000])
tensor([[8.8567e-05, 1.2908e-04, 1.4569e-04,  ..., 8.5171e-05, 9.1651e-05,
         1.2048e-04]], grad_fn=<SoftmaxBackward0>)

6. Seq2Seq 모델 실습

import random

class Seq2Seq(nn.Module):
    def __init__(self, encoder, decoder, device, trg_vocab_size, tokens):
        super().__init__()
        self.encoder = encoder
        self.decoder = decoder
        self.device = device
        self.sos_token = tokens['sos_token']
        self.eos_token = tokens['eos_token']
        self.trg_vocab_size = trg_vocab_size

    def forward(self, src, trg=None, teacher_force_ratio=1.0, max_len=50):
        # 인코더 실행, 컨텍스트 벡터 준비
        _, hidden = self.encoder(src)  

        # 첫 디코더 입력을 <sos> 토큰으로 설정
        current_token = torch.tensor([self.sos_token] * src.size(0)).to(self.device)

        # 최종 출력값 [batch_size, max_len, vocab_size]
        batch_size = src.shape[0]
        outputs = torch.zeros(batch_size, max_len, self.trg_vocab_size).to(self.device)
        
        # 실제 예측한 토큰 [batch_size, max_len]
        all_tokens = torch.zeros(batch_size, max_len).to(self.device)

        for t in range(1, max_len):
            output, hidden = self.decoder(current_token, hidden)
            outputs[:, t, :] = output
            top1 = output.argmax(1)
            all_tokens[:, t] = top1

            if teacher_force_ratio > 0 and trg is not None and t < trg.size(1):
                current_token = trg[:, t] if random.random() < teacher_force_ratio else top1
            else:
                current_token = top1

            # 종료 토큰을 만난 경우 디코딩 종료
            if (current_token == self.eos_token).all():
                break

        # 실제로 사용된 시퀀스 길이를 구합니다.
        actual_lengths = (all_tokens != self.eos_token).sum(dim=1)

        return outputs, actual_lengths
INPUT_DIM = 10000
OUTPUT_DIM = 10000
EMB_DIM = 3
HID_DIM = 5
N_LAYERS = 2
DROPOUT = 0.5
device = 'cpu'
tokens = {'sos_token': 0, 'eos_token': 1}

src = torch.tensor([[1, 2, 3, 4, 5]], dtype=torch.long).to(device).to(device)
trg = torch.tensor([[101, 102, 103, 104, 105]], dtype=torch.long).to(device)

encoder = Encoder(INPUT_DIM, EMB_DIM, HID_DIM, N_LAYERS, DROPOUT)
decoder = Decoder(OUTPUT_DIM, EMB_DIM, HID_DIM, N_LAYERS, DROPOUT)
model = Seq2Seq(encoder, decoder, device, trg_vocab_size=OUTPUT_DIM, tokens=tokens).to(device)

prediction = model(src, trg)

print(prediction[0].shape) #[batch_size, trg_len, output_dim]
print(prediction[0])
torch.Size([1, 50, 10000])
tensor([[[0.0000e+00, 0.0000e+00, 0.0000e+00,  ..., 0.0000e+00,
          0.0000e+00, 0.0000e+00],
         [8.5553e-05, 8.5570e-05, 1.2064e-04,  ..., 1.1699e-04,
          1.5557e-04, 7.1389e-05],
         [8.5037e-05, 8.6205e-05, 1.2577e-04,  ..., 1.1064e-04,
          1.7596e-04, 6.8759e-05],
         ...,
         [9.0486e-05, 7.8616e-05, 1.1852e-04,  ..., 1.2255e-04,
          2.8571e-04, 6.5046e-05],
         [9.0641e-05, 7.9400e-05, 1.1850e-04,  ..., 1.2384e-04,
          2.8484e-04, 6.4290e-05],
         [9.1075e-05, 8.0157e-05, 1.1672e-04,  ..., 1.2428e-04,
          2.7761e-04, 6.4394e-05]]], grad_fn=<CopySlices>)

'딥러닝 > 딥러닝: 자연어 처리' 카테고리의 다른 글

Seq2Seq 실습2  (0) 2025.03.01
Seq2Seq 실습1  (0) 2025.02.28
자연어 처리 모델  (0) 2025.02.26
임베딩(Embedding)  (0) 2025.02.25
AlexNet (2012)  (0) 2025.02.25