Level 6: 시퀀스 모델 (RNN/LSTM)
📝

Level 6

Seq2Seq 모델

인코더-디코더 구조로 시퀀스 변환하기

45분
Seq2Seq 모델 강의 영상
강의 영상 보기 (새 탭에서 재생)YouTube

📓Google Colab에서 실습하기

이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.

학습 내용

Seq2Seq (Sequence-to-Sequence) 모델

학습 목표

이 레슨을 완료하면:

  • Seq2Seq 모델이 무엇이고 왜 필요한지 이해할 수 있다
  • 인코더(Encoder)와 디코더(Decoder)의 역할을 설명할 수 있다
  • 컨텍스트 벡터의 개념과 한계를 파악할 수 있다
  • NumPy로 인코딩/디코딩 과정을 실습할 수 있다
  • Seq2Seq의 실제 활용 사례를 알 수 있다

핵심 메시지

"통역사처럼, 먼저 전체를 듣고 이해한 다음 번역을 시작하세요!" Seq2Seq은 입력 시퀀스를 하나의 요약 벡터로 압축(인코딩)한 뒤, 그것을 기반으로 출력 시퀀스를 생성(디코딩)합니다.


Seq2Seq이 필요한 이유

비유: 전문 통역사의 업무 방식

좋은 통역사는 이렇게 일합니다:

  1. 먼저 상대방의 말을 끝까지 듣는다 (인코딩)
  2. 머릿속에 핵심 의미를 정리한다 (컨텍스트 벡터)
  3. 정리된 의미를 바탕으로 번역을 시작한다 (디코딩)

단어 하나하나를 바로 번역하는 것이 아니라, 전체 의미를 이해한 후 번역하는 것이 핵심입니다!

이전까지 배운 RNN의 한계

지금까지 배운 RNN/LSTM은 입력과 출력의 길이가 같은 문제만 다뤘습니다.

[이전 RNN 방식] 입력: x1 x2 x3 x4 x5 (5개) | | | | | 출력: y1 y2 y3 y4 y5 (5개 - 입력과 같은 길이!)

하지만 현실에서는 입력과 출력의 길이가 다른 경우가 대부분입니다.

문제입력 길이출력 길이같은 길이?
영한 번역 "I love you" -> "나는 너를 사랑해"3단어3단어우연히 같음
영한 번역 "Thank you" -> "감사합니다"2단어1단어다름!
문서 요약100문장5문장다름!
챗봇 응답질문 길이답변 길이다름!

Seq2Seq은 입력과 출력의 길이가 달라도 처리할 수 있는 모델입니다.


Seq2Seq의 구조: 인코더와 디코더

전체 구조 한눈에 보기

[인코더 (Encoder)] [디코더 (Decoder)] "I" -> "love" -> "you" -> <EOS> <SOS> -> "나는" -> "너를" -> "사랑해" | | | | | | | | h1 -> h2 -> h3 -> h4 => d1 -> d2 -> d3 -> d4 | ^ 컨텍스트 벡터 --------+ (문장 전체의 요약)

인코더 (Encoder): "전체를 듣는다"

인코더는 입력 시퀀스를 하나씩 읽어서 **컨텍스트 벡터(context vector)**를 만듭니다.

비유: 통역사가 메모하기 상대방이 "I love you"라고 말하면, 통역사는 한 단어씩 들으면서 머릿속에 의미를 정리합니다. 마지막 단어까지 다 들은 후의 "머릿속 메모"가 바로 컨텍스트 벡터입니다.

인코더의 동작:

단계 1: "I"    입력 -> h1 = RNN(x_I, h0)        "아, 주어가 나구나"
단계 2: "love"  입력 -> h2 = RNN(x_love, h1)     "감정을 표현하는구나"
단계 3: "you"  입력 -> h3 = RNN(x_you, h2)       "너를 사랑한다는 뜻"

h3 = 컨텍스트 벡터 (문장 전체의 의미가 압축된 벡터!)

디코더 (Decoder): "번역을 시작한다"

디코더는 컨텍스트 벡터를 받아서 출력 시퀀스를 한 단어씩 생성합니다.

비유: 통역사가 말하기 시작 머릿속에 정리된 의미(컨텍스트 벡터)를 바탕으로, 한 단어씩 번역 결과를 말합니다. 이전에 말한 단어가 다음 단어 선택에 영향을 줍니다.

디코더의 동작:

초기 상태: d0 = 컨텍스트 벡터 (인코더가 만든 요약)

단계 1: <SOS> 입력 + d0 -> d1, 출력: "나는"
단계 2: "나는" 입력 + d1 -> d2, 출력: "너를"
단계 3: "너를" 입력 + d2 -> d3, 출력: "사랑해"
단계 4: "사랑해" 입력 + d3 -> d4, 출력: <EOS> (끝!)

특수 토큰의 역할

토큰이름역할
<SOS>Start of Sequence디코더에게 "번역 시작!"이라고 알려줌
<EOS>End of Sequence"번역 끝!"이라고 알려줌
<PAD>Padding배치 처리시 길이 맞추기

NumPy로 인코딩/디코딩 과정 체험하기

실행해보기: 인코더가 문장을 압축하는 과정

