
📓Google Colab에서 실습하기
이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.
학습 내용
Seq2Seq (Sequence-to-Sequence) 모델
학습 목표
이 레슨을 완료하면:
- •Seq2Seq 모델이 무엇이고 왜 필요한지 이해할 수 있다
- •인코더(Encoder)와 디코더(Decoder)의 역할을 설명할 수 있다
- •컨텍스트 벡터의 개념과 한계를 파악할 수 있다
- •NumPy로 인코딩/디코딩 과정을 실습할 수 있다
- •Seq2Seq의 실제 활용 사례를 알 수 있다
핵심 메시지
"통역사처럼, 먼저 전체를 듣고 이해한 다음 번역을 시작하세요!" Seq2Seq은 입력 시퀀스를 하나의 요약 벡터로 압축(인코딩)한 뒤, 그것을 기반으로 출력 시퀀스를 생성(디코딩)합니다.
Seq2Seq이 필요한 이유
비유: 전문 통역사의 업무 방식
좋은 통역사는 이렇게 일합니다:
- •먼저 상대방의 말을 끝까지 듣는다 (인코딩)
- •머릿속에 핵심 의미를 정리한다 (컨텍스트 벡터)
- •정리된 의미를 바탕으로 번역을 시작한다 (디코딩)
단어 하나하나를 바로 번역하는 것이 아니라, 전체 의미를 이해한 후 번역하는 것이 핵심입니다!
이전까지 배운 RNN의 한계
지금까지 배운 RNN/LSTM은 입력과 출력의 길이가 같은 문제만 다뤘습니다.
하지만 현실에서는 입력과 출력의 길이가 다른 경우가 대부분입니다.
| 문제 | 입력 길이 | 출력 길이 | 같은 길이? |
|---|---|---|---|
| 영한 번역 "I love you" -> "나는 너를 사랑해" | 3단어 | 3단어 | 우연히 같음 |
| 영한 번역 "Thank you" -> "감사합니다" | 2단어 | 1단어 | 다름! |
| 문서 요약 | 100문장 | 5문장 | 다름! |
| 챗봇 응답 | 질문 길이 | 답변 길이 | 다름! |
Seq2Seq은 입력과 출력의 길이가 달라도 처리할 수 있는 모델입니다.
Seq2Seq의 구조: 인코더와 디코더
전체 구조 한눈에 보기
인코더 (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로 인코딩/디코딩 과정 체험하기
실행해보기: 인코더가 문장을 압축하는 과정
pythonimport 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)
최대 길이를 넘으면 잘라내기
pythonmax_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']}")
핵심 정리
| 단계 | 목적 | 결과 |
|---|---|---|
| 토큰화 | 텍스트 분리 | 토큰 리스트 |
| 정수 인코딩 | 토큰 → 숫자 | 인덱스 리스트 |
| 임베딩 | 숫자 → 벡터 | 밀집 벡터 |
| 패딩 | 길이 맞추기 | 동일 길이 시퀀스 |
실습 과제
- •
토큰화 비교
- •단어/문자/서브워드 토큰화 결과 비교
- •각 방식의 시퀀스 길이 분석
- •
어휘 사전 분석
- •실제 텍스트로 어휘 사전 구축
- •빈도별 단어 분포 시각화
- •
사전 학습 임베딩
- •GloVe 임베딩 로드 및 활용
- •유사 단어 검색 실험
다음 레슨 예고
다음 시간에는 지금까지 배운 LSTM과 텍스트 전처리를 활용하여 감성 분석(Sentiment Analysis) 모델을 구현합니다.
레슨 정보
- 레벨
- Level 6: 시퀀스 모델 (RNN/LSTM)
- 예상 소요 시간
- 45분
- 참고 영상
- YouTube 링크
💡실습 환경 안내
이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.
Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.