python
import numpy as np np.random.seed(42) words_input = ["I", "love", "you"] input_dim = 4 hidden_dim = 6 embeddings = { "I": np.array([1.0, 0.0, 0.5, 0.2]), "love": np.array([0.3, 1.0, 0.8, 0.9]), "you": np.array([0.8, 0.1, 0.6, 0.3]), } W_enc_x = np.random.randn(input_dim, hidden_dim) * 0.3 W_enc_h = np.random.randn(hidden_dim, hidden_dim) * 0.3 print("=== 인코더: 문장을 읽고 요약하기 ===") print() h = np.zeros(hidden_dim) for i, word in enumerate(words_input): x = embeddings[word] h = np.tanh(x @ W_enc_x + h @ W_enc_h) print(f"단계 {i+1}: '{word}' 읽음") print(f" 은닉 상태: {np.round(h, 3)}") info_so_far = " + ".join(words_input[:i+1]) print(f" 지금까지 읽은 내용: {info_so_far}") print() context_vector = h print("=" * 50) print(f"컨텍스트 벡터 (문장 전체의 요약):") print(f" {np.round(context_vector, 3)}") print(f" 차원: {len(context_vector)}") print(f" 'I love you' 전체가 이 {len(context_vector)}개 숫자에 압축됨!")

실행해보기: 디코더가 번역을 생성하는 과정

배치 처리를 위해 시퀀스 길이를 동일하게 맞춥니다.

패딩이 필요한 이유

시퀀스 1: [1, 2, 3]       (길이 3)
시퀀스 2: [4, 5, 6, 7, 8] (길이 5)
시퀀스 3: [9, 10]         (길이 2)

→ 배치 처리 시 길이가 다르면 행렬 연산 불가!

패딩 적용

python
⚠️ 로컬 실행 필요
from torch.nn.utils.rnn import pad_sequence # 길이가 다른 시퀀스들 seq1 = torch.tensor([1, 2, 3]) seq2 = torch.tensor([4, 5, 6, 7, 8]) seq3 = torch.tensor([9, 10]) # 패딩 적용 (최대 길이에 맞춤) padded = pad_sequence( [seq1, seq2, seq3], batch_first=True, padding_value=0 # PAD 토큰 인덱스 ) print(padded) # tensor([[1, 2, 3, 0, 0], # [4, 5, 6, 7, 8], # [9, 10, 0, 0, 0]])

트렁케이션 (Truncation)

최대 길이를 넘으면 잘라내기

python
max_length = 10 def truncate(sequence, max_len): return sequence[:max_len] long_seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] truncated = truncate(long_seq, max_length) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

전처리 파이프라인 구축

완전한 전처리 예제

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from collections import Counter

class TextDataset(Dataset):
    def __init__(self, texts, labels, vocab=None, max_len=100):
        self.texts = texts
        self.labels = labels
        self.max_len = max_len

        # 어휘 사전 구축
        if vocab is None:
            self.vocab = self._build_vocab(texts)
        else:
            self.vocab = vocab

    def _build_vocab(self, texts, min_freq=2):
        # 토큰화 및 빈도 계산
        counter = Counter()
        for text in texts:
            tokens = text.lower().split()
            counter.update(tokens)

        # 특수 토큰 + 빈도 이상 단어
        vocab = {'<PAD>': 0, '<UNK>': 1}
        for word, freq in counter.items():
            if freq >= min_freq:
                vocab[word] = len(vocab)

        return vocab

    def _encode(self, text):
        tokens = text.lower().split()
        indices = [
            self.vocab.get(token, self.vocab['<UNK>'])
            for token in tokens
        ]
        return indices

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        # 텍스트 → 인덱스
        indices = self._encode(self.texts[idx])

        # 트렁케이션
        indices = indices[:self.max_len]

        # 텐서 변환
        return {
            'input': torch.tensor(indices),
            'label': torch.tensor(self.labels[idx])
        }

def collate_fn(batch):
    inputs = [item['input'] for item in batch]
    labels = torch.stack([item['label'] for item in batch])

    # 패딩
    padded_inputs = nn.utils.rnn.pad_sequence(
        inputs, batch_first=True, padding_value=0
    )

    return {'input': padded_inputs, 'label': labels}

# 사용 예시
texts = [
    "I love this movie",
    "This film is terrible",
    "Great acting and story"
]
labels = [1, 0, 1]  # 긍정/부정

dataset = TextDataset(texts, labels)
dataloader = DataLoader(dataset, batch_size=2, collate_fn=collate_fn)

for batch in dataloader:
    print(f"Input shape: {batch['input'].shape}")
    print(f"Labels: {batch['label']}")

핵심 정리

단계목적결과
토큰화텍스트 분리토큰 리스트
정수 인코딩토큰 → 숫자인덱스 리스트
임베딩숫자 → 벡터밀집 벡터
패딩길이 맞추기동일 길이 시퀀스

실습 과제

  1. 토큰화 비교

    • 단어/문자/서브워드 토큰화 결과 비교
    • 각 방식의 시퀀스 길이 분석
  2. 어휘 사전 분석

    • 실제 텍스트로 어휘 사전 구축
    • 빈도별 단어 분포 시각화
  3. 사전 학습 임베딩

    • GloVe 임베딩 로드 및 활용
    • 유사 단어 검색 실험

다음 레슨 예고

다음 시간에는 지금까지 배운 LSTM과 텍스트 전처리를 활용하여 감성 분석(Sentiment Analysis) 모델을 구현합니다.

레슨 정보

레벨
Level 6: 시퀀스 모델 (RNN/LSTM)
예상 소요 시간
45분
참고 영상
YouTube 링크

💡실습 환경 안내

이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.

Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.